Git 2.1 有哪些新特性?

李鼎發表於2014-09-08

git 2.0.0釋出2個半月後,作為小版本更新迎來了2.1.0,帶來了一大波令人興奮的新特性。

完整的釋出說明文件可以在這裡檢視,但如果你不怎麼接觸git社群,會覺得釋出說明文件有些太簡明瞭。這篇文章是我對這次釋出在Atlassian使用中令我們興奮的方面所做的評註。

更好的分頁程式預設設定

本文引文都是直接摘自發布說明文件,其中會加上自己的評註。

從很早期的Git開始,呼叫less分頁程式用的LESS環境變數設定的預設值是FRSXS選項(截斷長文字行而不是折行)從預設值中刪除了,因為對不同的人有不同的說法,這個選項或多或少是個人口味問題。相比,其它的選項作為預設設定是合理的(如,R選項非常合理,因為很多不同的輸出都是彩色的,而FX也是合理的,因為輸出常常短於一頁。)

如果你還沒有覆蓋過git分頁程式的預設值,這個變化意味著git命令的分頁輸出會在終端寬度的地方折行而不是截斷行。下面是git 2.1.0(折行,左圖)和git 2.0.3(截斷,右圖)顯示的例子:

git210leftvsgit200right-600x293

這個只會影響你日誌的輸出,如果你用的是一個窄的終端,或者在提交訊息中有長行。一般git推薦提交日誌資訊的寬度不要超過72字元,但如果覺得折行很煩,可以通過恢復原來的行為來禁用:

$ git config core.pager "less -S"

當然,分頁程式也會用於其它的輸出,比如git blame,這種情況下由於作者名的長度和程式碼風格,可以能會有很長的行。2.1.0的釋出說明文件也指出了可以只在blame的分頁程式中啟用-S選項:

$ git config pager.blame "less -S"

如果你對git還在使用的預設less選項很好奇,說明如下:

  • -F:讓less程式自動退出,如果輸出少於一頁。
  • -R:保證只有ANSI顏色轉義序列按原始形式輸出,這樣git控制檯顏色才能生效。
  • -X:避免螢幕在less啟動時被清空。這個也是在less輸出少於一頁時才有用。

更好的Bash補全

更新了Bash的補全指令碼(在contrib/目錄),能更好地處理定義了複雜命令序列的別名。

這個超酷!我是一個自定義git別名的大粉絲。能夠在複雜的別名上用上gitBash自動補全,讓這些別名在命令列上使用起來更強大和方便。舉個例子,我定義一個可以從日誌中grepJIRA風格的issue主鍵(如STASH-123)的別名:

issues = !sh -c 'git log --oneline $@ | egrep -o [A-Z]+-[0-9]+ | sort | uniq' -

所有的命令列引數傳給git log命令,這樣可以限制提交的範圍用於返回issue主鍵。比如,git issues -n 1只會顯示我的分支最近一次提交所關聯的issue主鍵。在2.1.0中,gitBash補全讓我可以像git log命令一樣去補全gitissue別名。

git 2.0.3下,鍵入git issues m會退化成預設的Bash補全行為,列出當前目錄下m開頭的檔案。在git 2.1.0下,正確地補全成master,就和git log命令下補全動作一樣。通過在別名加上空命令字首:,可以用於提示Bash補全行為。如果要補全的不是別名中的第一個命令,這個很有用。舉個例子:

issues = "!f() { echo 'Printing issue keys'; git log --oneline $@ | egrep -o [A-Z]+-[0-9]+ | sort | uniq; }; f"

這個別名不能正常補全,因為git不能把echo命令識別為補全目標。但如果加上字首成: git log;,補全就正確了:

issues = "!f() { : git log ; echo 'Printing issue keys'; git log --oneline $@ | egrep -o [A-Z]+-[0-9]+ | sort | uniq; }; f"

如果你喜歡編寫複雜的別名指令碼,這是個可用性的巨大改進!請記住,補全功能的指令碼在contrib/目錄下,不是git核心的一部分,所以如果你要使用這個功能,不要忘了更新Bash profile指向新版本的contrib/completion/git-completion.bash

git commit命令使用approxidate

git commit ‐‐date=<date>選項有了更多的時間戳格式選項,包括--date=now

當嚴格的parse_date()函式不能解析給的日期字串時,git提交的--date選項現在會回退到git酷炫的(也有些古怪的)approxidate(大概日期)解析器。approxidate可以處理顯而易見的值,像--date=now,也允許一些略複雜格式,像--date="midnight the 12th of october, anno domini 1979"或是--date=teatime。如果你想了解更多,Alex Peattie有一篇優秀的關於git酷炫日期處理的博文

更好的路徑顯示方式grep.fullname

git grep會讀取grep.fullname配置變數,強制‐‐full-name成為預設。
這可能會讓以指令碼方式使用的使用者出錯,這些使用者不期望這樣的新行為。

省得你去翻git-grepman,下面是--full-name選項的文件說明:

--full-name

當從子目錄執行時,命令輸出的路徑通常相對於當前目錄。這個選項強制輸出的路徑是相對專案的頂級目錄。

非常貼心!這個預設行為非常符合我的工作方式,我常常會執行git grep找出一個檔案的路徑,拷貝貼上到一個XML檔案中(這樣的做法可能出賣了我是個Java開發)。如果你也覺得有用,只要簡單執行:

$ git config --global grep.fullname true

在你的配置檔案開啟這個選項。

--global選項把配置應用到$HOME/.gitconfig檔案中,這樣配置值就會成為我係統上所有git倉庫的預設行為。如果有必要,你可以也只在倉庫級別覆蓋配置值。

更聰明的git replace

停一會兒!先看看git replace能做什麼?

簡單地說,git replace重寫git倉庫中的某個物件並且不保持對應樹或是提交的SHA不變。如果你是第一次聽到git replace並且知道git的資料模型,會覺得這樣的做法聽起來很逆天!我就是這麼覺得。我有另一篇正在寫的博文討論什麼時候和為什麼要使用這樣的功能。如果現在你想了解更多,看這篇文章比看man手冊好得多,手冊中只有很少且有些牽強的用例說明。

git replace會讀取--edit選項,可以編輯一個已有的物件再做替換。

--edit選項會dump一個物件的內容到一個臨時檔案,啟動你喜歡的編輯器,這樣就可以方便地拷貝和替換這個物件。要替換master分支的最近那次提交,可以簡單執行命令:

$ git replace --edit master

或者編輯最近那次提交的blob,假設是檔案jira-components/pom.xml,可以執行命令:

$ git replace --edit master:jira-components/pom.xml

應該這麼做?基本上不會 :) 大部分情況應該用git rebase重寫物件,這樣會正確的重寫提交的SHA,保證歷史是健全的(sane history)。

git replace會讀取--graft選項,可以編輯父提交。

--graft選項是替換一個提交有相同的內容但用不同的父提交的快捷操作。這可以方便地完成一個稍微正常一點的git replace的用例,縮短git歷史。要替換master分支的最近那次提交的父,可以簡單執行命令:

$ git replace master --graft [new parent]..

或者要砍掉某個點之後的歷史,可以忽略所有父提交讓這個提交成為孤兒提交:

$ git replace master --graft

再次說明,如果沒有好的理由基本上不應該這麼做。通常重寫歷史的首選方法是用明智的git rebase

更合理的tag排序通過tag.sort

git tag開始注意tag.sort的配置問題了,這個配置在沒有指定--sort選項時做為預設排序。

如果你在tag名中使用版本號(我想99.9%你就是這麼做的),這真是好訊息。一旦你釋出的版本號中有一段多於一個數字(比如 v10v1.10),git預設的字典排序就不好用了。舉個例子,看看Atlassian Stashgit倉庫的預設tag排序:

src/stash $ git tag -l *.*.0
..
stash-parent-2.0.0
stash-parent-2.1.0
stash-parent-2.10.0
stash-parent-2.11.0
stash-parent-2.12.0
stash-parent-2.2.0
stash-parent-2.3.0
stash-parent-2.4.0
stash-parent-2.5.0
stash-parent-2.6.0
stash-parent-2.7.0
stash-parent-2.8.0
stash-parent-2.9.0
stash-parent-3.0.0
..

有問題啊!2.10.02.3.0之後發的,所以預設的tag排序不對的。從git 2.0.0開始,可以用--sort選項可以正確按數值做版本排序:

src/stash $ git tag --sort="version:refname" -l *.*.0
..
stash-parent-2.0.0
stash-parent-2.1.0
stash-parent-2.2.0
stash-parent-2.3.0
stash-parent-2.4.0
stash-parent-2.5.0
stash-parent-2.6.0
stash-parent-2.7.0
stash-parent-2.8.0
stash-parent-2.9.0
stash-parent-2.10.0
stash-parent-2.11.0
stash-parent-2.12.0
stash-parent-3.0.0
..

這好多了。在git 2.1.0中,可以設定這種排序成預設方式,執行命令:

$ git config --global tag.sort version:refname

順便說一下,上面的示例git tag命令中使用了方便的-l選項,限制了只顯示符合指定模式的tag名。-l *.*.0用於只顯示大版本(major)和小版本(minor)的Stash的釋出。

更簡單地驗證有簽名的提交

新加了git verify-commit命令用於檢查有簽名提交的GPG簽名,使用方式和git verify-tag檢查簽名的tag類似。

如果你要用提交簽名來認證提交的作者,verify-commit命令大大簡化了驗證簽名的操作。不再需要自己寫個指令碼去分析git log --show-signature,只要簡單把要驗證簽名的那些提交傳給git verify-commit。有可能你沒有用簽名提交(在Atlassian我們沒有用),因為這麼做需要管理Key和開發額外麻煩的操作。對於多數專案,一般情況下簽名的Tag是在方便性和安全性之間一個更好的平衡。如果你想知道為什麼有專案要使用簽名提交,Mike Gerwitz講了一個在假設場景下git的恐怖故事,這個場景下簽名提交是非常有用的。所以如果你在特別敏感的企業工作,可能要把它加入到工作流中。

更多的效能加速

git 2.1.0也帶來了一些不錯的效能提升。

引入了使用2個檔案(一個基礎檔案和一個相應的增量檔案)來表示索引的試驗性格式;當要重寫只有小部分工作樹變化的大索引時,這樣可能減少I/O消耗。

複述一下意思就是:如果你的提交有大量檔案修改時,執行git add可能會更快。我本地的任何增量操作,git add已經像閃電一樣快了,所以我看不出和測試的git版本之間有什麼大的效能變化。有意思的是,大量檔案的第一次add好像快了一點。做了個快糙猛的效能測試,我試著暫存所有在JIRA程式碼庫從JIRA 5JIRA 6的修改。

$ git checkout jira-v6.0
$ git reset jira-v5.0
$ time git add --all

git 2.0.3平均使用2.44秒。而git 2.1.0平均使用2.18秒 —— 節省超過10%的時間!注意,由於實驗條件這是個很不準確的測試,新增14500+個檔案到索引中節省了1/4秒,所以在日常git使用中不會看到大的變化。關於新索引格式可以在索引格式文件中瞭解更多。

預設開啟了core.preloadindex配置項,以充分利用現代平臺的多核。

不錯!之前我沒有開啟這個功能,但升級到2.1.0後效能變化很顯著。再做一個快糙猛的測試,執行git status顯示之前我用的從JIRA 5JIRA 6的暫存修改。顯示暫存的14500+個檔案,git 2.0.3平均使用4.94秒。而git 2.1.0平均使用3.99秒 —— 節省了多達~19%的時間。如果你使用了自定義的shell提示符,在每次提示符顯示時檢查工作拷貝中是否有未提交的修改,這個效能就非常有用!當索引很大時,我明顯覺得bash反應快了一些。

通過重組用於跟蹤已有提交的資料結構,大大優化了git blame

在分析出誰提交某行(搞壞專案的)程式碼,git blame更快了。我很高興看到這個改進,就是說git-guilt(我寫的一個小工具,用於研究如何blame提交的修改)可以有相當的效能提高,因為它重度依賴於blame到函式的輸出。

又來一個快糙猛的測試,看一下算出git原始碼倉庫從2.0.02.1.0的『罪行證據』(guilt transfer)要花多長時間。這個操作git-guilt要在git 2.0.02.1.0修改過的不同大小的檔案上呼叫886次git blame命令。

$ git guilt v2.0.0 v2.1.0

git 2.0.3平均使用72.1秒。而git 2.1.0平均使用66.7秒,提升了7.5%!如果有興趣,你可以看看git-guilt transfer的實際程式碼(Karsten Blees的66 LOC行的實現,以微弱優勢勝出Junio C Hamano)。

上面的效能測試都有些隨意,但我們正在進行Bitbucketgit 2.1.0升級。線上會監控升級前後的功能,可以確定新版本對這些操作的效能影響,特別是blamediff操作。過幾周我會發出結果讓大家知道。

等等,還有還有!

git 2.1.0中還有其它很好的內容我沒有在一篇文章中涉及到,所以有興趣可以看看完整的釋出說明文件。由衷地感謝git團隊又提供了一個高質量和豐富新功能的版本。如果你有興趣瞭解更多有關於git的實用小建議和花邊新聞,歡迎在Twitter上關注我(@kannonboy)和Atlassian開發工具(@atldevtools)。

譯註
譯文的原始碼在GitHubGit 2.1新特性自己理解粗淺,翻譯中不足和不對之處,歡迎建議(提交Issue)和指正(Fork後提交程式碼)!

打賞支援我翻譯更多好文章,謝謝!

打賞譯者

打賞支援我翻譯更多好文章,謝謝!

任選一種支付方式

Git 2.1 有哪些新特性? Git 2.1 有哪些新特性?

相關文章