Andy Jeffries 給 Git 中級使用者總結分享的 25 個小貼士。你不需要去做大量搜尋,或許這些小貼士對你就很有幫助的。
我從開始使用git到現在已經差不多18個月了,以為自己已經很懂git了。直到我看到github上 Scott Chacon在 LVS, a supplier/developer of betting/gaming software 上的教學,第一天就受益匪淺。
作為一個很享受git的人,我想要分享從各種社群學到的實用經驗,讓大家不需要花費過多的功夫就能找到答案。
基本技巧
1.安裝後的第一步
安裝git後,第一件事你需要設定你的名字和郵箱,因為每次提交都需要這些資訊。
1 2 |
$ git config --global user.name "Some One" $ git config --global user.email "someone@gmail.com" |
2.是基於指標的
git上的所有東西都是儲存在檔案裡的,當你建立一次提交時,它會建立一個包含你的提交資訊和相關資料(名字,郵箱,日期/時間、上一次提交等等)的檔案並連線一個樹檔案,而這個樹檔案包含了物件列表或者其他樹。這上面的物件或者blob檔案就是這次提交的實際內容(你可以認為這也是一個檔案,儘管並沒有儲存在物件裡而是儲存在樹中)。所有的檔案都以經過SHA-1計算後的檔名(譯者注:經過SHA-1計算後的數,即git中的版本號)儲存在上面。
從這裡可以看出,分支和標籤都是包含一個指向這次提交的sha-1數(版本號)簡單的檔案,這樣使用引用會變得更快和更靈活,建立一個新的分支是就像建立檔案一樣簡單,SHA – 1數(版本號)也會引用你這個分支的提交。當然,如果你使用GIT命令列工具(或者GUI)你將無法接觸這些。但真的很簡單。
你可能聽說過HEAD引用,這是一個指向你當前提交的內容的SHA-1 數(版本號)的指標。如果你正在解決合併衝突,使用HEAD不會對你的特定分支有任何改動只會指向你當前的分支。
所有分支的指標都儲存在 .git/refs/heads,HEAD指標儲存在.git/HEAD,標籤則儲存在 .git/refs/tags,有時間就去看看吧。
3. 兩個母體(Parent),當然!
當我們在日誌檔案中檢視合併提交資訊,你會看到兩個母體,第一個母體是正在進行的分支,第二個是你要合併的分支。
4.合併衝突
現在,我發現有合併衝突並解決了它,這是一件在我們編輯檔案時很正常的事。將 <<<<, ====, >>>> 這些標記移除後,並儲存你想要儲存的程式碼。有些時候在程式碼被直接替代之前,能看到衝突是件挺不錯的事。比如在兩個衝突的分支變動之前,可以用這樣的命令方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
$ git diff --merge diff --cc dummy.rb index 5175dde,0c65895..4a00477 --- a/dummy.rb +++ b/dummy.rb @@@ -1,5 -1,5 +1,5 @@@ class MyFoo def say - puts "Bonjour" - puts "Hello world" ++ puts "Annyong Haseyo" end end If the file is binary, diffing files isn’t so easy… What you’ll normally want to do is to try each version of the binary file and decide which one to use (or manually copy portions over in the binary file’s editor). To pull a copy of the file from a particular branch (say you’re merging master and feature132): |
如果是二進位制檔案(binary),區別這些檔案並不容易。通常你會檢視每個二進位制檔案的版本,再決定使用哪個(或者在二進位制檔案編輯器中手動複製),並將其推送至特定的分支。(比如你要合併master和feature132)
1 2 3 4 5 6 |
$ git checkout master flash/foo.fla # or... $ git checkout feature132 flash/foo.fla $ # Then... $ git add flash/foo.fla Another way is to cat the file from git – you can do this to another filename then copy the correct file over (when you’ve decided which it is) to the normal filename: |
另一個方法就是在git中cat檔案,你可以將其命名為另一個檔名,然後將你決定的那個檔案改為正確的檔名:
1 2 3 4 5 6 7 8 |
$ git show master:flash/foo.fla > master-foo.fla $ git show feature132:flash/foo.fla > feature132-foo.fla $ # Check out master-foo.fla and feature132-foo.fla $ # Let's say we decide that feature132's is correct $ rm flash/foo.fla $ mv feature132-foo.fla flash/foo.fla $ rm master-foo.fla $ git add flash/foo.fla |
更新:感謝carls在原博評論中提醒我,可以使用 “git checkout —ours flash/foo.fla” 和“git checkout —theirs flash/foo.fla” 在不用考慮你需要合併的分支來檢查指定版本,就我個人而言,我喜歡更明確的方法,但這也是一個選擇…
記住,解決完合併衝突後要新增檔案。(我之前就犯過這樣的錯誤)
服務,分支和標註
5. 遠端服務
Git有一個非常強大的特性,就是可以有多個遠端服務端(以及你執行的一個本地倉庫)。你不需要總是進行訪問,你可以有多個服務端並能從其中一個(合併工作)讀取再寫入另一個。新增一個遠端服務端很簡單:
1 2 3 |
$ git remote add john git@github.com:johnsomeone/someproject.git If you want to see information about your remote servers you can do: |
如果你想檢視遠端服務端的資訊你可以:
1 2 3 4 5 6 7 |
# shows URLs of each remote server $ git remote -v # gives more details about each $ git remote show name You can always see the differences between a local branch and a remote branch: |
你總是能看到本地分支和遠端分支不同的地方:
1 2 3 |
$ git diff master..john/master You can also see the changes on HEAD that aren’t on that remote branch: |
你同樣也能看到遠端分支上沒有的HEAD指標的改動:
1 2 |
$ git log remote/branch.. # Note: no final refspec after .. |
6. Tagging 標籤
在Git中有兩種型別的標註:輕量級標註和註釋型標註。
記住第二個是Git的指標基礎,兩者區別很簡單,輕量級標註是簡單命名提交的指標,你可以將其指向另一個提交。註釋型標註是一個有資訊和歷史並指向標註物件的名字指標,它有著自己的資訊,如果需要的話,可以進行GPG標記。
建立兩種型別的標籤很簡單(其中一個命令列有改動)
1 2 |
$ git tag to-be-tested $ git tag -a v1.1.0 # Prompts for a tag message |
7. Creating Branches 建立分支
在git中建立分支是件非常簡單的事情(非常快並只需要不到100byte的檔案大小)。建立新分支並切換到該分支,通常是下面這樣的:
1 2 |
$ git branch feature132 $ git checkout feature132 |
當然,如果你想切換到該分支,最直接的方式是使用這樣一條命令:
1 |
$ git checkout -b feature132 |
如果你想要重新命名本地分支,也很簡單:
1 2 |
$ git checkout -b twitter-experiment feature132 $ git branch -d feature132 |
更新:或者你(Brian Palmer在原博的評論中指出的)可以使用 -m來切換到“git branch”(就像Mike指出,如果你只需要一個特定的分支,就可以重新命名當前分支)
1 2 |
$ git branch -m twitter-experiment $ git branch -m feature132 twitter-experiment |
8.合併分支
以後你可能回想合併你的變動,有兩種方式可以做到這一點:
1 2 3 |
$ git checkout master $ git merge feature83 # Or... $ git rebase feature83 |
merge和rebase的區別是,merge會嘗試解決改動並建立的新的提交來融合他們。rebase則是將從你最後一次從另一個分支分離之後的改動併入,並直接沿用另一個分支的head指標。儘管如此,在你往遠端伺服器上推送分支之前,不要使用rebase。這會讓你混亂。
如果你不能確定哪個分支(哪些需要合併,哪些需要移除)。這裡有兩個git分支切換方式來幫助你:
1 2 3 4 5 |
# Shows branches that are all merged in to your current branch $ git branch --merged # Shows branches that are not merged in to your current branch $ git branch --no-merged |
9.遠端分支
如果你想將本地分支放置遠端服務端,你可以用這條命令進行推送:
1 2 |
$ git push origin twitter-experiment:refs/heads/twitter-experiment # Where origin is our server name and twitter-experiment is the branch |
如果你想要從服務端刪除分支:
1 |
$ git push origin :twitter-experiment |
如果你想要檢視遠端分支的狀態:
1 |
$ git remote show origin |
這將列出那些曾經存在而現在不存在的遠端分支,這將幫助你輕易地刪除你本地多餘的分支。
1 |
$ git remote prune |
最後,如果本地追蹤遠端分支,常用方式是:
1 2 |
$ git branch --track myfeature origin/myfeature $ git checkout myfeature |
儘管這樣,Git的新版本將啟動自動追蹤,如果你使用-b來checkout:
1 |
$ git checkout -b myfeature origin/myfeature |
Storing Content in Stashes, Index and File System 在stash儲存內容、索引和檔案系統
10. Stashing
在Git中你可以將當前的工作區的內容儲存到Git棧中並從最近的一次提交中讀取相關內容。以下是個簡單的例子:
1 2 3 |
$ git stash # Do something... $ git stash pop |
很多人推薦使用git stash apply來代替pop。這樣子恢復後儲存的stash內容並不會刪除,而‘pop’恢復的同時把儲存的stash內容也刪了 ,使用git stash apply 就可以移除任何棧中最新的內容。
1 2 |
<code data-language="javascript">$ git stash drop </code> |
git可以自動建立基於當前提交資訊的指令,如果你更喜歡使用通用的資訊(相當於不會對前一次提交做任何改動)
1 2 |
<code data-language="javascript">$ git stash save "My stash message" </code> |
如果你想使用某個stash(不一定是最後一個),你可以這樣將其列表顯示出來然後使用:
1 2 3 4 5 |
<code data-language="javascript">$ git stash list stash@{0}: On master: Changed to German stash@{1}: On master: Language is now Italian $ git stash apply stash@{1} </code> |
11.新增互動
在svn中,如果你檔案有了改動之後,然後會提交所有改動的檔案,在 Git中為了能更好的提交特定的檔案或者某個補丁,你需要在互動模式提交選擇提交的檔案的內容。
1 2 3 4 5 6 7 |
$ git add -i staged unstaged path *** Commands *** 1: status 2: update 3: revert 4: add untracked 5: patch 6: diff 7: quit 8: help What now&gt; |
這是基於選單的互動式提示符。您可以使用命令前的數字或進入高亮字母(如果你有高亮輸入)模式。常用形式是,輸入你想執行的操作前的數字。(你可以像1或1 – 4或2、4、7的格式來執行命令)。
如果你想進入補丁模式(在互動模式中輸入p或5),同樣也可以這樣操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
$ git add -p diff --git a/dummy.rb b/dummy.rb index 4a00477..f856fb0 100644 --- a/dummy.rb +++ b/dummy.rb @@ -1,5 +1,5 @@ class MyFoo def say - puts "Annyong Haseyo" + puts "Guten Tag" end end Stage this hunk [y,n,q,a,d,/,e,?]? |
如你所見,你將在選擇新增改動的那部分檔案的底部獲得一些選項。此外,使用“?”會說明這個選項。
12. 檔案系統中的儲存/檢索
有些專案(比如Git自己的專案)需要直接在Git的檔案系統中新增額外的並不想被檢查的檔案。
讓我們開始在Git中儲存隨機檔案
1 2 |
$ echo "Foo" | git hash-object -w --stdin 51fc03a9bb365fae74fd2bf66517b30bf48020cb |
比如資料庫中的物件,如果你不想讓一些物件被垃圾回收,最簡單的方式是給它加標籤:
1 |
$ git tag myfile 51fc03a9bb365fae74fd2bf66517b30bf48020cb |
在這裡我們設定myfile的標籤,當我們需要檢索該檔案時可以這樣:
1 |
$ git cat-file blob myfile |
這對開發者可能需要的但是並不想每次都去檢查的有用檔案(密碼,gpg鍵等等)很管用(特別是在生產過程中)。
Logging and What Changed? 記錄日誌和什麼改變了?
13. 檢視日誌
在不使用“git log”的情況下,你不能檢視你長期的最近提交內容,但是,仍然有一些更便於你使用的方法,比如,你可以這樣檢視單次提交變動的內容:
1 |
$ git log -p |
或者你只看檔案變動的摘要:
1 |
$ git log --stat |
這個很讚的別名,可以讓你在一行命令下簡化提交,並展示不錯的圖形化分支。
1 2 3 4 |
$ git config --global alias.lol "log --pretty=oneline --abbrev-commit --graph --decorate" $ git lol * 4d2409a (master) Oops, meant that to be in Korean * 169b845 Hello world |
14.在日誌中查詢
如果你想根據指定的作者查詢:
1 |
$ git log --author=Andy |
更新:感謝 Johannes的評論,解除了我的一些困惑,
或者你可以搜尋你提交資訊的內容:
1 |
$ git log --grep="Something in the message" |
這些強大的指令被稱為pickaxe指令,來檢查被移除或新增特定塊的內容(比如,當他們第一次出現或者被移除),新增任何一行內容都會告訴你(但是並不包括那行內容剛剛被改動)
1 |
$ git log -S "TODO: Check for admin status" |
如果你改動一個特定的檔案會怎麼樣?如:lib/foo.rb
1 |
$ git log lib/foo.rb |
如果你有feature/132 和ferature/145這兩個分支,並想檢視這些不在master上的分支內容。( ^ 符號是意味著非)
1 |
$ git log feature/132 feature/145 ^master |
你同樣可以使用ActiveSupport風格的日期來縮短時間範圍:
1 |
$ git log --since=2.months.ago --until=1.day.ago |
預設會使用OR來合併查詢,但你也可改用AND(如果你有不止一個條件)
1 |
$ git log --since=2.months.ago --until=1.day.ago --author=andy -S "something" --all-match |
15.選擇試圖/改動的之前的版本。
根據你知道的資訊,可以按照以下方式來找到之前的版本:
1 2 3 4 5 6 7 |
$ git show 12a86bc38 # By revision $ git show v1.0.1 # By tag $ git show feature132 # By branch name $ git show 12a86bc38^ # Parent of a commit $ git show 12a86bc38~2 # Grandparent of a commit $ git show feature132@{yesterday} # Time relative $ git show feature132@{2.hours.ago} # Time relative |
注意:不像前一部分所說,在最後的插入符號意味著提交的父類,在前面的插入符號意味著不在這個分支上。
16. 選擇一個方式
最簡單的方式:
1 2 |
$ git log origin/master..new # [old]..[new] - everything you haven't pushed yet |
你也可以省略[new],這樣將預設使用當前的HEAD指標。
Rewinding Time & Fixing Mistakes 回滾和修復錯誤
17.重置更改
如果你沒有提交你可以簡單的撤銷改動:
1 |
$ git reset HEAD lib/foo.rb |
通常我們使用”unstage“這樣的別名來代替:
1 2 |
$ git config --global alias.unstage "reset HEAD" $ git unstage lib/foo.rb |
如果你已經提交了,有兩種情況:如果是最後一次提交你僅僅需要amend:
1 |
$ git commit --amend |
這將不執行最後一次提交,恢復你原來的內容,提交資訊將預設為你下次提交的資訊。
如果你已經提交過不止一次了並且想完全回到之前那個記錄,你可以重置分支回到指定的時間。
1 2 |
$ git checkout feature132 $ git reset --hard HEAD~2 |
如果你想將分支回滾但想要SHA1數(版本號)不一樣(也許你可以將分支的HEAD指向另一個分支,或者之後的提交),你可以通過如下方式:
1 2 |
$ git checkout FOO $ git reset --hard SHA |
實際上還有個更快的方式(這樣並不會改變你的檔案複製內容,並回歸到第一次FOO的狀態並指向SHA)
1 |
$ git update-ref refs/heads/FOO SHA |
18. 提交至錯誤的分支
好吧,假定你提交到master上了,但是你想提交的是名為experimental的主題分支上,如果想移除這個改動,你可以在當前建立一個分支並將head指標回滾再檢查新的分支
1 2 3 |
$ git branch experimental # Creates a pointer to the current master state $ git reset --hard master~3 # Moves the master branch pointer back to 3 revisions ago $ git checkout experimental |
如果你在分支的分支的分支進行了改動將會很麻煩,那麼你需要做的就是在其他處進行分支rebase改動
1 2 |
$ git branch newtopic STARTPOINT $ git rebase oldtopic --onto newtopic |
19. rebase的互動
這是個很不錯的功能,我曾看過演示但一直以來並沒有真正搞懂,現在我知道了,非常簡單。假如你進行了三次提交,但是你想重新編輯它們(或者結合它們)。
1 |
$ git rebase -i master~3 |
然後你讓你的編輯器開啟一些指令,你需要做的就是修改指令來選擇/squash/編輯(或刪除)/提交和儲存/退出,編輯完使用git rebase —continue
來通過你的每一個指令。
如果你選擇編輯一個,它將離開你的提交狀態,所以你需要使用git commit -amend來編輯它。
注意:不要在rebase的時候提交——只能新增了之後再使用—continue, —skip 或—abort.
20. 清除
如果你在分支中提交了一些內容(也許是一些SVN上老的資原始檔)並想從歷史記錄中完全移除,可以這樣:
1 |
$ git filter-branch --tree-filter 'rm -f *.class' HEAD |
如果你已經將其推送至origin,並提交了一些垃圾內容,你同樣可以推送之前在本地系統這樣做:
1 |
$ git filter-branch --tree-filter 'rm -f *.class' origin/master..HEAD |
Miscellaneous Tips 各種各樣的技巧
21.你看過的前面的引用
如果你知道你之前看到的SHA-1數(版本號),並需要進行一些重置/回滾,可以使用reflog命令查詢最近檢視的sha – 1數(版本號):
1 2 |
$ git reflog $ git log -g # Same as above, but shows in 'log' format |
22. 分支命名
一個有趣的小技巧,不要忘記分支名不僅僅限於a-z和0-9,在名字中使用/和.用於命名偽名稱空間和版本控制,也是個不錯的主意,例如:
1 2 3 4 |
$ # Generate a changelog of Release 132 $ git shortlog release/132 ^release/131 $ # Tag this as v1.0.1 $ git tag v1.0.1 release/132 |
23. 找到Dunnit
找出誰在一個檔案中改變了一行程式碼,簡單的命令是:
1 |
$ git blame FILE |
有時候是上一個檔案發生了變動(如果你合併兩個檔案,或者你已經轉移到一個函式),這樣你就可以使用:
1 2 |
$ # shows which file names the content came from $ git blame -C FILE |
有時候需要通過點選來追蹤來回的變動,這裡有一個不錯的內建gui:
1 |
$ git gui blame FILE |
24. 資料庫維護
通常Git並不需要過多的維護,它幾乎可以自己搞定,儘管如此你也可以檢視資料庫使用的統計:
1 |
$ git count-objects -v |
如果數值過高你可以選擇將你的克隆垃圾回收。這不會影響你推送內容或其他人,但它可以讓你的命令執行的更快,並使用更少的空間:
1 |
$ git gc |
它也可以在執行時進行一致性檢驗:
1 |
$ git fsck --full |
你可以在後面新增-auto 引數(如果你在伺服器跑定時任務時),這在統計資料時是必須的。
當檢查的結果是“dangling”或“unreachable”這樣的是正常的,這通常是回滾和rebase的結果。 得到“missing” 或 “sha1 mismatch” 這樣的結果是不好的…你需要得到專業的幫助!
25. 恢復失去的分支
如果你意外的刪除一個分支,可以重新建立它:
1 |
$ git branch experimental SHA1_OF_HASH |
你可以使用git reflog檢視你最近訪問過的SHA1數(版本號)
另一個方式就是使用 git fsck —lost-found ,懸空物件(dangling commit )是就是失去HEAD指標的提交,(刪除的分支只是失去了HEAD指標成為懸空物件)
Done!完成!
這篇是我寫過最長的博文,希望大家能從此文中獲益,如果你有所收益或是有任何問題都可以在評論中告訴我!
1 |