你需要知道的12個Git高階命令

InfoQ - 張龍發表於2016-01-29

眾所周知,Git目前已經是分散式版本控制領域的翹楚,圍繞著Git形成了完整的生態圈。學習Git,首先當然是學習Git的基本工作流。相比於SVN等傳統版本控制系統來說,Git是專為分散式版本控制而生的強大工具。使用Git時常用的命令有pull、commit、push等,貌似很簡單。不過,有時你會遇到合併衝突的情況,Git這時會將衝突標記出來,需要你手工來解決。有時,你會不小心將程式碼提交到錯誤的分支上,並且又推送到了遠端倉庫。還有些時候,你需要切換到不同的分支,但Git卻不讓你這麼做,因為還有未儲存的修改。如果需要通過另一個分支的提交來為程式碼打補丁該怎麼做呢?本文就將介紹12個Git高階命令,合理使用這些命令可以大大提升應用Git的效率。

1. 使用rebase而非merge來拉取上游修改

分支合併會被記錄為一次合併提交,這種做法是很有意義的。比如說,可以通過這種方式來標識一個新特性被合併到了釋出分支中。不過,當多個團隊成員工作在一個專案中並使用常規的git pull來同步分支時,提交時間線就會被不必要的合併提交所汙染。更好的做法則是使用git rebase將一個feature分支變基到master分支:

$ git checkout feature
$ git rebase master

這麼做會將整個feature分支移動到master分支的起點,它會合並master分支上所有新的提交。不過,相比於使用合併提交來說,變基會通過在原來的分支中為每次提交建立全新提交來重寫專案歷史。變基的主要好處在於你會得到一個更加整潔的專案歷史。此外,這裡還有關於變基的陷阱的一些討論。

2. 在執行git rebase後解決合併衝突

正如能力越大責任就越大一樣。在執行git rebase時,你可能會遇到合併衝突的情況。合併衝突表示兩個提交修改了同一個檔案的同一行,Git不知道該應用哪一個修改。這會導致如下所示的錯誤訊息:

Git會為你提供3個選擇來修復導致衝突的提交(fa39187):

  • 可以執行git rebase –abort來完全取消變基。這麼做會取消變基修改,並將分支置回到執行git rebase之前的狀態。
  • 可以執行git rebase –skip來完全忽略該提交。這樣,有問題的提交所引入的變化就不會被新增到歷史中。
  • 可以使用與合併衝突相同的標準步驟來解決衝突。

3. 臨時性儲存修改

在工作進行中時,有些東西常常會處於凌亂的狀態。如果這時需要切換到不同的分支該怎麼辦呢?Git是不允許你這麼做的,因為還有尚未儲存的修改。坦率地說,你並不想將半成品提交上去,後面再來修改。這個問題的解決之道就是使用git stash命令。Stash會接收工作目錄的當前狀態(比如說,修改了的追蹤檔案與暫存區的修改等),並將其儲存到未完成的修改棧中,這樣後面隨時可以再來修改。可以通過如下命令來暫存你的工作:

$ git stash
Saved working directory and index state WIP on feature: 3fc175f fix race condition
HEAD is now at 3fc175f fix race condition

現在,工作目錄就是乾淨的了:

$ git status
# On branch feature
nothing to commit, working directory clean

這時就可以安全地切換分支做別的事情了。不過不必擔心,暫存的提交依舊還在:

$ git stash list
stash@{0}: WIP on feature: 3fc175f fix race condition

稍後,在回到feature分支後,你就可以取回所有暫存的變更了:

$ git stash pop
On branch feature
Changes not staged for commit:
  (use "git add ..." to update what will be committed)

     modified:   index.html
Dropped refs/stash@{0} (ac2321cc3a33ba712b8e50c99a99d3c20da9d6b8)

關於暫存,還有其他一些選項可用,如下所示:

$ git stash save "describe it"   # give the stash a name
$ git stash clear                # delete a stashed commit
$ git stash save --keep-index    # stash only unstaged files

4. 克隆一個特定的遠端分支

如果想要從遠端倉庫中克隆一個特定的分支該怎麼做呢?通常你會使用git clone,不過這麼做會將所有其他分支都一併克隆下來。一個便捷的方式是使用git remote add:

$ git init  
$ git remote add -t  -f origin 
$ git checkout

5. 將cherry-pick遠端提交合併到自己的分支中

更有甚者,如果只想將遠端倉庫的一個特定提交合併到自己的分支中該怎麼做呢?可以使用git cherry-pick 來選擇給定SHA值的提交,然後將其合併到當前分支中:

$ git cherry-pick

6. 應用來自於不相關的本地倉庫的補丁

如果需要將另一個不相關的本地倉庫的提交補丁應用到當前倉庫該怎麼做呢?答案就是下面這條命令:

$ git --git-dir=/.git format-patch -k -1 --stdout  | git am -3 -k

7. 忽略追蹤檔案中的變更

如果你和你的同事操縱的是相同分支,那麼很有可能需要頻繁執行git merge或是git rebase。不過,這麼做可能會重置一些與環境相關的配置檔案,這樣在每次合併後都需要修改。與之相反,你可以通過如下命令永久性地告訴Git不要管某個本地檔案:

$ git update-index --assume-unchanged

8. 每隔X秒執行一次git pull

通常,合併衝突出現的原因在於你正在工作的本地倉庫不再反映遠端倉庫的當前狀態。這正是我們為什麼每天早晨要首先執行一次git pull的緣故。此外,你還可以在後臺通過指令碼(或是使用GNU Screen)每隔X秒呼叫一次git pull:

$ screen
$ for((i=1;i<=10000;i+=1)); do sleep X && git pull; done

9. 將子目錄分隔為新的倉庫

有時,你可能需要將Git倉庫中某個特定的目錄轉換為一個全新的倉庫。這可以通過git filter-branch來實現:

$ git filter-branch --prune-empty --subdirectory-filter  master
# Filter the master branch to your directory and remove empty commits
Rewrite 48dc599c80e20527ed902928085e7861e6b3cbe6 (89/89)
Ref 'refs/heads/master' was rewritten

現在,倉庫會包含指定子目錄中的所有檔案。雖然之前的所有檔案都會被刪除,但他們依舊存在於Git歷史中。現在可以將新的本地倉庫推送到遠端了。

10. 清理

有時,Git會提示“untracked working tree files”會“overwritten by checkout”。造成這種情況的原因有很多。不過通常來說,我們可以使用如下命令來保持工作樹的整潔,從而防止這種情況的發生:

$ git clean -f     # remove untracked files
$ git clean -fd    # remove untracked files/directories
$ git clean -nfd   # list all files/directories that would be removed

11. 將專案檔案打成tar包,並且排除.git目錄

有時,你需要將專案副本提供給無法訪問GitHub倉庫的外部成員。最簡單的方式就是使用tar或zip來打包所有的專案檔案。不過,如果不小心,隱藏的.git目錄就會包含到tar檔案中,這會導致檔案體積變大;同時,如果裡面的檔案與接收者自己的Git倉庫弄混了,那就更加令人頭疼了。輕鬆的做法則是自動從tar檔案中排除掉.git目錄:

$ tar cJf .tar.xz / --exclude-vcs

12. 查詢修改者

最後,如果出現混亂的情況,你一定想要找出是誰造成的。如果生產伺服器當機,那麼找到罪魁禍首是比較容易的事情:只需執行git blame。該命令會顯示出檔案中每一行的作者,提交hash則會找出該行的上一次修改,還能看到提交的時間戳:

$ git blame

當然,Git命令是非常多的,除了上面介紹的12個重要命令外,相信各位InfoQ讀者在日常工作過程中也有自己偏愛且好用的一些命令,不妨以評論的形式與其他讀者一同分享。

相關文章