Git
Chapters ▾ 2nd Edition

7.6 Git Alətləri - Tarixi Yenidən Yazmaq

Tarixi Yenidən Yazmaq

Git ilə işləyərkən dəfələrlə local commit-lər tarixinizi yenidən nəzərdən keçirmək istəyə bilərsiniz. Git ilə əlaqəli ən yaxşı şeylərdən biri, mümkün olan ən son anda qərar verməyə imkan verməsidir. Səhnələşdirmə sahəsinə başlamazdan əvvəl hansı faylların hansı qeydlərə keçəcəyini, hələ git stash ilə bir şey üzərində işləmək istəmədiyinizi qərar verə bilərsiniz və əvvəllər görülən proseslərin görünməsi üçün yenidən fərqli bir şəkildə baş vermiş kimi yaza bilərsiniz. Bu, işinizi başqaları ilə bölüşməzdən əvvəl əmrlərin dəyişdirilməsini, mesajların dəyişdirilməsini və ya commit edilən fayların dəyişdirilməsini, bir yerə yığılmağı, ayrılmağı və ya tamamilə silinməsini ehtiva edə bilər.

Bu bölmədə bu tapşırıqları necə yerinə yetirəcəyinizi görürsünüz ki, başqaları ilə paylaşmazdan əvvəl commit tarixçənizi istədiyiniz şəkildə göstərə biləsiniz.

Note
İşinizdən məmnun olana qədər push etməyin

Git-in əsas qaydalarından biri budur ki, klon daxilində bu qədər iş local olduğundan, tarixinizi yenidən locally olaraq yenidən yazmaq üçün çoxlu azadlığa sahibsiniz. Ancaq işinizi push etdikdən sonra tamamilə fərqli bir hekayədir və dəyişdirmək üçün əsaslı səbəbiniz olmadığı təqdirdə sövq edilən işi yekun hesab etməlisiniz. Bir sözlə, işinizdən məmnun olana qədər və onu dünyanın qalan hissəsi ilə bölüşməyə hazır olana qədər push etməkdən çəkinməlisiniz.

Son Commit Dəyişdirilməsi

Ən son cimmit-i dəyişdirmək, bəlkə də tarixin ən çox yayılmış yenidən yazılmasıdır. Sonuncu commit-nizə görə tez-tez iki əsas işi görmək istəyəcəksiniz: sadəcə commit mesajını dəyişdirin və ya faylları əlavə etmək, silmək və dəyişdirməklə commit-in həqiqi məzmununu dəyişdirin.

$ git commit --amend

Yuxarıdakı əmr əvvəlki commit mesajını redaktor sessiyasına yükləyir, burada mesajda dəyişiklik edə, həmin dəyişiklikləri saxlaya və çıxa bilərsiniz. Redaktoru saxlayıb bağladığınız zaman, redaktor yenilənmiş commit mesajı olan yeni bir commit yazır və onu yeni son commit-nizə çevirir.

Digər tərəfdən, son commit-in əsl content-ini dəyişdirmək istəyirsinizsə, proses əsasən eyni şəkildə işləyir - əvvəl unutduğunuzu düşündüyünüz dəyişiklikləri edin, bu dəyişiklikləri səhnələşdirin və sonrakı git commit --amend yeni, yaxşılaşdırılmış commit-nizlə son commit-i olan replaces edəcəkdir.

Bu texnika ilə diqqətli olmalısınız, çünki dəyişikliklər commit-in SHA-1-ini dəyişdirir. Çox kiçik bir rebase-ə bənzəyir - son commit-nizin əvvəldən push etmsisnizsə, düzəliş etməyin.

Tip
Dəyişdirilmiş commit-in dəyişdirilmiş commit mesajına ehtiyacı ola bilər (ya da olmaya bilər)

Bir commit-i dəyişdirdiyiniz zaman həm commit mesajını, həm də commit—​in məzmununu dəyişdirmək imkanınız var. Commit-in məzmununu əsaslı şəkildə dəyişdirirsinizsə, demək olar ki, dəyişiklik edilmiş məzmunu əks etdirmək üçün commit mesajını yeniləməlisiniz.

Digər tərəfdən, düzəlişləriniz əvvəlcədən edilən mesajın çox yaxşı olması üçün (mənasız bir səhvi düzəltmək və ya səhnəyə qoymağı unutduğunuz bir faylı əlavə etmək) uyğun deyilsə, sadəcə dəyişikliklər edə, onları səhnələşdirə və lazımsız redaktordan qaça bilərsiniz:

$ git commit --amend --no-edit

Birdən Çox Commit Mesajının Dəyişdirilməsi

Keçmişinizə qayıdan bir prosesi dəyişdirmək üçün daha mürəkkəb alətlərə keçməlisiniz. Git-in bir dəyişdirmə tarixçəsi vasitəsi yoxdur, ancaq bir sıra commit-ləri başqasına köçürmək əvəzinə əvvəlcə əsaslandıqları HEAD üzərinə biraz bərpa etmək üçün rebase alətindən istifadə edə bilərsiniz. İnteraktiv rebase aləti ilə mesajı modify etmək və dəyişdirmək, fayl əlavə etmək və ya etmək istədiyiniz hər bir əməldən sonra dayandıra bilərsiniz. İnteraktiv olaraq git rebase seçiminə -i seçimi əlavə edərək reaktivliyi işə sala bilərsiniz. Yenidən yazmaq istədiyiniz əmri söyləyərək commit-ləri yenidən yazmaq istədiyinizi göstərməlisiniz.

Məsələn, son üç commit mesajını və ya bu qrupdakı commit mesajlarından birini dəyişdirmək istəyirsinizsə, düzəliş etmək istədiyiniz son commit-in valideynini git rebase -i üçün bir arqument olaraq verirsiniz, yəni HEAD~2^ və ya HEAD~3.

Son üç commit-i düzəltməyə çalışdığınız üçün ~3-ü xatırlamaq daha asan ola bilər, ancaq düzəliş etmək istədiyiniz son commit-in əsas hissəsi olan dörd commit-i əvvəl təyin etdiyinizi unutmayın:

$ git rebase -i HEAD~3

Yenidən unutmayın ki, bu bir rebasing əmrdir - dəyişdirilmiş bir mesajla HEAD ~ 3..HEAD aralığında hər bir commit və onun bütün nəsilləri yenidən yazılacaqdır. Artıq mərkəzi bir serverə sövq etdiyiniz hər hansı bir commit-i daxil etməyin - bunu etmək eyni dəyişikliyin alternativ versiyasını təqdim edərək digər developerləri çaşdıracaqdır.

Bu əmri işə salmaq mətn redaktorunuzda aşağıdakı kimi görünən commit-lərin siyahısını verir:

pick f7f3f6d Change my name a bit
pick 310154e Update README formatting and add blame
pick a5f4a0d Add cat-file

# Rebase 710f0f8..a5f4a0d onto 710f0f8
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

Qeyd etmək vacibdir ki, bu commit-lər normal olaraq log əmrindən istifadə etdiyinizdən əks qaydada verilmişdir. Bir log işlədirsinizsə, belə bir şey görürsünüz:

$ git log --pretty=format:"%h %s" HEAD~3..HEAD
a5f4a0d Add cat-file
310154e Update README formatting and add blame
f7f3f6d Change my name a bit

Ters sıraya diqqət yetirin. İnteraktiv rebase sizə çalışacağı bir skript verir. Komanda xəttində göstərdiyiniz commit-dən başlayacaq (HEAD ~ 3) və bu əmrlərin hər birində tətbiq olunan dəyişiklikləri yuxarıdan aşağıya doğru təkrarlayın. Ən yenisinin yerinə, ən köhnəsini ən üstdə siyahıya alır, çünki yenidən replay edəcəyi ilk budur.

Skripti redaktə etmək istədiyiniz commit-də dayandığı üçün düzəltməlisiniz. Bunu etmək üçün, ssenarinin dayandırılmasını istədiyiniz commit-lərin hər biri üçün ‘pick’ sözünü ‘edit’ sözünə çevirin. Məsələn, yalnız üçüncü commit mesajını dəyişdirmək üçün sənədi bu şəkildə dəyişirsiniz:

edit f7f3f6d Change my name a bit
pick 310154e Update README formatting and add blame
pick a5f4a0d Add cat-file

Redaktoru saxladığınızda və çıxdığınızda, Git sizi həmin siyahıda sonuncu commit-ə qaytarır və aşağıdakı mesajla komanda xəttinə salır:

$ git rebase -i HEAD~3
Stopped at f7f3f6d... changed my name a bit
You can amend the commit now, with

       git commit --amend

Once you're satisfied with your changes, run

       git rebase --continue

Bu təlimatlar sizə tam olaraq nə edəcəyinizi bildirir. Növ:

$ git commit --amend

Commit mesajını dəyişdirin və redaktordan çıxın. Sonra çalıştırın:

$ git rebase --continue

Bu əmr digər iki commit-i avtomatik olaraq tətbiq edəcək və sonra işiniz biticək. Seçimi daha çox sətirdə redaktə etmək üçün dəyişdirsəniz, dəyişdirmək üçün etdiyiniz hər bir commit üçün bu addımları təkrarlaya bilərsiniz. Hər dəfə Git dayanacaq, commit-i düzəltməyinizə icazə verin və bitirdikdən sonra davam edin.

Commit-lərin Yenidən Tənzimlənməsi

Commit-lərin yenidən sıralanması və ya tamamilə silinməsi üçün interaktiv reaksiyalardan da istifadə edə bilərsiniz. “added cat-file” commit-ini aradan qaldırmaq və digər iki commit-in təqdim olunduğu sıranı dəyişdirmək istəyirsinizsə, rebase skriptini burdan dəyişə bilərsiniz:

pick f7f3f6d Change my name a bit
pick 310154e Update README formatting and add blame
pick a5f4a0d Add cat-file

buna:

pick 310154e Update README formatting and add blame
pick f7f3f6d Change my name a bit

Redaktoru saxladığınızda və çıxdığınızda, Git branch-nızı bu commit-lərin valideyninə geri qaytarır, 310154e və sonra f7f3f6d tətbiq edir və sonra dayanır. Bu commit-in sırasını təsirli şəkildə dəyişdirir və “added cat-file” tamamilə aradan qaldırırsınız.

Squashing Commits

İnteraktiv rebasing alati vasitəsi ilə bir sıra commit-lər götürmək və onları tək bir işə salmaq da mümkündür. Skript rebase mesajında faydalı təlimatlar verir:

#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

Əgər “pick” və ya “edit” əvəzinə, “squash”-ı təyin etsəniz, Git həm bu dəyişikliyi, həm də dəyişikliyi birbaşa ondan əvvəl tətbiq edir və commit mesajlarını birləşdirməyə məcbur edir. Beləliklə, bu üç commit-dən bir commit götürmək istəyirsinizsə, ssenarini belə göstərəcəksiniz:

pick f7f3f6d Change my name a bit
squash 310154e Update README formatting and add blame
squash a5f4a0d Add cat-file

Redaktoru saxladıqda və çıxdıqda, Git hər üç dəyişikliyi tətbiq edir və sonra üç commit mesajını birləşdirmək üçün sizi yenidən redaktora qaytarır:

# This is a combination of 3 commits.
# The first commit's message is:
Change my name a bit

# This is the 2nd commit message:

Update README formatting and add blame

# This is the 3rd commit message:

Add cat-file

Bunu saxladığınızda əvvəlki üç commit-in dəyişikliklərini təqdim edən tək bir commit-niz var.

Bir Commit-i Bölmək

Bir commit-in bölünməsi bir commit-i ləğv edir, sonra qismən mərhələli edir və sona çatdırmaq istədiyiniz qədər commit yerinə yetirir. Məsələn, üç commit-nizin orta commit-ni bölmək istədiyinizi düşünək.

“README formatını yeniləyin və blame əlavə edin” əvəzinə, onu iki commit-ə bölmək istəyirsinizsə: birincisi üçün “README formatını yeniləyin”, ikincisi üçün “Blame əlavə edin”. Bölmək istədiyiniz commit-in təlimatını “edit” etməklə dəyişdirərək bunu rebase -i skriptində edə bilərsiniz:

pick f7f3f6d Change my name a bit
edit 310154e Update README formatting and add blame
pick a5f4a0d Add cat-file

Sonra, skript sizi əmr sətirinə endirdikdə, commit-i yenidən qurursunuz, yenidən qurulmuş dəyişiklikləri götürürsünüz və onlardan bir çox commit yaradırsınız.

Redaktoru saxladığınızda və çıxdığınızda, Git siyahınızdakı ilk commit-in valideyninə geri qayıdır, ilk commit-i tətbiq edir (f7f3f6d), ikincisini (310154e) tətbiq edir və sizi konsola salır. Orada, bu commit-i bir hash ilə git reset HEAD^ ilə yenidən qura bilərsiniz; bu commit-i effektiv şəkildə ləğv edir və dəyişdirilmiş sənədləri işlənmədən buraxır. İndi bir neçə hərəkətiniz olana qədər sənədləri hazırlayıb təqdim edə və bitdikdən sonra git rebase --continue düyməsini işə sala bilərsiniz:

$ git reset HEAD^
$ git add README
$ git commit -m 'Update README formatting'
$ git add lib/simplegit.rb
$ git commit -m 'Add blame'
$ git rebase --continue

Git, ssenaridəki son commit-i (a5f4a0d) tətbiq edir və tarixiniz belə görünür:

$ git log -4 --pretty=format:"%h %s"
1c002dd Add cat-file
9b29157 Add blame
35cfb2b Update README formatting
f7f3f6d Change my name a bit

Bu, siyahınızdakı ən son üç commit-in SHA-1-lərini dəyişdirir, buna görə heç bir dəyişdirilmiş commit-in həmin siyahıda artıq paylaşılan bir depoya sövq etdiyinizin görünməməsinə əmin olun. Diqqət yetirin ki, siyahıda sonuncu commit (f7f3f6d) dəyişməzdir. Bu commit-in ssenaridə göstərilməsinə baxmayaraq “pick” kimi qeyd olunduğundan və hər hansı bir endirim dəyişikliyindən əvvəl tətbiq olunduğu üçün Git commit-i dəyişdirilmədən tərk edir.

Note

Drew DeVault, git rebase-dən istifadə qaydalarını öyrənmək üçün praktik bir təlimat hazırladı. Burdan tapa bilərsiniz: https://git-rebase.io/

Nuclear Seçimi: filter-branch

Misal üçün e-poçt adresinizi qlobal olaraq dəyişdirmək və ya hər bir commit-dən bir sənəd çıxarmaq üçün daha çox sayda commit-i yenidən yazmaq lazımdırsa, istifadə edə biləcəyiniz başqa bir tarix yazma seçimi var. Əmr filter-branch-dir və tarixinizin böyük hissələrini yenidən yaza bilər, buna görə proyektiniz hələ public-ə açıq olmadıqca və digər insanlar sizin etdiyiniz vəzifələrin icrasına əsaslanmadığı təqdirdə istifadə etməməlisiniz.

Bununla birlikdə, çox faydalı ola bilər. Yaygın istifadə üsullarından bir neçəsini öyrənəcəksiniz, buna görə bacardığı bəzi şeylər haqqında bir fikir əldə edə bilərsiniz.

Caution

git filter-branch-in bir çox tələsi var və artıq tarixi yenidən yazmağın tövsiyə olunan yol deyil. Bunun əvəzinə, normal olaraq filter-branch-ə müraciət etdiyiniz bir çox tətbiqetmə üçün daha yaxşı iş görən bir Python skripti olan git-filter-repo istifadə etməyi düşünün. Sənədlərinə və mənbə koduna https://github.com/newren/git-filter-repo ünvanından baxmaq olar.

Hər Commit-dən Bir Sənədin Silinməsi

Bu olduqca yaygındır. Biri təsadüfən düşüncəsiz bir git add . İlə böyük bir ikili sənəd işlədir və onu hər yerdə silmək istəyirsən. Bəlkə də səhvən bir şifrə olan bir sənəd işlədmisiniz və layihənizi açıq mənbəyə çevirmək istəyirsiniz. filter-branch, yəqin ki, bütün tarixinizi təmizləmək üçün istifadə etmək istədiyiniz bir vasitədir. Bütün tarixinizdən passwords.txt adlı bir faylı silmək üçün --tree-filter seçimini filter-branch üçün istifadə edə bilərsiniz:

$ git filter-branch --tree-filter 'rm -f passwords.txt' HEAD
Rewrite 6b9b3cf04e7c5686a9cb838c3f36a8cb6a0fc2bd (21/21)
Ref 'refs/heads/master' was rewritten

--tree-filter seçimi, layihənin hər yoxlanışdan sonra göstərilən əmri işə salır və nəticələri tövsiyə edir. Bu vəziyyətdə, mövcud olub-olmamasından asılı olmayaraq hər snapshot-dan passwords.txt adlı bir faylı çıxararsınız. Səhvən yadda qalan bütün redaktor backup sənədlərini silmək istəyirsinizsə, git filter-branch --tree-filter 'rm -f *~' HEAD kimi bir şey işlədə bilərsiniz.

Git-in ağacları və commit-lərini yenidən yazdığını izləyə biləcəksiniz və sonra branch göstəricisini sonda hərəkət etdirə bilərsiniz. Bunu ümumiyyətlə bir test branch-ında etmək və nəticəni həqiqətən istədiyiniz şeyi müəyyənləşdirdikdən sonra master branch-nızı yenidən bərpa etmək daha yaxşıdır. Bütün branch-larınızda filter-branch işlətmək üçün əmrinə --all keçə bilərsiniz.

Bir Subdirectory-nı Yeni Root Halına Gətirmək

Fərz edək ki, başqa bir mənbədən idarəetmə sistemindən bir idxal etdiniz və heç bir mənası olmayan subdirectory-ləriniz var (trunk, tags və s.). trunk subdirectory-ni hər bir commit üçün yeni layihə kökü etmək istəyirsinizsə, filter-branch bu halda sizə kömək edə bilər:

$ git filter-branch --subdirectory-filter trunk HEAD
Rewrite 856f0bf61e41a27326cdae8f09fe708d679f596f (12/12)
Ref 'refs/heads/master' was rewritten

İndi yeni layihə root-unuz hər dəfə trunk subdirectory-dadır. Git ayrıca subdirectory-ni təsir etməyən commit-ləri avtomatik olaraq siləcəkdir.

E-poçt Ünvanlarını Qlobal Olaraq Dəyişdirmək

Başqa bir ümumi hal, işə başlamazdan əvvəl adınızı və e-poçt adresinizi təyin etmək üçün git config-i işə salmağı unutmusunuz və ya bəlkə də iş yerində bir layihə açaraq bütün iş e-poçt adreslərinizi şəxsi adresinizə dəyişdirmək istəyirsiniz.

Hər halda, birdən çox prosesdə e-poçt adreslərini bir toplu halında filter-branch ilə əvəz edə bilərsiniz. Yalnız özünüzə aid e-poçt adreslərini dəyişdirərkən diqqətli olmalısınız, beləliklə --commit-filter istifadə etməlisiniz:

$ git filter-branch --commit-filter '
        if [ "$GIT_AUTHOR_EMAIL" = "schacon@localhost" ];
        then
                GIT_AUTHOR_NAME="Scott Chacon";
                GIT_AUTHOR_EMAIL="schacon@example.com";
                git commit-tree "$@";
        else
                git commit-tree "$@";
        fi' HEAD

Bu, yeni ünvanınız üçün hər bir commit-i yenidən yazır. Qeydlər rəhbərlərinin SHA-1 dəyərlərini ehtiva etdiyindən, yalnız e-poçt ünvanlarına uyğun olanları deyil, əmr tarixinizdəki hər SHA-1 qeydini əvəz edir.