1-只取一個 commit
1-1-介紹
1-1-1-在 local 的堆疊的 commit
有一個經常發生的情況:我在追蹤一個有點棘手的 bug,但是它實在太難抓出來了,在不得已的情況下我加入了一些 debug 的指令,並且做了一些 commit。
所有的這些 debug 的指令都只在 bugFix 這個 branch 裡面。最後我終於找到這個 bug,並且 fix 掉它,接著撒花慶祝一下!
現在唯一的問題就是要把我在 bugFix branch 裡面所做的修改 merge 回 master branch。我可以簡單地透過 fast-forward 來 merge ,但這樣的話 master branch 就會包含這些含有 debug 指令的 commit 了。我相信一定有其它方法…
我們需要告訴 git 只去複製其中一個 commit。 這種情況跟之前的關卡有一點類似,我們可以使用一樣的指令來完成這個目的。
1 2 | git rebase -i git cherry-pick |
1-2-解題
這一個關卡是比較後面的關卡,你可以隨意決定你要選擇使用哪個指令,但是 bugFix 所指向的那個 commit 一定要可以被 master branch 包含到。
1-2-1-官網解法
1 2 | git rebase -i HEAD~3 git rebase bugFix master |
目前的 HEAD 停在 bugFix 分支上,使用指令 git rebase -i HEAD~3 (在目前 HEAD 包函現在的 commit 記錄點的基準點,要選起三個記錄點做 rebase 處理出新的分支,新產生的分支會在 C1 做叉出。),將二個 commit C2 與 C3 點下 Omit 按鈕,所以只有 C4 會出現在新的分支上。
使用指令 git rebase bugFix master,讓目前做為 rebase 基準點的分支 bugFix 將 master 接進來,有點像 git merge 的概念,如果基準點是同線的父層,就會有 git merge 的線性快轉效果,直接會將 master 分支的 commit 點接到 bugFix 的 commit 上取得裡面內容。
rebase 的概念可以參考 另一種合併方式(使用 rebase)- 誰 Rebase 誰有差嗎? 頁面說明
1-2-2-解法一
1 2 | git checkout master git cherry-pick C4 |
切換分支到 master ,在 master 分支上直接使用指令 git cherry-pick C4 取得 HASH C4 的 commit 記錄過來,這裡比較特別的是 C4 的 commit 與 bugFix 分支會還在,而直接會被判定為過關。
2-commit 的戲法 (主要使用 git commit –amend,針對同線不同分支的連續 commit 其中一個 commit 修改,完成後將 master 分支併於修改後的新分支上)
2-1-說明
2-1-1-commit 的戲法
下面這種情況也是經常出現的。例如你之前已經在 newImage branch 上做了一些 commit,然後又開了一個 branch 叫做 caption ,並且在上面做了一些相關的 commit ,因此它們看起來是一個接著一個的。
有點棘手的就是有時候你又想在之前的 commit 裡面做一些修改。在這個例子裡面,我們要去稍微修改一下 newImage 所指向的 commit,儘管已經是之前的 commit 了 。
為了克服這個困難,我們可以按照下面的方法來做:
– 先用 git rebase -i 將 commit 重新排序,然後把我們想要修改的 commit 移到最前面
– 然後用 commit --amend 來進行一些修改
– 接著再用 git rebase -i 來將他們按照最開始的順序重新排好
– 最後我們把 master 移到這個修改的最前端(用你自己喜歡的方法),就大功告成啦!
當然還有許多方法可以完成這個任務(我知道你在想 cherry-pick 啦),之後我們會多點關注這些技巧啦,但現在暫時只注意上面這種方法。
啊!最後還要提醒你一下最後所產生的 commit tree,因為我們把 commit 移動了兩次,所以會分別產生一個 apostrophe(單引號) commit。還有一個 apostrophe commit 是因為我們修改 commit 而加進來的。
2-2-解題
1 2 3 4 | git rebase -i HEAD~2 git commit --amend git rebase -i HEAD~2 git rebase caption master |
git rebase -i HEAD~2 將 commit 對調重排與開新分支。
git commit --amend 將 commit 在新建。
將 commit C3 與 C2''對調,送出指令後會另外在開啟新的分支出來,但舊的分支會直接不顯示。
git rebase caption master 將在同線的 master 以 caption 為基底取二個 commit ,指定目前的 master 分支,讓 master 與 caption 分支同步於同一個 commit 上。
git commit –amend 的用法
git commit --amend 等同於將原本的 commit 內容或是提交訊息做修改後,重新在發送出一個新的 commit 出來。
git commit --amend 相當於上次提交錯誤的信息被覆蓋了,gitk圖形化界面上看不到上次提交的信息,git log上也看不到之前的信息,而add 後再commit 相當於重新加了一個信息。相當於打了個補丁?例子:
Step1:我們先在工作區中創建兩個文件a.txt和b.txt。並且add到暫存區,然後執行提交操作:
Step2:此時我們查看一下我們的提交日誌:可以看到我們的提交日誌中顯示最新提交有兩個文件被改變。
Step3:此時我們發覺我們忘了創建文件c.txt,而我們認為c.txt應該和a.txt,b.txt一同提交,而且a.txt文件中應該有內容‘a’。於是我們在工作區中創建c.txt,並add到暫存區。並且修改a.txt(故意寫錯語法且沒有將a.txt的修改add到暫存區):
Step4:我們查看一下此時的提交日誌,可以看到上次的提交0c35a不見了,並且新的提交11225好就是上次提交的修補提交,它就像是在上次提交被無視了,修改後重新進行提交了一樣:
Step5:此時我們發現a.txt文件修改沒有成功,於是我們還得進行一次對a.txt的修改,將a.txt add到stage,然後再執行一次與上一次類似的提交修補:
3-commit 的戲法 #2 (cherry-pick 與 commit –amend 配合使用)
3-1-說明
假如你還沒有完成 commit 的戲法 #1(前面那一個關卡),請先完成之後再來這一關!
如你在上一個關卡所看到的,我們使用 rebase -i 來重新排列那些 commit。
只要把我們想要修改的 commit 移到最前面,我們就可以很容易地重新修改它,然後再把它們重新排成我們想要的順序。
但唯一的問題就是這樣做就要排很多次,有可能造成 rebase conflict。
下面就看看用另外一種方法 git cherry-pick 是怎麼做的吧!
要記住喔! cherry-pick 可以從 commit tree 的任何地方拿一個 commit 來放在 HEAD 上(只要那個 commit 不是 HEAD 的 parent)。
下面是一個簡單清楚的 demo:
1 | git cherry-pick C2 |
3-2-解題
在這一關和上一關一樣要去修改一個 commit 叫做 C2,但你要避免使用 rebase -i。自己想想看要怎麼解決吧!
3-2-1-解法一
1 2 3 4 5 | git checkout master git cherry-pick C2 git reset HEAD^ git cherry-pick C2' git cherry-pick C3 |
git cherry-pick <commit SHA-1 NO> 無法在同一支分支時使用,將 commit 在進行指令創建新的 commit 於同分支上,所以需要在第二行時,將 master 分支先退回 commit,但 C2' 還是會存在在數劇庫中,只要記得 <commit SHA-1 NO> 就可以透過 git cherry-pick 指令,接在退回的 master 分支上成為新的 commit 。
3-2-2-官網解法
1 2 3 4 | git checkout master git cherry-pick C2 git commit --amend git cherry-pick C3 |
git commit --amend 比照先前的章節,在目前的分支上使用等同於 git reset HEAD^ 加上 git cherry-pick <commit SHA-1 NO>,可以少打一次指令。
4-git tag
4-1-說明
就像你之前學到的一樣,branch 很容易被移動,而且當有新的 commit 時,又會再移動,branch 經常指向不同的 commit,branch 很容易改變。
你可能會有疑問,有沒有什麼方法可以永遠有一個指向 commit 的記號,例如,表示重大的軟體釋出,或者是修正很大的 bug,有沒有其它比 branch 更好的方法,可以永遠地指向這些 commit?
你說對了!git tag 可以解決這個問題,它們可以永遠地指向某個特定的 commit,就像是表示一個”里程碑”一樣。
更重要的是,當有新的 commit 時,它們也不會移動,你不可以 “checkout” 到 tag 上面 commit,tag 的存在就像是一個在 commit tree 上的表示特定訊息的一個錨。
讓我們來實際看一下 tag 長什麼樣子…
1 | git tag v1 C1 |
4-2-解題
在這個關卡中,建立一個如視覺化目標裡面的 tag,然後 checkout 到 v1 上面,要注意你會進到分離 HEAD 的狀態,這是因為你不能夠直接在 v1 上面做 commit。
在下個關卡中我們會介紹更多 tag 的應用…
1 2 3 | git tag v1 C2 git tag v0 C1 git checkout C2 // or git checkout tag v0 |
5-git describe
describe (中譯為描述) ,用來說明最在指定的分支名上,最近的 Tag 標籤編號有幾個 commit,最新的 commit 與最舊的 commit 共點有多少個。
5-1-說明
因為 tag 在 commit tree 上表示的是一個錨點,git 有一個指令可以用來顯示離你最近的錨點(也就是 tag),而且這個指令叫做 git describe!
當你已經完成了一個 git bisect(一個找尋有 bug 的 commit 的指令),或者是當你使用的是你跑去度假的同事的電腦時, git describe 可以幫助你了解你離最近的 tag 差了多少個 commit。
5-1-1-git describe 的使用方式
1 | git describe <ref> |
<ref> 是任何一個可以被 git 解讀成 commit 的位置,如果你沒有指定的話,git 會以你目前所在的位置為準( HEAD)。
指令的輸出就像這樣:
1 | <tag>_<numCommits>_g<hash> |
<tag> 表示的是離 <ref> 最近的 tag。
numCommits 是表示這個 tag 離 <ref> 有多少個 commit, <hash> 表示的是你所給定的 <ref> 所表示的 commit 的前七個 id。
讓我們來看一個例子,對於下面的 tree:
1 | git tag v2 C3 |
git describe master 會輸出: v1_2_gC2
解讀成 master 最近的 Tag 為 v1 共有二個 commit, _g 接著說明目前最新的 commit 在 C2。
git describe side 會輸出: v2_1_gC4
解讀成 side 分支最近的 Tag 為 v2 共有一個 commit, _g 接著說明目前最新的 commit 在 C4。
5-2-解題
git describe 就是這樣了!試著在這個關卡指定幾個位置來感受一下這個指令吧!
當你完成的時候,只要一個 commit 就可以結束這個關卡,我們會給你一個免費贈品:P
1 | git commit |