天天看點

場景化學習 git

linux 之父的第二件作品—— git 自從誕生後就改變了軟體生産和協作的面貌,gitlab、github、bitbucket、 gitbook、gerrit 等項目的出現都極大地豐富了現代化軟體工程實踐。業界介紹 git 各種用法的書已經是汗牛充棟,本文的特色在于通過問答化場景來串聯 git 實踐中的常用功能及背後原理。

如果你期望讓自己的 git 學習之旅更加有趣、記憶深刻,可以通過克隆樣例倉庫并 checkout 到每一節的 tag 來和我一起學習:

場景:小明剛修改完一個 bug, commit 完才想起忘記寫明修複的 bug 的 url

解決方案:<code>git commit —amend</code> 可以重寫最近一次送出的送出記錄

輸出:

重寫前:

通過<code>git commit --amend</code>重寫後:

有心的童鞋一定發現 commit sha 值發生了變化,那這裡引申出了一個注意點:

git 中一個 commit 的 sha 值由送出記錄、送出者、送出日期、創作者、創作日期、送出時的檔案樹、父送出 生成,這些因素中任意一項發生改變,都會導緻 sha 發生變化。其中父送出這個因素要注意下,它在後文場景"該用 rebase 還是 merge 同步代碼"中也是重要的一個因子。

注:以上重寫送出記錄的技巧也可以用來解決 git user.email 不符合團隊規定時導緻送出被拒的問題。

場景:小明剛為 test.c 添加了一個函數 some_func,結果在一次錯誤的 git reset head^ —hard 後不小心弄丢了這個送出,想要找回先前的送出

解決方案:<code>git reflog</code>可以讓你回溯最近的 git 操作記錄,進而找回丢失的送出

可見<code>29c8ead</code>便是小明想要恢複的送出。到底如何恢複呢,請接着看下一節

注:git reflog 依賴于 git 倉庫的 gc 狀況,并不一定總是能幫上忙,是以無論何時丢失後及時 git reflog 都是明智之舉。

場景:小明想要追加<code>已存在</code>但并不在目前工作分支上的送出<code>29c8ead</code>

解決方案:<code>git cherry-pick {target_commit}</code>即可将 <code>target-commit</code>追加到目前分支

場景:小明所在的組,有 2 位其他同學和他一起開發一個 c 語言項目,有的同學喜歡在從 master 分支同步代碼時用 master,有的同學喜歡用 rebase,這讓小明很痛苦,莫衷一是,到底應該如何抉擇呢?

解決方案:

讓我們先來看下 rebase vs merge 各自對代碼 commit 鍊的影響:

### rebase 前 master 分支的狀态

### rebase 前 base 分支的狀态

### 進行 rebase 操作:

站在 master 分支 rebase base 分支

### rebase 後 master 分支的狀态

可見在分支 master 上 rebase 分支 base 的實際操作是:

分從 master 分支的 head(最新的一個送出)和 base 分支的 head(最新的一個送出)各自往 parent 方向回溯找到兩者的共同 parent commit: f2c45e9fcd9715324ac576a862dc8ef08160b0b2

将目前代碼回撤到 f2c45e9fcd9715324ac576a862dc8ef08160b0b2

應用共同 parent commit 之後 base 分支的改動:2f8f3fc27eb938a63b1c7caae73a28e555f18310 和0a3b7ab9c04387a5f8ac69a44415de87d5077efb

應用共同 parent commit 之後 master 分支的改動:345447ca7f65c4f134bb2496092e4881cec2c66e

其中 2、3、4 步是一個三方合并過程。

同時也可以看出 master 分支上的修改:s4:03:添加來自master分支的修改 對應的 sha 由先前的:

8f919e54656f6b3e42932db689b7f7358555777f 變成了 345447ca7f65c4f134bb2496092e4881cec2c66e,這是由于它的父送出發生了改變導緻的。

對于 git 來說,隻認識這些生硬的 sha 值,并不知道真正的修改是否完全一樣,而這一點需要人來辨識。

是以可以看出 rebase 别的分支會導緻本分支上在中間強行插入一些 commit 進而送出記錄不是在尾部單向追加,本分支上尾部的 commit 的 sha 值會“被動”改變。基于這個特征,可以總結出選擇 rebase 還是merge 時的黃金法則

上遊分支合并下遊分支用 merge

下遊分支頻繁同步(比如每天,分支間差異較小)上遊分支改動用 rebase

下遊分支不頻繁同步(比如一年,分支間差異極大)上遊分支改動用 merge

作為團隊工作起點的主幹分支,不要 rebase 其他分支

場景:近期進修過《程式員的自我修養》後的小明最近在修複一個極其頑固的線上閃退,結果一共送出了 3 次才 code review 通過,小明想将這三個 commit 合并為一個 commit,因為這個三個 commit 邏輯上屬于一個 commit。這該怎麼實作呢?

小明的三次送出如下:

解決方案:git rebase -i {起點}

起點選擇你想修改的最老的(時間上先)送出的 parent。在小明的場景下就是 345447ca7f65c4f134bb2496092e4881cec2c66e

指令:

輸出(git editor 以 vim 為例):

此時我們将第二次,第三次 commit 前的 pick 改為 s 或 squash,意思即為将該 commit 壓縮至前一個送出。

改完,鍵入:wq儲存并退出目前編輯,會來到下一個編輯最終送出記錄的界面:

我們将它改為:

最終的送出記錄變成了: