Git
Chapters ▾ 2nd Edition

10.7 Dahili Git Ögeleri - Bakım ve Veri Kurtarma

Bakım ve Veri Kurtarma

Bazen veri havuzunu daha derli toplu hale getirmek, içe aktarılan bir veri havuzunu temizlemek veya kaybolan çalışmaları kurtarmak için bir miktar temizlik yapmanız gerekebilir. Bu bölümde bu senaryolardan bazıları ele alınacaktır.

Bakım

Arada sırada, Git otomatik olarak auto gc adlı bir komut çalıştırır. Çoğu zaman bu komut hiçbir şey yapmaz. Ancak, fazla sayıda gevşek nesne (paket dosyasında olmayan nesneler) veya fazla sayıda paket dosyası varsa, Git tam donanımlı bir git gc komutunu başlatır. gc çöp toplama (garbage collection) anlamına gelir ve bu komut birkaç şey yapar: tüm gevşek nesneleri toplayarak bunları paket dosyalarına yerleştirir, paket dosyalarını birleştirerek tek bir büyük paket dosyası haline getirir, birkaç ay öncesine dayanan ve hiçbir katkısına erişilemeyen nesneleri kaldırır.

Auto gc 'yi aşağıdaki gibi manuel olarak çalıştırabilirsiniz:

$ git gc --auto

Tekrar belirtmek gerekirse, bu genellikle hiçbir şey yapmaz. Gerçek bir gc komutunu çalıştırmak için yaklaşık 7.000 gevşek nesneye veya 50’den fazla paket dosyasına ihtiyacınız vardır. Bu sınırları sırasıyla gc.auto ve gc.autopacklimit yapılandırma ayarları ile değiştirebilirsiniz.

gc 'nin yapacağı diğer bir şey de referanslarınızı tek bir dosyaya paketlemektir. Diyelim ki repo şu dalları ve etiketleri içeriyor:

$ find .git/refs -type f
.git/refs/heads/experiment
.git/refs/heads/master
.git/refs/tags/v1.0
.git/refs/tags/v1.1

git gc komutunu çalıştırırsanız, bu dosyalar artık refs dizininde bulunmaz. Git verimlilik açısından onları .git/packed-refs adlı, şu şekilde gözüken bir dosyaya taşır:

$ cat .git/packed-refs
# pack-refs with: peeled fully-peeled
cac0cab538b970a37ea1e769cbbde608743bc96d refs/heads/experiment
ab1afef80fac8e34258ff41fc1b867c702daa24b refs/heads/master
cac0cab538b970a37ea1e769cbbde608743bc96d refs/tags/v1.0
9585191f37f7b0fb9444f35a9bf50de191beadc2 refs/tags/v1.1
^1a410efbd13591db07496601ebc7a059dd55cfe9

Bir referansı güncellerseniz Git bu dosyayı düzenlemez, bunun yerine refs/heads 'e yeni bir dosya yazar. Belirli bir referans için uygun SHA-1’i almak amacıyla Git, refs dizininde bu referansı kontrol eder ve ardından bir yedek olarak packed-refs dosyasını kontrol eder. Ancak, refs dizininde bir referans bulamazsanız, bu muhtemelen packed-refs dosyanızdadır.

Dosyanın ^ ile başlayan son satırına dikkat edin. Bu, doğrudan yukarıdaki etiketin açıklamalı bir etiket olduğu ve bu satırın açıklamalı etiketin işaret ettiği katkı olduğu anlamına gelir.

Veri Kurtarma

Git yolculuğunuzun bir noktasında, bir katkıyı kazara kaybedebilirsiniz. Bunu genellikle, üzerinde çalışma olan bir dalı zorla silip, sonunda dala yine ihtiyacınız olduğunu fark ettiğinizde yaşarsınız; veya bir dala sert sıfırlama yaparsınız, böylece ihtiyacınız olan bazı katkıları kaybetmiş olursunuz. Bu durum gerçekleşirse, katkıları nasıl geri alabilirsiniz?

İşte test repomdaki ana dalı bir önceki katkıya sert sıfırlayan ve sonra kaybolan katkıları kurtaran bir örnek: Önce, repomun şu anda nerede olduğunu gözden geçirelim:

$ git log --pretty=oneline
ab1afef80fac8e34258ff41fc1b867c702daa24b modified repo a bit
484a59275031909e19aadb7c92262719cfcdf19a added repo.rb
1a410efbd13591db07496601ebc7a059dd55cfe9 third commit
cac0cab538b970a37ea1e769cbbde608743bc96d second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit

Şimdi, master dalını ortadaki katkıya taşıyalım:

$ git reset --hard 1a410efbd13591db07496601ebc7a059dd55cfe9
HEAD is now at 1a410ef third commit
$ git log --pretty=oneline
1a410efbd13591db07496601ebc7a059dd55cfe9 third commit
cac0cab538b970a37ea1e769cbbde608743bc96d second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit

Etkili bir şekilde en üstteki iki katkıyı kaybettiniz ve bu katkılara erişilebilecek hiçbir dalınız yok. Bu katkıların SHA-1’ini bulmanız ve sonra ona işaret eden bir dal eklemeniz gerekiyor. Sorun, bu en son katkının SHA-1’ini bulmaktadır (neticede onu ezberlemediniz, değil mi)?

Genellikle, en hızlı yol git reflog adı verilen bir araç kullanmaktır. Çalışırken, Git HEAD’i her değiştirdiğinizde ne olduğunu sessizce kaydeder. Her bir katkı veya dal değiştirdiğinizde reflog güncellenir. Reflog ayrıca git update-ref komutu tarafından da güncellenir, bu da sadece SHA-1 değerini ref dosyalarınıza yazmak yerine onu kullanmanın başka bir nedenidir (Git Referansları bölümünde ele aldığımız gibi). Herhangi bir zamanda nerede olduğunuzu görmek için git reflog komutunu çalıştırarak geçmişinizi görebilirsiniz:

$ git reflog
1a410ef HEAD@{0}: reset: moving to 1a410ef
ab1afef HEAD@{1}: commit: modified repo.rb a bit
484a592 HEAD@{2}: commit: added repo.rb

Burada kontrol ettiğimiz iki katkıyı görebiliriz, ancak burada pek fazla bilgi yok. Aynı bilgiyi çok daha kullanışlı bir şekilde görmek için git log -g komutunu çalıştırabiliriz, bu size reflog’unuz için normal bir günlük (log) çıktısı verecektir.

$ git log -g
commit 1a410efbd13591db07496601ebc7a059dd55cfe9
Reflog: HEAD@{0} (Scott Chacon <schacon@gmail.com>)
Reflog message: updating HEAD
Author: Scott Chacon <schacon@gmail.com>
Date:   Fri May 22 18:22:37 2009 -0700

		third commit

commit ab1afef80fac8e34258ff41fc1b867c702daa24b
Reflog: HEAD@{1} (Scott Chacon <schacon@gmail.com>)
Reflog message: updating HEAD
Author: Scott Chacon <schacon@gmail.com>
Date:   Fri May 22 18:15:24 2009 -0700

       modified repo.rb a bit

Görünüşe göre kaybettiğiniz katkı en alttaki katkıdır, bu yüzden onu o katkıya işaret eden yeni bir dal oluşturarak kurtarabilirsiniz. Örneğin, o katkıya (ab1afef) işaret eden recover-branch adında bir dal başlatabilirsiniz:

$ git branch recover-branch ab1afef
$ git log --pretty=oneline recover-branch
ab1afef80fac8e34258ff41fc1b867c702daa24b modified repo a bit
484a59275031909e19aadb7c92262719cfcdf19a added repo.rb
1a410efbd13591db07496601ebc7a059dd55cfe9 third commit
cac0cab538b970a37ea1e769cbbde608743bc96d second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit

Harika – artık master dalınızın bulunduğu yerde olan recover-branch adında bir dalınız var, böylece ilk iki katkıya yeniden ulaşabilirsiniz. Şimdi, varsayalım ki kaybınız reflog’da değildi (recover-branch'i kaldırarak ve reflog’u silerek bu durumu canlandırabilirsiniz edebilirsiniz). Şimdi, ilk iki katkı hiçbir şey tarafından ulaşılamaz durumda:

$ git branch -D recover-branch
$ rm -Rf .git/logs/

Çünkü reflog verileri .git/logs/ dizininde saklanır, hiçbir etkin reflog’unuz yoktur. Bu noktada bu katkıyı nasıl kurtarabilirsiniz? Bir yol, veritabanınızı bütünlük açısından kontrol eden git fsck yardımcı programını kullanmaktır. Onu --full seçeneği ile çalıştırırsanız, başka bir nesne tarafından işaret edilmeyen tüm nesneleri size gösterir:

$ git fsck --full
Checking object directories: 100% (256/256), done.
Checking objects: 100% (18/18), done.
dangling blob d670460b4b4aece5915caf5c68d12f560a9fe3e4
dangling commit ab1afef80fac8e34258ff41fc1b867c702daa24b
dangling tree aea790b9a58f6cf6f2804eeac9f0abbe9631e4c9
dangling blob 7108f7ecb345ee9d0084193f147cdad4d2998293

Bu durumda, eksik katkınızı ``dangling commit`` (sallantıdaki katkı) dizgesinden sonra görebilirsiniz. Aynı şekilde, o SHA-1’e işaret eden bir dal ekleyerek onu kurtarabilirsiniz.

Nesneleri Silmek

Git’in harika birçok özelliği olsa da, sorunlara da neden olabilen bir özelligi: git clone komutunun projenin her dosyasının tüm sürümlerini de içermekte olan "tüm geçmişini" indirmesidir. Bu, hepsinin kaynak kodu olduğu durumlar için sorun değildir, çünkü Git bu veriyi etkili bir şekilde sıkıştırmak için yüksek derecede optimize edilmiştir. Ancak, projenizin geçmişinde herhangi bir noktada tek bir büyük dosya ekleyen biri olduğunda, her zaman için her klonun o büyük dosyayı indirmek zorunda kalacak olması - o dosya sonraki katkıda projeden kaldırılmış olsa bile - geçerli bir durumdur. Çünkü geçmişten erişilebilirdir ve her zaman orada olacaktır.

Bu, Subversion veya Perforce repolarını Git’e dönüştürürken büyük bir sorun olabilir. Bu sistemlerde tüm geçmişi indirmediğiniz için, bu tür bir eklemenin bazı sonuçları vardır. Başka bir sistemden bir içe aktarma yaptıysanız veya başka bir şekilde deponuzun beklenenden çok daha büyük olduğunu görürseniz, büyük nesneleri bulup kaldırmanın bir yolunu aşağıda bulabilirsiniz.

Dikkat: Bu teknik, katkı geçmişinize zarar vericidir. Büyük bir dosyanın referansını kaldırmak için değiştirirseniz, ilk ağacınızdan itibaren her katkı nesnesini yeniden yazacaktır. Bunu, bir içe aktarmadan hemen sonra ve henüz hiç kimse çalışmaya başlamamışken yaparsanız, sorunsuzdur. Aksi takdirde, tüm katkıda bulunanlara, çalışmalarını yeni katkılara dayandırmaları gerektiğini bildirmeniz gerekir.

Bu tekniği göstermek için, test repomuza büyük bir dosya ekleyecek, bir sonraki katkıda onu kaldıracak, ardından onu bulup repodan kalıcı olarak sileceğiz. İlk olarak, geçmişinize büyük bir nesne ekleyin:

$ curl https://www.kernel.org/pub/software/scm/git/git-2.1.0.tar.gz > git.tgz
$ git add git.tgz
$ git commit -m 'add git tarball'
[master 7b30847] add git tarball
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 git.tgz

Eyvah! Projenize devasa bir tarball eklemek istememiştiniz. Onu kaldırmak daha iyi olur:

$ git rm git.tgz
rm 'git.tgz'
$ git commit -m 'oops - removed large tarball'
[master dadf725] oops - removed large tarball
 1 file changed, 0 insertions(+), 0 deletions(-)
 delete mode 100644 git.tgz

Şimdi (gc komutuyla) veritabanınızı temizleyin ve kullandığınız alanı görün:

$ git gc
Counting objects: 17, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (13/13), done.
Writing objects: 100% (17/17), done.
Total 17 (delta 1), reused 10 (delta 0)

Kullandığınız alanı hızlı bir şekilde görmek için count-objects komutunu çalıştırabilirsiniz:

$ git count-objects -v
count: 7
size: 32
in-pack: 17
packs: 1
size-pack: 4868
prune-packable: 0
garbage: 0
size-garbage: 0

size-pack girişi, paket dosyalarınızın kilobayt cinsinden boyutudur, bu yüzden neredeyse 5MB kullanıyorsunuz. Son katkıdan önce yaklaşık 2K kullanıyordunuz: açıkçası, dosyayı önceki katkıdan kaldırmak onu geçmişinizden kaldırmadı. Bu repoyu kopyalayan herkes, bu küçük projeyi almak için 5MB’lik tümünü kopyalamak zorunda kalacak, çünkü yanlışlıkla büyük bir dosya eklediniz. Hadi onu kaldıralım.

İlk olarak, onu bulmanız gerekiyor. Bu durumda, zaten hangi dosyanın olduğunu biliyorsunuz. Ancak öyle olmadığını varsayalım; bu kadar çok alanı alan dosyayı veya dosyaları nasıl tanımlarsınız? git gc komutunu çalıştırırsanız, tüm nesneler bir paket dosyasında bulunur; büyük nesneleri tanımlamak için git verify-pack adlı başka bir işleme komutunu çalıştırabilir ve çıktıdaki üçüncü alana göre sıralayabilirsiniz, işte bu dosya boyutudur. Ayrıca, yalnızca en büyük birkaç dosyaya ilgilendiğiniz için çıktıyı tail komutuyla borudan (pipe) geçirebilirsiniz:

$ git verify-pack -v .git/objects/pack/pack-29…69.idx \
  | sort -k 3 -n \
  | tail -3
dadf7258d699da2c8d89b09ef6670edb7d5f91b4 commit 229 159 12
033b4468fa6b2a9547a70d88d1bbe8bf3f9ed0d5 blob   22044 5792 4977696
82c99a3e86bb1267b236a4b6eff7868d97489af1 blob   4975916 4976258 1438

Büyük nesne en alttadır: 5MB. Bu dosyanın ne olduğunu bulmak için, kısa bir süre önce Belirli Bir Katkı Mesajı Formatını Zorunlu Hale Getirme bölümünde kullandığınız rev-list komutunu kullanacaksınız. rev-list'e --objects parametresini ilettiğinizde, tüm katkı SHA-1’leri ve bunlarla ilişkilendirilmiş dosya yollarıyla birlikte blok SHA-1’lerini listeler. Bunu blob’un adını bulmak için kullanabilirsiniz:

$ git rev-list --objects --all | grep 82c99a3
82c99a3e86bb1267b236a4b6eff7868d97489af1 git.tgz

Şimdi, bu dosyayı geçmişinizdeki tüm ağaçlardan kaldırmanız gerekiyor. Bu dosyayı değiştiren tüm katkıları kolayca görebilirsiniz:

$ git log --oneline --branches -- git.tgz
dadf725 oops - removed large tarball
7b30847 add git tarball

7b30847 'den sonraki tüm katkıları bu dosyayı tamamen Git geçmişinizden kaldırmak için yeniden yazmalısınız. Bunu yapmak için, Geçmişi Yeniden Yazma bölümünde kullandığınız filter-branch komutunu kullanırsınız:

$ git filter-branch --index-filter \
  'git rm --ignore-unmatch --cached git.tgz' -- 7b30847^..
Rewrite 7b30847d080183a1ab7d18fb202473b3096e9f34 (1/2)rm 'git.tgz'
Rewrite dadf7258d699da2c8d89b09ef6670edb7d5f91b4 (2/2)
Ref 'refs/heads/master' was rewritten

--index-filter seçeneği, Geçmişi Yeniden Yazma bölümünde kullanılan --tree-filter seçeneğine benzer, ancak her seferinde diske çıkarılan dosyaları değiştiren bir komut yerine, indeksinizi veya dizin belirtmenizi sağlar.

rm dosya gibi belirli bir dosyayı kaldırmak yerine, onu git rm --cached ile kaldırmalısınız; bu onu dizinden değil, indeksten kaldırır. Bunu bu şekilde yapmamızın nedeni hızdır; çünkü Git, filtrelerinizi çalıştırmadan önce her revizyonu diske çıkarmak zorunda kalmadığı için, işlem çok daha hızlı olabilir. Aynı görevi --tree-filter ile de gerçekleştirebilirsiniz. git rm 'nin --ignore-unmatch seçeneği, kaldırmaya çalıştığınız desen orada olmadığında hata vermemesini sağlar. Son olarak, bu sorunun başladığı yere yani 7b30847 katkısından başlayarak yalnızca geçmişinizi yeniden yazmasını istersiniz, aksi takdirde işlem başlangıçtan başlayacak ve gereksiz yere daha uzun sürecektir.

Geçmişiniz artık o dosyaya bir referans içermiyor. Yine de reflog’unuz ve filter-branch yaptığınızda Git’in eklediği yeni bir dizi refs, hala bu dosyaya işaret ediyor; bu yüzden onları kaldırmanız ve ardından veritabanını yeniden paketlemeniz gerekiyor. Yeniden paketlemeden önce bu eski katkılara dönük bir işaretçisi olan her şeyden kurtulmanız gerekiyor:

$ rm -Rf .git/refs/original
$ rm -Rf .git/logs/
$ git gc
Counting objects: 15, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (11/11), done.
Writing objects: 100% (15/15), done.
Total 15 (delta 1), reused 12 (delta 0)

Ne kadar alan kazandığınızı görelim.

$ git count-objects -v
count: 11
size: 4904
in-pack: 15
packs: 1
size-pack: 8
prune-packable: 0
garbage: 0
size-garbage: 0

Paketlenmiş repo boyutu 8K’ya kadardır, bu da 5 MB’tan çok daha iyidir. Boyut değerinden, büyük nesnenin hala gevşek nesnelerinizin içinde olduğunu, yani gitmediğini görebilirsiniz; ancak bir itme veya sonraki kopyalamayla (clone) aktarılmayacaktır ki önemli olan da budur. Eğer gerçekten isterseniz`git prune` komutunu --expire seçeneğiyle çalıştırarak nesneyi tamamen kaldırabilirsiniz:

$ git prune --expire now
$ git count-objects -v
count: 0
size: 0
in-pack: 15
packs: 1
size-pack: 8
prune-packable: 0
garbage: 0
size-garbage: 0
scroll-to-top