Linux中國

2016 Git 新視界

2016 年 Git 發生了 驚天動地 地變化,發布了五大新特性¹ (從 v2.7v2.11 )和十六個補丁²。189 位作者³貢獻了 3676 個提交master 分支,比 2015 年多了 15%!總計有 1545 個文件被修改,其中增加了 276799 行並移除了 100973 行

但是,通過統計提交的數量和代碼行數來衡量生產力是一種十分愚蠢的方法。除了深度研究過的開發者可以做到憑直覺來判斷代碼質量的地步,我們普通人來作仲裁難免會因我們常人的判斷有失偏頗。

謹記這一條於心,我決定整理這一年裡六個我最喜愛的 Git 特性涵蓋的改進,來做一次分類回顧。這篇文章作為一篇中篇推文有點太過長了,所以我不介意你們直接跳到你們特別感興趣的特性去。

  • 完成 git worktree 命令
  • 更多方便的 git rebase 選項
  • git lfs 夢幻的性能加速
  • git diff 實驗性的演算法和更好的默認結果
  • git submodules 差強人意
  • git stash 的90 個增強

在我們開始之前,請注意在大多數操作系統上都自帶有 Git 的舊版本,所以你需要檢查你是否在使用最新並且最棒的版本。如果在終端運行 git --version 返回的結果小於 Git v2.11.0,請立刻跳轉到 Atlassian 的快速指南 更新或安裝 Git 並根據你的平台做出選擇。

[所需的「引用」]

在我們進入高質量內容之前還需要做一個短暫的停頓:我覺得我需要為你展示我是如何從公開文檔生成統計信息(以及開篇的封面圖片)的。你也可以使用下面的命令來對你自己的倉庫做一個快速的 年度回顧

¹ 2016 年匹配 vX.Y.0 格式的里程碑

$ git for-each-ref --sort=-taggerdate --format 
'%(refname) %(taggerdate)' refs/tags | grep "vd.d*.0 .* 2016"

² 2016 年匹配 vX.Y.Z 格式的里程碑

$ git for-each-ref --sort=-taggerdate --format '%(refname) %(taggerdate)' refs/tags | grep "vd.d*.[^0] .* 2016"

³ 2016 年做過提交的貢獻者數量

$ git shortlog -s -n --since=2016-01-01 --until=2017-01-01

⁴ 2016 年的提交數量

$ git log --oneline --since=2016-01-01 --until=2017-01-01 | wc -l

⁵ 以及 2015 年的提交數量

$ git log --oneline --since=2015-01-01 --until=2016-01-01 | wc -l

⁶ 2016 年添加、刪除行數

$ git diff --shortstat `git rev-list -1 --until=2016-01-01 master` 
 `git rev-list -1 --until=2017-01-01 master`

以上的命令是在 Git 的 master 分支運行的,所以不會顯示其他出色的分支上沒有合併的工作。如果你使用這些命令,請記住提交的數量和代碼行數不是應該值得信賴的度量方式。請不要使用它們來衡量你的團隊成員的貢獻。

現在,讓我們開始說好的回顧……

完成 Git 工作樹 worktree

git worktree 命令首次出現於 Git v2.5 ,但是在 2016 年有了一些顯著的增強。兩個有價值的新特性在 v2.7 被引入:list 子命令,和為二分搜索增加了命令空間的 refs。而 lock/unlock 子命令則是在 v2.10 被引入。

什麼是工作樹呢?

git worktree 命令允許你同步地檢出和操作處於不同路徑下的同一倉庫的多個分支。例如,假如你需要做一次快速的修復工作但又不想擾亂你當前的工作區,你可以使用以下命令在一個新路徑下檢出一個新分支:

$ git worktree add -b hotfix/BB-1234 ../hotfix/BB-1234
Preparing ../hotfix/BB-1234 (identifier BB-1234)
HEAD is now at 886e0ba Merged in bedwards/BB-13430-api-merge-pr (pull request #7822)

工作樹不僅僅是為分支工作。你可以檢出多個 里程碑 tag 作為不同的工作樹來並行構建或測試它們。例如,我從 Git v2.6 和 v2.7 的里程碑中創建工作樹來檢驗不同版本 Git 的行為特徵。

$ git worktree add ../git-v2.6.0 v2.6.0
Preparing ../git-v2.6.0 (identifier git-v2.6.0)
HEAD is now at be08dee Git 2.6

$ git worktree add ../git-v2.7.0 v2.7.0
Preparing ../git-v2.7.0 (identifier git-v2.7.0)
HEAD is now at 7548842 Git 2.7

$ git worktree list
/Users/kannonboy/src/git         7548842 [master]
/Users/kannonboy/src/git-v2.6.0  be08dee (detached HEAD)
/Users/kannonboy/src/git-v2.7.0  7548842 (detached HEAD)

$ cd ../git-v2.7.0 && make

你也使用同樣的技術來並行構造和運行你自己應用程序的不同版本。

列出工作樹

git worktree list 子命令(於 Git v2.7 引入)顯示所有與當前倉庫有關的工作樹。

$ git worktree list
/Users/kannonboy/src/bitbucket/bitbucket       37732bd [master]
/Users/kannonboy/src/bitbucket/staging         d5924bc [staging]
/Users/kannonboy/src/bitbucket/hotfix-1234     37732bd [hotfix/1234]

二分查找工作樹

git bisect 是一個簡潔的 Git 命令,可以讓我們對提交記錄執行一次二分搜索。通常用來找到哪一次提交引入了一個指定的退化。例如,如果在我的 master 分支最後的提交上有一個測試沒有通過,我可以使用 git bisect 來貫穿倉庫的歷史來找尋第一次造成這個錯誤的提交。

$ git bisect start

# 找到已知通過測試的最後提交
# (例如最新的發布里程碑)
$ git bisect good v2.0.0

# 找到已知的出問題的提交
# (例如在 `master` 上的提示)
$ git bisect bad master

# 告訴 git bisect 要運行的腳本/命令;
# git bisect 會在 「good」 和 「bad」範圍內
# 找到導致腳本以非 0 狀態退出的最舊的提交
$ git bisect run npm test

在後台,bisect 使用 refs 來跟蹤 「good」 與 「bad」 的提交來作為二分搜索範圍的上下界限。不幸的是,對工作樹的粉絲來說,這些 refs 都存儲在尋常的 .git/refs/bisect 命名空間,意味著 git bisect 操作如果運行在不同的工作樹下可能會互相干擾。

到了 v2.7 版本,bisect 的 refs 移到了 .git/worktrees/$worktree_name/refs/bisect, 所以你可以並行運行 bisect 操作於多個工作樹中。

鎖定工作樹

當你完成了一顆工作樹的工作,你可以直接刪除它,然後通過運行 git worktree prune 等它被當做垃圾自動回收。但是,如果你在網路共享或者可移除媒介上存儲了一顆工作樹,如果工作樹目錄在刪除期間不可訪問,工作樹會被完全清除——不管你喜不喜歡!Git v2.10 引入了 git worktree lockunlock 子命令來防止這種情況發生。

# 在我的 USB 盤上鎖定 git-v2.7 工作樹
$ git worktree lock /Volumes/Flash_Gordon/git-v2.7 --reason 
"In case I remove my removable media"
# 當我完成時,解鎖(並刪除)該工作樹
$ git worktree unlock /Volumes/Flash_Gordon/git-v2.7
$ rm -rf /Volumes/Flash_Gordon/git-v2.7
$ git worktree prune

--reason 標籤允許為未來的你留一個記號,描述為什麼當初工作樹被鎖定。git worktree unlocklock 都要求你指定工作樹的路徑。或者,你可以 cd 到工作樹目錄然後運行 git worktree lock . 來達到同樣的效果。

更多 Git 變基 rebase 選項

2016 年三月,Git v2.8 增加了在拉取過程中交互進行變基的命令 git pull --rebase=interactive 。對應地,六月份 Git v2.9 發布了通過 git rebase -x 命令對執行變基操作而不需要進入交互模式的支持。

Re-啥?

在我們繼續深入前,我假設讀者中有些並不是很熟悉或者沒有完全習慣變基命令或者互動式變基。從概念上說,它很簡單,但是與很多 Git 的強大特性一樣,變基散發著聽起來很複雜的專業術語的氣息。所以,在我們深入前,先來快速的複習一下什麼是 變基 rebase

變基操作意味著將一個或多個提交在一個指定分支上重寫。git rebase 命令是被深度重載了,但是 rebase 這個名字的來源事實上還是它經常被用來改變一個分支的基準提交(你基於此提交創建了這個分支)。

從概念上說,rebase 通過將你的分支上的提交臨時存儲為一系列補丁包,接著將這些補丁包按順序依次打在目標提交之上。

對 master 分支的一個功能分支執行變基操作 (git reabse master)是一種通過將 master 分支上最新的改變合併到功能分支的「保鮮法」。對於長期存在的功能分支,規律的變基操作能夠最大程度的減少開發過程中出現衝突的可能性和嚴重性。

有些團隊會選擇在合併他們的改動到 master 前立即執行變基操作以實現一次快速合併 (git merge --ff <feature>)。對 master 分支快速合併你的提交是通過簡單的將 master ref 指向你的重寫分支的頂點而不需要創建一個合併提交。

變基是如此方便和功能強大以致於它已經被嵌入其他常見的 Git 命令中,例如拉取操作 git pull 。如果你在本地 master 分支有未推送的提交,運行 git pull 命令從 origin 拉取你隊友的改動會造成不必要的合併提交。

這有點混亂,而且在繁忙的團隊,你會獲得成堆的不必要的合併提交。git pull --rebase 將你本地的提交在你隊友的提交上執行變基而不產生一個合併提交。

這很整潔吧!甚至更酷,Git v2.8 引入了一個新特性,允許你在拉取時 交互地 變基。

互動式變基

互動式變基是變基操作的一種更強大的形態。和標準變基操作相似,它可以重寫提交,但它也可以向你提供一個機會讓你能夠互動式地修改這些將被重新運用在新基準上的提交。

當你運行 git rebase --interactive (或 git pull --rebase=interactive)時,你會在你的文本編輯器中得到一個可供選擇的提交列表視圖。

$ git rebase master --interactive

pick 2fde787 ACE-1294: replaced miniamalCommit with string in test
pick ed93626 ACE-1294: removed pull request service from test
pick b02eb9a ACE-1294: moved fromHash, toHash and diffType to batch
pick e68f710 ACE-1294: added testing data to batch email file

# Rebase f32fa9d..0ddde5f onto f32fa9d (4 commands)
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit&apos;s log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to 
# bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.

注意到每一條提交旁都有一個 pick。這是對 rebase 而言,「照原樣留下這個提交」。如果你現在就退出文本編輯器,它會執行一次如上文所述的普通變基操作。但是,如果你將 pick 改為 edit 或者其他 rebase 命令中的一個,變基操作會允許你在它被重新運用前改變它。有效的變基命令有如下幾種:

  • reword:編輯提交信息。
  • edit:編輯提交了的文件。
  • squash:將提交與之前的提交(同在文件中)合併,並將提交信息拼接。
  • fixup:將本提交與上一條提交合併,並且逐字使用上一條提交的提交信息(這很方便,如果你為一個很小的改動創建了第二個提交,而它本身就應該屬於上一條提交,例如,你忘記暫存了一個文件)。
  • exec: 運行一條任意的 shell 命令(我們將會在下一節看到本例一次簡潔的使用場景)。
  • drop: 這將丟棄這條提交。

你也可以在文件內重新整理提交,這樣會改變它們被重新應用的順序。當你對不同的主題創建了交錯的提交時這會很順手,你可以使用 squash 或者 fixup 來將其合併成符合邏輯的原子提交。

當你設置完命令並且保存這個文件後,Git 將遞歸每一條提交,在每個 rewordedit 命令處為你暫停來執行你設計好的改變,並且自動運行 squashfixupexecdrop 命令。

非交互性式執行

當你執行變基操作時,本質上你是在通過將你每一條新提交應用於指定基址的頭部來重寫歷史。git pull --rebase 可能會有一點危險,因為根據上游分支改動的事實,你的新建歷史可能會由於特定的提交遭遇測試失敗甚至編譯問題。如果這些改動引起了合併衝突,變基過程將會暫停並且允許你來解決它們。但是,整潔的合併改動仍然有可能打斷編譯或測試過程,留下破敗的提交弄亂你的提交歷史。

但是,你可以指導 Git 為每一個重寫的提交來運行你的項目測試套件。在 Git v2.9 之前,你可以通過綁定 git rebase --interactiveexec 命令來實現。例如這樣:

$ git rebase master −−interactive −−exec=」npm test」

……這會生成一個互動式變基計劃,在重寫每條提交後執行 npm test ,保證你的測試仍然會通過:

pick 2fde787 ACE-1294: replaced miniamalCommit with string in test
exec npm test
pick ed93626 ACE-1294: removed pull request service from test
exec npm test
pick b02eb9a ACE-1294: moved fromHash, toHash and diffType to batch
exec npm test
pick e68f710 ACE-1294: added testing data to batch email file
exec npm test

# Rebase f32fa9d..0ddde5f onto f32fa9d (4 command(s))

如果出現了測試失敗的情況,變基會暫停並讓你修復這些測試(並且將你的修改應用於相應提交):

291 passing
1 failing

1) Host request "after all" hook:
Uncaught Error: connect ECONNRESET 127.0.0.1:3001
…
npm ERR! Test failed.
Execution failed: npm test
You can fix the problem, and then run
        git rebase −−continue

這很方便,但是使用互動式變基有一點臃腫。到了 Git v2.9,你可以這樣來實現非互動式變基:

$ git rebase master -x "npm test"

可以簡單替換 npm testmakerakemvn clean install,或者任何你用來構建或測試你的項目的命令。

小小警告

就像電影里一樣,重寫歷史可是一個危險的行當。任何提交被重寫為變基操作的一部分都將改變它的 SHA-1 ID,這意味著 Git 會把它當作一個全新的提交對待。如果重寫的歷史和原來的歷史混雜,你將獲得重複的提交,而這可能在你的團隊中引起不少的疑惑。

為了避免這個問題,你僅僅需要遵照一條簡單的規則:

永遠不要變基一條你已經推送的提交!

堅持這一點你會沒事的。

Git LFS 的性能提升

Git 是一個分散式版本控制系統,意味著整個倉庫的歷史會在克隆階段被傳送到客戶端。對包含大文件的項目——尤其是大文件經常被修改——初始克隆會非常耗時,因為每一個版本的每一個文件都必須下載到客戶端。Git LFS 大文件存儲 Large File Storage )是一個 Git 拓展包,由 Atlassian、GitHub 和其他一些開源貢獻者開發,通過需要時才下載大文件的相對版本來減少倉庫中大文件的影響。更明確地說,大文件是在檢出過程中按需下載的而不是在克隆或抓取過程中。

在 Git 2016 年的五大發布中,Git LFS 自身就有四個功能版本的發布:v1.2 到 v1.5。你可以僅對 Git LFS 這一項來寫一系列回顧文章,但是就這篇文章而言,我將專註於 2016 年解決的一項最重要的主題:速度。一系列針對 Git 和 Git LFS 的改進極大程度地優化了將文件傳入/傳出伺服器的性能。

長期過濾進程

當你 git add 一個文件時,Git 的凈化過濾系統會被用來在文件被寫入 Git 目標存儲之前轉化文件的內容。Git LFS 通過使用 凈化過濾器 clean filter 將大文件內容存儲到 LFS 緩存中以縮減倉庫的大小,並且增加一個小「指針」文件到 Git 目標存儲中作為替代。

污化過濾器 smudge filter 是凈化過濾器的對立面——正如其名。在 git checkout 過程中從一個 Git 目標倉庫讀取文件內容時,污化過濾系統有機會在文件被寫入用戶的工作區前將其改寫。Git LFS 污化過濾器通過將指針文件替代為對應的大文件將其轉化,可以是從 LFS 緩存中獲得或者通過讀取存儲在 Bitbucket 的 Git LFS。

傳統上,污化和凈化過濾進程在每個文件被增加和檢出時只能被喚起一次。所以,一個項目如果有 1000 個文件在被 Git LFS 追蹤 ,做一次全新的檢出需要喚起 git-lfs-smudge 命令 1000 次。儘管單次操作相對很迅速,但是經常執行 1000 次獨立的污化進程總耗費驚人。、

針對 Git v2.11(和 Git LFS v1.5),污化和凈化過濾器可以被定義為長期進程,為第一個需要過濾的文件調用一次,然後為之後的文件持續提供污化或凈化過濾直到父 Git 操作結束。Lars Schneider,Git 的長期過濾系統的貢獻者,簡潔地總結了對 Git LFS 性能改變帶來的影響。

使用 12k 個文件的測試倉庫的過濾進程在 macOS 上快了 80 倍,在 Windows 上 快了 58 倍。在 Windows 上,這意味著測試運行了 57 秒而不是 55 分鐘。

這真是一個讓人印象深刻的性能增強!

LFS 專有克隆

長期運行的污化和凈化過濾器在對向本地緩存讀寫的加速做了很多貢獻,但是對大目標傳入/傳出 Git LFS 伺服器的速度提升貢獻很少。 每次 Git LFS 污化過濾器在本地 LFS 緩存中無法找到一個文件時,它不得不使用兩次 HTTP 請求來獲得該文件:一個用來定位文件,另外一個用來下載它。在一次 git clone 過程中,你的本地 LFS 緩存是空的,所以 Git LFS 會天真地為你的倉庫中每個 LFS 所追蹤的文件創建兩個 HTTP 請求:

幸運的是,Git LFS v1.2 提供了專門的 git lfs clone 命令。不再是一次下載一個文件; git lfs clone 禁止 Git LFS 污化過濾器,等待檢出結束,然後從 Git LFS 存儲中按批下載任何需要的文件。這允許了並行下載並且將需要的 HTTP 請求數量減半。

自定義 傳輸路由器 Transfer Adapter

正如之前討論過的,Git LFS 在 v1.5 中提供對長期過濾進程的支持。不過,對另外一種類型的可插入進程的支持早在今年年初就發布了。 Git LFS 1.3 包含了對 可插拔傳輸路由器 pluggable transfer adapter 的支持,因此不同的 Git LFS 託管服務可以定義屬於它們自己的協議來向或從 LFS 存儲中傳輸文件。

直到 2016 年底,Bitbucket 是唯一一個執行專屬 Git LFS 傳輸協議 Bitbucket LFS Media Adapter 的託管服務商。這是為了從 Bitbucket 的一個被稱為 chunking 的 LFS 存儲 API 獨特特性中獲益。Chunking 意味著在上傳或下載過程中,大文件被分解成 4MB 的 文件塊 chunk

分塊給予了 Bitbucket 支持的 Git LFS 三大優勢:

  1. 並行下載與上傳。默認地,Git LFS 最多並行傳輸三個文件。但是,如果只有一個文件被單獨傳輸(這也是 Git LFS 污化過濾器的默認行為),它會在一個單獨的流中被傳輸。Bitbucket 的分塊允許同一文件的多個文件塊同時被上傳或下載,經常能夠神奇地提升傳輸速度。
  2. 可續傳的文件塊傳輸。文件塊都在本地緩存,所以如果你的下載或上傳被打斷,Bitbucket 的自定義 LFS 流媒體路由器會在下一次你推送或拉取時僅為丟失的文件塊恢復傳輸。
  3. 免重複。Git LFS,正如 Git 本身,是一種可定位的內容;每一個 LFS 文件都由它的內容生成的 SHA-256 哈希值認證。所以,哪怕你稍微修改了一位數據,整個文件的 SHA-256 就會修改而你不得不重新上傳整個文件。分塊允許你僅僅重新上傳文件真正被修改的部分。舉個例子,想想一下 Git LFS 在追蹤一個 41M 的 精靈表格 spritesheet 。如果我們增加在此精靈表格上增加 2MB 的新的部分並且提交它,傳統上我們需要推送整個新的 43M 文件到伺服器端。但是,使用 Bitbucket 的自定義傳輸路由,我們僅僅需要推送大約 7MB:先是 4MB 文件塊(因為文件的信息頭會改變)和我們剛剛添加的包含新的部分的 3MB 文件塊!其餘未改變的文件塊在上傳過程中被自動跳過,節省了巨大的帶寬和時間消耗。

可自定義的傳輸路由器是 Git LFS 的一個偉大的特性,它們使得不同服務商在不重載核心項目的前提下體驗適合其伺服器的優化後的傳輸協議。

更佳的 git diff 演算法與默認值

不像其他的版本控制系統,Git 不會明確地存儲文件被重命名了的事實。例如,如果我編輯了一個簡單的 Node.js 應用並且將 index.js 重命名為 app.js,然後運行 git diff,我會得到一個看起來像一個文件被刪除另一個文件被新建的結果。

我猜測移動或重命名一個文件從技術上來講是一次刪除後跟著一次新建,但這不是對人類最友好的描述方式。其實,你可以使用 -M 標誌來指示 Git 在計算差異時同時嘗試檢測是否是文件重命名。對之前的例子,git diff -M 給我們如下結果:

第二行顯示的 similarity index 告訴我們文件內容經過比較後的相似程度。默認地,-M 會處理任意兩個超過 50% 相似度的文件。這意味著,你需要編輯少於 50% 的行數來確保它們可以被識別成一個重命名後的文件。你可以通過加上一個百分比來選擇你自己的 similarity index,如,-M80%

到 Git v2.9 版本,無論你是否使用了 -M 標誌, git diffgit log 命令都會默認檢測重命名。如果不喜歡這種行為(或者,更現實的情況,你在通過一個腳本來解析 diff 輸出),那麼你可以通過顯式的傳遞 --no-renames 標誌來禁用這種行為。

詳細的提交

你經歷過調用 git commit 然後盯著空白的 shell 試圖想起你剛剛做過的所有改動嗎?verbose 標誌就為此而來!

不像這樣:

Ah crap, which dependency did I just rev?

# Please enter the commit message for your changes. Lines starting
# with 『#』 will be ignored, and an empty message aborts the commit.
# On branch master
# Your branch is up-to-date with 『origin/master』.
#
# Changes to be committed:
# new file: package.json
#

……你可以調用 git commit --verbose 來查看你改動造成的行內差異。不用擔心,這不會包含在你的提交信息中:

--verbose 標誌並不是新出現的,但是直到 Git v2.9 你才可以通過 git config --global commit.verbose true 永久的啟用它。

實驗性的 Diff 改進

當一個被修改部分前後幾行相同時,git diff 可能產生一些稍微令人迷惑的輸出。如果在一個文件中有兩個或者更多相似結構的函數時這可能發生。來看一個有些刻意人為的例子,想像我們有一個 JS 文件包含一個單獨的函數:

/* @return {string} "Bitbucket" */
function productName() {
  return "Bitbucket";
}

現在想像一下我們剛提交的改動包含一個我們專門做的 另一個可以做相似事情的函數:

/* @return {string} "Bitbucket" */
function productId() {
  return "Bitbucket";
}

/* @return {string} "Bitbucket" */
function productName() {
  return "Bitbucket";
}

我們希望 git diff 顯示開頭五行被新增,但是實際上它不恰當地將最初提交的第一行也包含進來。

錯誤的注釋被包含在了 diff 中!這雖不是世界末日,但每次發生這種事情總免不了花費幾秒鐘的意識去想 啊? 在十二月,Git v2.11 介紹了一個新的實驗性的 diff 選項,--indent-heuristic,嘗試生成從美學角度來看更賞心悅目的 diff。

在後台,--indent-heuristic 在每一次改動造成的所有可能的 diff 中循環,並為它們分別打上一個 「不良」 分數。這是基於啟發式的,如差異文件塊是否以不同等級的縮進開始和結束(從美學角度講「不良」),以及差異文件塊前後是否有空白行(從美學角度講令人愉悅)。最後,有著最低不良分數的塊就是最終輸出。

這個特性還是實驗性的,但是你可以通過應用 --indent-heuristic 選項到任何 git diff 命令來專門測試它。如果,如果你喜歡嘗鮮,你可以這樣將其在你的整個系統內啟用:

$ git config --global diff.indentHeuristic true

子模塊 Submodule 差強人意

子模塊允許你從 Git 倉庫內部引用和包含其他 Git 倉庫。這通常被用在當一些項目管理的源依賴也在被 Git 跟蹤時,或者被某些公司用來作為包含一系列相關項目的 monorepo 的替代品。

由於某些用法的複雜性以及使用錯誤的命令相當容易破壞它們的事實,Submodule 得到了一些壞名聲。

但是,它們還是有著它們的用處,而且,我想這仍然是用於需要廠商依賴項的最好選擇。 幸運的是,2016 對子模塊的用戶來說是偉大的一年,在幾次發布中落地了許多意義重大的性能和特性提升。

並行抓取

當克隆或則抓取一個倉庫時,加上 --recurse-submodules 選項意味著任何引用的子模塊也將被克隆或更新。傳統上,這會被串列執行,每次只抓取一個子模塊。直到 Git v2.8,你可以附加 --jobs=n 選項來使用 n 個並行線程來抓取子模塊。

我推薦永久的配置這個選項:

$ git config --global submodule.fetchJobs 4

……或者你可以選擇使用任意程度的平行化。

淺層化子模塊

Git v2.9 介紹了 git clone —shallow-submodules 標誌。它允許你抓取你倉庫的完整克隆,然後遞歸地以一個提交的深度淺層化克隆所有引用的子模塊。如果你不需要項目的依賴的完整記錄時會很有用。

例如,一個倉庫有著一些混合了的子模塊,其中包含有其他廠商提供的依賴和你自己其它的項目。你可能希望初始化時執行淺層化子模塊克隆,然後深度選擇幾個你想工作與其上的項目。

另一種情況可能是配置持續集成或部署工作。Git 需要一個包含了子模塊的超級倉庫以及每個子模塊最新的提交以便能夠真正執行構建。但是,你可能並不需要每個子模塊全部的歷史記錄,所以僅僅檢索最新的提交可以為你省下時間和帶寬。

子模塊的替代品

--reference 選項可以和 git clone 配合使用來指定另一個本地倉庫作為一個替代的對象存儲,來避免跨網路重新複製你本地已經存在的對象。語法為:

$ git clone --reference <local repo> <url>

到 Git v2.11,你可以使用 —reference 選項與 —recurse-submodules 結合來設置子模塊指向一個來自另一個本地倉庫的子模塊。其語法為:

$ git clone --recurse-submodules --reference <local repo> <url>

這潛在的可以省下大量的帶寬和本地磁碟空間,但是如果引用的本地倉庫不包含你克隆的遠程倉庫所必需的所有子模塊時,它可能會失敗。

幸運的是,方便的 —-reference-if-able 選項將會讓它優雅地失敗,然後為丟失了的被引用的本地倉庫的所有子模塊回退為一次普通的克隆。

$ git clone --recurse-submodules --reference-if-able 
<local repo> <url>

子模塊的 diff

在 Git v2.11 之前,Git 有兩種模式來顯示對更新你的倉庫子模塊的提交之間的差異。

git diff —-submodule=short 顯示你的項目引用的子模塊中的舊提交和新提交(這也是如果你整體忽略 --submodule 選項的默認結果):

git diff —submodule=log 有一點啰嗦,顯示更新了的子模塊中任意新建或移除的提交的信息中統計行。

Git v2.11 引入了第三個更有用的選項:—-submodule=diff。這會顯示更新後的子模塊所有改動的完整的 diff。

git stash 的 90 個增強

不像子模塊,幾乎沒有 Git 用戶不鍾愛 git stashgit stash 臨時擱置(或者 藏匿)你對工作區所做的改動使你能夠先處理其他事情,結束後重新將擱置的改動恢復到先前狀態。

自動擱置

如果你是 git rebase 的粉絲,你可能很熟悉 --autostash 選項。它會在變基之前自動擱置工作區所有本地修改然後等變基結束再將其復用。

$ git rebase master --autostash
Created autostash: 54f212a
HEAD is now at 8303dca It&apos;s a kludge, but put the tuple from the database in the cache.
First, rewinding head to replay your work on top of it...
Applied autostash.

這很方便,因為它使得你可以在一個不潔的工作區執行變基。有一個方便的配置標誌叫做 rebase.autostash 可以將這個特性設為默認,你可以這樣來全局啟用它:

$ git config --global rebase.autostash true

rebase.autostash 實際上自從 Git v1.8.4 就可用了,但是 v2.7 引入了通過 --no-autostash 選項來取消這個標誌的功能。如果你對未暫存的改動使用這個選項,變基會被一條工作樹被污染的警告禁止:

$ git rebase master --no-autostash
Cannot rebase: You have unstaged changes.
Please commit or stash them.

補丁式擱置

說到配置標籤,Git v2.7 也引入了 stash.showPatchgit stash show 的默認行為是顯示你擱置文件的匯總。

$ git stash show
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

-p 標誌傳入會將 git stash show 變為 「補丁模式」,這將會顯示完整的 diff:

stash.showPatch 將這個行為定為默認。你可以將其全局啟用:

$ git config --global stash.showPatch true

如果你使能 stash.showPatch 但卻之後決定你僅僅想要查看文件總結,你可以通過傳入 --stat 選項來重新獲得之前的行為。

$ git stash show --stat
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

順便一提:--no-patch 是一個有效選項但它不會如你所希望的取消 stash.showPatch。不僅如此,它會傳遞給用來生成補丁時潛在調用的 git diff 命令,然後你會發現完全沒有任何輸出。

簡單的擱置標識

如果你慣用 git stash ,你可能知道你可以擱置多次改動然後通過 git stash list 來查看它們:

$ git stash list
stash@{0}: On master: crazy idea that might work one day
stash@{1}: On master: desperate samurai refactor; don&apos;t apply
stash@{2}: On master: perf improvement that I forgot I stashed
stash@{3}: On master: pop this when we use Docker in production

但是,你可能不知道為什麼 Git 的擱置有著這麼難以理解的標識(stash@{1}stash@{2} 等),或許你可能將它們勾勒成 「僅僅是 Git 的癖好吧」。實際上就像很多 Git 特性一樣,這些奇怪的標誌實際上是 Git 數據模型的一個非常巧妙使用(或者說是濫用了的)的結果。

在後台,git stash 命令實際創建了一系列特定的提交目標,這些目標對你擱置的改動做了編碼並且維護一個 reglog 來保存對這些特殊提交的引用。 這也是為什麼 git stash list 的輸出看起來很像 git reflog 的輸出。當你運行 git stash apply stash@{1} 時,你實際上在說,「從 stash reflog 的位置 1 上應用這條提交。」

到了 Git v2.11,你不再需要使用完整的 stash@{n} 語句。相反,你可以通過一個簡單的整數指出該擱置在 stash reflog 中的位置來引用它們。

$ git stash show 1
$ git stash apply 1
$ git stash pop 1

講了很多了。如果你還想要多學一些擱置是怎麼保存的,我在 這篇教程 中寫了一點這方面的內容。

好了,結束了。感謝您的閱讀!我希望您喜歡閱讀這份長篇大論,正如我樂於在 Git 的源碼、發布文檔和 man 手冊中探險一番來撰寫它。如果你認為我忘記了一些重要的事,請留下一條評論或者在 Twitter 上讓我知道,我會努力寫一份後續篇章。

至於 Git 接下來會發生什麼,這要靠廣大維護者和貢獻者了(其中有可能就是你!)。隨著 Git 的採用日益增長,我猜測簡化、改進的用戶體驗,和更好的默認結果將會是 2017 年 Git 主要的主題。隨著 Git 倉庫變得越來越大、越來越舊,我猜我們也可以看到繼續持續關注性能和對大文件、深度樹和長歷史的改進處理。

如果你關注 Git 並且很期待能夠和一些項目背後的開發者會面,請考慮來 Brussels 花幾周時間來參加 Git Merge 。我會在那裡發言!但是更重要的是,很多維護 Git 的開發者將會出席這次會議而且一年一度的 Git 貢獻者峰會很可能會指定來年發展的方向。

或者如果你實在等不及,想要獲得更多的技巧和指南來改進你的工作流,請參看這份 Atlassian 的優秀作品: Git 教程

封面圖片是由 instaco.de 生成的。

via: https://medium.com/hacker-daily/git-in-2016-fad96ae22a15

作者:Tim Pettersen 譯者:xiaow6 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出


本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive

對這篇文章感覺如何?

太棒了
0
不錯
0
愛死了
0
不太好
0
感覺很糟
0
雨落清風。心向陽

    You may also like

    Leave a reply

    您的電子郵箱地址不會被公開。 必填項已用 * 標註

    此站點使用Akismet來減少垃圾評論。了解我們如何處理您的評論數據

    More in:Linux中國