眾所周知,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 f fix 『了:
$ 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 files4. 複製一個特定的遠端分支 如果想要從遠端倉庫中克隆一個特定的分支該怎麼做呢?通常你會使用git clone,不過這麼做會將所有其他分支都一併克隆下來。一個方便的方式是使用git remote add:
$ git init $ git remote add -t-f origin$ git checkout5. 將cherry-pick遠端提交合併到自己的分支中 更甚者,如果只想將遠端倉庫的一個特定提交合併到自己的分支中該怎麼做呢?可以使用git cherry-pick 來選擇給定SHA值的提交,然後將其合併到當前分支中:
$ git cherry-pick6. 應用來自於不相關的本地倉庫的補丁 如果需要將另一個不相關的本地倉庫的提交補丁應用到目前倉庫該怎麼做呢?答案就是下面這條指令:
$ git --git-dir=/.git format-patch -k -1 --stdout| git am -3 -k7. 忽略追蹤文件中的變更
如果你和你的同事操纵的是相同分支,那么很有可能需要频繁执行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-filtermaster # 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读者在日常工作过程中也有自己偏爱且好用的一些命令,不妨以评论的形式与其他读者一同分享。