Git
Chapters ▾ 2nd Edition

10.7 Git’in Daxili İşləri - Maintenance və Məlumatların Bərpası

Maintenance və Məlumatların Bərpası

Bəzən bəzi təmizlik işləri görməli ola bilərsiniz - deponu daha yığcam etmək, idxal olunan deponu təmizləmək və ya itirilmiş işi bərpa etmək. Bu bölmə bu ssenarilərdən bəzilərini əhatə edəcəkdir.

Maintenance

Bəzən, Git avtomatik olaraq “auto gc” adlı bir əmr işlədir. Çox vaxt bu əmr heç bir şey etmir. Bununla birlikdə, bir çox boş obyekt (packfile-da olmayan obyektlər) və ya çox çox paket varsa, Git tam hüquqlu bir git gc əmrini işə salır. “gc” zibil yığmaq deməkdir və bu əmr bir sıra şeyləri yerinə yetirir: bütün boş obyektləri toplayır və onları paketlərə yerləşdirir, paketləri bir böyük paketə birləşdirir hər hansı bir commit-dən əlçatmaz olan və bir neçə aylıq olan obyektləri silir.

Avtomatik gc-ni aşağıdakı kimi manual olaraq işə sala bilərsiniz:

$ git gc --auto

Yenə də bu tam olaraq heç bir şey etmir. Git-in həqiqi bir gc əmrini işə salması üçün təxminən 7,000 boş obyekt və ya 50-dən çox paketiniz olmalıdır. Bu məhdudiyyətləri sırasıyla gc.auto və` gc.autopacklimit` konfiqurasiya parametrləri ilə dəyişdirə bilərsiniz.

gc-nin edəcəyi başqa bir şey, istinadlarınızı bir fayla yığmaqdır. Tutaq ki, deponuzda aşağıdakı branch-lar və etiketlər var:

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

Əgər siz git gc işlədirsinizsə, artıq bu fayllar refs qovluğunda mövcud olmayacaq. Git bunları effektivlik naminə belə görünən .git/packed-refs adlı bir fayla köçürəcəkdir:

$ 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 istinadı yeniləsəniz, Git bu faylı redaktə etmir, əksinə refs/heads üçün yeni bir fayl yazır. Müəyyən bir istinad üçün uyğun SHA-1 əldə etmək üçün Git, refs qovluğunda həmin istinadı yoxlayır və sonra packed-refs faylını geri yük kimi yoxlayır. Beləliklə, refs qovluğunda bir müraciət tapa bilmirsinizsə, ehtimal ki,packed-refs faylınızdadır.

Faylın ^ ilə başlayan son sətirinə diqqət yetirin. Bu o deməkdir ki, birbaşa yuxarıdakı etiket izahatlı bir etiketdir və həmin sətir izlənilmiş etiketin göstərdiyi commit-dir.

Data Recovery

Git səyahətinizin bir nöqtəsində təsadüfən bir commit-i itirə bilərsiniz. Ümumiyyətlə, bu, üzərində işləyən bir branch-ı zorla sildiyiniz üçün baş verir və nəticədə branch-ı istədiyinizi ortaya qoyur; ya da bir branch-ı yenidən sıfırlayırsınız, beləliklə bir şey istədiyiniz commit-dən imtina edirsiniz. Bunun baş verdiyini düşünsək, commit-lərinizi necə geri ala bilərsiniz?

Test anbarınızdakı master branch-ı köhnə bir commit-ə yenidən bərpa edən və sonra itirilmiş commit-ləri bərpa edən bir nümunə göstərək. Əvvəlcə bu deponuzun harada olduğunu nəzərdən keçirək:

$ git log --pretty=oneline
ab1afef80fac8e34258ff41fc1b867c702daa24b Modify repo a bit
484a59275031909e19aadb7c92262719cfcdf19a Create repo.rb
1a410efbd13591db07496601ebc7a059dd55cfe9 Third commit
cac0cab538b970a37ea1e769cbbde608743bc96d Second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d First commit

İndi, master branch-ınızı ortadakı commit-ə köçürün:

$ git reset --hard 1a410efbd13591db07496601ebc7a059dd55cfe9
HEAD is now at 1a410ef Third commit
$ git log --pretty=oneline
1a410efbd13591db07496601ebc7a059dd55cfe9 Third commit
cac0cab538b970a37ea1e769cbbde608743bc96d Second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d First commit

Ən yaxşı iki səmərəli işi itirmisiniz - bu commit-lərin əldə edilə biləcəyi bir branch-nız yoxdur. Ən son SHA-1 əmrini tapmalı və sonra ona işarə edən bir budaq əlavə etməlisiniz. Hiylə budur: son SHA-1 commit-ni tapmaq - bu yadınızda qalan kimi deyil, düzdür?

Çox vaxt, ən sürətli yol git reflog adlı bir vasitə istifadə etməkdir. İşləyərkən, Git hər dəfə dəyişdirdiyiniz zaman HEAD-in nə olduğunu səssizcə qeyd edir. Branch-ları hər dəfə işlətdiyiniz və ya dəyişdirdiyiniz zaman reflog yenilənir. Reflog ayrıca, Git Referansları-də qeyd etdiyimiz kimi, yalnız ref fayllarınıza SHA-1 dəyərini yazmaq əvəzinə istifadə etmək üçün başqa bir səbəb olan git update-ref əmri ilə də yenilənir. İstədiyiniz zaman harada olduğunuzu git reflog çalıştırarak görə bilərsiniz:

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

Burada yoxladığımız iki işi görə bilərik, lakin burada çox məlumat yoxdur. Eyni məlumatları daha faydalı bir şəkildə görmək üçün, git log -g işlədə bilərik ki, bu da reflog-unuz üçün normal bir günlük output-u təmin edəcəkdir.

$ 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

       Modify repo.rb a bit

Göründüyü kimi alt commit itirdiyiniz işdir, buna görə yeni commit yaratmaqla onu bərpa edə bilərsiniz. Məsələn, bu əməli (ab1afef) bərpa etmək üçün recover-branch başlada bilərsiniz:

$ git branch recover-branch ab1afef
$ git log --pretty=oneline recover-branch
ab1afef80fac8e34258ff41fc1b867c702daa24b Modify repo.rb a bit
484a59275031909e19aadb7c92262719cfcdf19a Create repo.rb
1a410efbd13591db07496601ebc7a059dd55cfe9 Third commit
cac0cab538b970a37ea1e769cbbde608743bc96d Second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d First commit

Əla - indi recovery-branch adlı bir branch-nız var, əvvəllər master branch-nızın olduğu yerlərdə ilk iki iş yenidən əlçatandır. Bundan sonra, itkinizin bir səbəbdən reflog-da olmadığını düşünək – bunu recovery-branch-nı çıxarıb təkrar qeydini silməklə simulyasiya edə bilərsiniz. İndi ilk iki commit-ə heç bir şey çatmır:

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

Reflog məlumatları .git/logs/ qovluğunda saxlanıldığından, sizdə heç bir reflog yoxdur. Bu anda bu işi necə bərpa edə bilərsiniz? Bir üsul, verilənlər bazanızın bütövlüyünü yoxlayan git fsck yardım proqramından istifadə etməkdir. Bunu --full seçimi ilə işə salırsınızsa, başqa bir obyekt tərəfindən göstərilməyən bütün obyektləri göstərir:

$ 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 vəziyyətdə, “dangling commit” sətrindən sonra itkin commit-nizi görə bilərsiniz. Eyni SHA-1-ə işarə edən bir qol əlavə edərək onu eyni şəkildə bərpa edə bilərsiniz.

Obyektlərin Silinməsi

Git haqqında çox yaxşı şey var, amma bu problemə səbəb ola biləcək bir xüsusiyyətdir, yəni, bir git clone-un hər faylın hər versiyası da daxil olmaqla layihənin bütün tarixini yükləməsidir. Bu əgər mənbə kodu olarsa yaxşıdır, çünki Git bu məlumatları səmərəli şəkildə sıxmaq üçün yüksək dərəcədə optimize edilmişdir. Bununla birlikdə, layihənizin tarixinin hər hansı bir nöqtəsində kimsə tək bir nəhəng fayl əlavə etsə, bütün klonlar, sonrakı commit-lərdə layihədən çıxarılan olsa da, bütün zaman üçün bu klonu yükləmək məcburiyyətində qalacaq. Tarixdən əlçatan olduğundan həmişə mövcud olacaq.

Subversion və ya Perforce depolarını Git-ə çevirdiyiniz zaman bu böyük bir problem ola bilər. Bu sistemlərdəki bütün tarixi yükləmədiyiniz üçün bu əlavə bir neçə nəticəyə səbəb olur. Başqa bir sistemdən idxal etmisinizsə və ya başqa bir şəkildə deponuzun lazım olduğundan daha böyük olduğunu görmünüzsə, burada böyük obyektləri necə tapa və silə biləcəyinizi görə bilərsiniz.

Xəbərdarlıq: bu texnika sizin commit tarixçəniz üçün dağıdıcıdır. O, böyük bir fayl arayışını silmək üçün dəyişdirməli olduğunuz ilk ağacdan bəri hər bir commit obyektini yenidən yazır. Bunu idxaldan dərhal sonra, kimsə işin commit üzərində qurulmasına başlamazdan əvvəl etsəniz, yaxşıdır - əks halda, bütün dəstəkçilərinizə işlərini yeni commit-lərinizə qaytarmaları lazım olduğunu bildirməlisiniz.

Nümayiş üçün test deponuza böyük bir fayl əlavə edəcək, növbəti commit-də götürüb tapacaqsınız və depodan qalıcı olaraq çıxaracaqsınız. Əvvəlcə tarixçənizə böyük bir obyekt əlavə edin:

$ 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

Vay - layihənizə böyük bir tarball əlavə etmək istəmirdiniz. Ən yaxşısı bundan yaxa qurtarmaq üçün:

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

İndi, məlumat bazanızı gc edin və nə qədər yer istifadə etdiyinizi 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)

Nə qədər yer istifadə etdiyinizi görmək üçün count-objects əmrini işə sala bilərsiniz:

$ 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 paketlərinizin kilobayt ölçüsündədir, buna görə demək olar ki, 5MB istifadə edirsiniz. Son commit-dən əvvəl, 2K-a yaxın istifadə edirdiniz - açıq şəkildə, əvvəlki commit-dən faylın silinməsi tarixçənizdən silinmədi. Hər kəs bu deponu klonladıqda, yalnız bu kiçik layihəni əldə etmək üçün bütün 5 MB-ı klonlamalı olacaq, çünki siz təsadüfən böyük bir fayl əlavə etmisiniz. Gəlin bundan qurtulaq.

Əvvəlcə onu tapmaq lazımdır. Bu vəziyyətdə, bunun hansı fayl olduğunu artıq bilirsiniz. Ancaq güman etmədiniz ki; hansı fayl və ya sənədlərin bu qədər yer tutduğunu necə müəyyənləşdirərdiniz? git gc-i işlədirsinizsə, bütün obyektlər packfile-dadır; böyük obyektləri git verify-pack adlı başqa bir plumbing əmrini işə salmaqla və sənəddəki fayl ölçüsü olan üçüncü sahəyə görə ayırmaqla təyin edə bilərsiniz. Yalnız son bir neçə ən böyük sənədlə maraqlandığınız üçün onu tail əmri ilə ötürə bilərsiniz:

$ 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

Aşağıdakı böyük obyekt: 5 MB. Hansı fayl olduğunu tapmaq üçün qısa müddətdə Xüsusi bir Commit-Mesaj Formatının Tətbiq Edilməsi-da istifadə etdiyiniz rev-list əmrini istifadə edəcəksiniz. --objects-i rev-list-ə keçirsəniz, bütün SHA-1’ləri və bunlarla əlaqəli fayl yolları ilə blob SHA-1’ləri siyahıya alır. Blob adınızı tapmaq üçün bundan istifadə edə bilərsiniz:

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

İndi keçmişdəki bütün ağaclardan bu faylı silməlisiniz. Bu faylı hansı commit-in dəyişdirdiyini asanlıqla görə bilərsiniz:

$ git log --oneline --branches -- git.tgz
dadf725 Oops - remove large tarball
7b30847 Add git tarball

Bu faylı Git tarixinizdən tamamilə silmək üçün 7b30847-dən aşağıda olan bütün commit-ləri yenidən yazmalısınız. Bunu etmək üçün Tarixi Yenidən Yazmaq-də istifadə etdiyiniz filter-branch-dan istifadə edirsiniz:

$ 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çimi, Tarixi Yenidən Yazmaq-də istifadə olunan --tree-filter seçiminə bənzəyir, yalnız diskdə qeyd edilmiş sənədləri dəyişdirən bir əmr ötürmək əvəzinə, hər dəfə quruluş sahənizi və ya indeksinizi dəyişdirirsiniz.

Müəyyən bir faylı rm file kimi bir şeylə silmək əvəzinə, git rm --cached ilə silməlisiniz - diskdən yox, indeksdən çıxarmalısınız. Bunu bu şəkildə etməyinizin səbəbi sürətdir - çünki Git filterinizi işə salmadan əvvəl diskdəki hər bir düzəlişə baxmaq məcburiyyətində qalmadığı üçün proses çox daha sürətli ola bilər. Həmin tapşırığı --tree-filter ilə də yerinə yetirə bilərsiniz. git rm seçimi üçün --ignore-unmatch seçimi, silmək istədiyiniz şablonun olmadığı təqdirdə səhv etməməsini tələb edir. Nəhayət, filter-branch-dan tarixinizi yalnız 7b30847 commit-indən yenidən yazmasını xahiş edirsiniz, çünki bu problemin burada başladığını bilirsiniz. Əks təqdirdə, başlanğıcdan başlayacaq və lazımsız olaraq daha uzun çəkəcəkdir.

Tarixinizdə artıq həmin fayla istinad yoxdur. Bununla birlikdə reflogunuz və Git-in .git/refs/original altında filter-branch etdiyiniz zaman əlavə etdiyi yeni bir refs dəsti hələ də mövcuddur, buna görə onları silməli və sonra verilənlər bazasını yenidən paketləməlisiniz. Yenidən qablaşdırmadan əvvəl bu köhnə commit-lərə işarə edən bir şeydən qurtulmalısınız:

$ 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)

Gəlin nə qədər yer saxladığınıza baxaq:

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

Paketlənmiş depo ölçüsü 8K-a qədərdir, bu da 5MB-dən daha yaxşıdır. Ölçü dəyərindən görə bilərsiniz ki, böyük obyekt hələ də boş obyektlərinizdədir, buna görə də getməyib; lakin vacib olan odur ki, o, push və ya sonrakı bir klon üzərinə köçürülməyəcəkdir. Həqiqətən istəsəniz, --expire seçimi ilə git prune düyməsini basaraq obyekti tamamilə silə bilərsiniz:

$ 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