Skip to content

Latest commit

 

History

History
1948 lines (1346 loc) · 73 KB

README_zh-TW.md

File metadata and controls

1948 lines (1346 loc) · 73 KB

Git 飛行規則(Flight Rules)

🌍 EnglishEspañolРусский繁體中文簡體中文한국어Tiếng ViệtFrançais日本語

前言

  • 英文原版 README
  • 翻譯可能存在錯誤或不標準的地方,歡迎大家指正和修改,謝謝!

什麼是「飛行規則」?

這是一篇給太空人(這裡就是指使用 Git 的程式設計師們)的指南,用來指導問題出現後的應對之法。

飛行規則(flight rules)是記錄在手冊上的來之不易的一系列知識,記錄了某個事情發生的原因,以及怎樣一步一步的進行處理。本質上,它們是特定場景的非常詳細的標準處理流程。[...]

自 20 世紀 60 年代初以來,NASA 一直在捕捉(capturing)失誤、災難和解決方案。當時水星時代(Mercury-era)的地面小組首先開始將「經驗教訓」收集到一個綱要(compendium)中,該綱現在已經有上千個問題情景,從發動機故障、到破損的艙口把手、再到計算機故障,以及它們對應的解決方案。

——Chris Hadfield,《一個太空人的生活指南》(An Astronaut's Guide to Life)

這篇文章的約定

為了清楚的表述,這篇文件裡的所有例子使用了自訂的 Bash 提示字元,以便指示目前分支和是否有暫存的更動。分支名用小括號括起來,分支名後面跟的 * 表示暫存的更動。

Join the chat at https://gitter.im/k88hudson/git-flight-rules

Table of Contents generated with DocToc

版本庫

我想建立本機版本庫

將現有目錄初始化為 Git 版本庫:

(my-folder)$ git init

我想複製遠端版本庫

要複製遠端版本庫,找到該版本庫的 URL,然後執行:

$ git clone [URL]

版本庫會複製到與其名稱相同的目錄中。確保你能連線到你要從之複製的遠端伺服器(通常來說,這表示你要連接上網際網路。)

如果想要將版本庫複製到不同名稱的目錄:

$ git clone [URL] [目錄名稱]

我設定了錯的遠端版本庫

有幾種可能的情況:

如果你複製錯了遠端版本庫,刪掉 git clone 創建的目錄,然後複製正確的版本庫就好。

如果你為本機版本庫設定了錯的遠端,你可以用以下命令變更 origin 的 URL:

$ git remote set-url origin [正確的 URL]

詳見 此 Stack Overflow 討論串

我想將程式碼加到其他人的版本庫中

Git 不允許沒有存取權的人將程式碼加到其他人的版本庫。GitHub,一個 Git 版本庫的託管服務,也不行。不過你可以建議程式碼,透過修補,或者用 GitHub 的分叉與拉取請求。

關於分叉:分叉是版本庫的複本。這不是 Git 的功能,但是是 GitHub、Bitbucket、GitLab——或其他任何託管 Git 版本庫的地方——上很常見的動作。你可以透過其 UI 分叉版本庫。

透過拉取請求建議程式碼

在分叉版本庫後,你只需要一般地將其複製到你的機器上。你可以在 GitHub 上做一些小修改,而不需要複製,但這不是一份 GitHub 飛行規則,所以我們還是來談談該怎麼在本機上操作吧。

# 如果你用 SSH 的話:
$ git clone [email protected]:k88hudson/git-flight-rules.git

# 如果你用 HTTPS 的話:
$ git clone https://github.com/k88hudson/git-flight-rules.git

如果你 cd 進創建的目錄,然後輸入 git remote,你會看到遠端的列表。基本上,會有一個遠端——origin——指向 k88hudson/git-flight-rules。在這個情況下,我們也希望有一個指向你的分叉的遠端。

首先,按照 Git 的慣例,我們用 origin 表示你的分叉、upstream 表示原本的版本庫。因此,先將 origin 重新命名為 upstream

$ git remote rename origin upstream

你也可以用 git remote set-url 來達到同樣的結果,但會花費更多步驟。

接著,設定一個新的遠端指向你的版本庫:

$ git remote add origin [email protected]:YourName/git-flight-rules.git

現在有兩個遠端了:

  • origin 指向你分叉版本庫。
  • upstream 則指向原版版本庫。

origin 可以讀寫,upstream 則是唯讀的。

當你完成更動後,推送你(通常在某個分支上的)更動到遠端 origin。如果是在分支上,你可以用 --set-upstream 來避免每次推送都要指定遠端分支。例如:

(feature/my-feature)$ git push --set-upstream origin feature/my-feature

你沒辦法用 Git 在 CLI 中發起拉取請求(但有一些工具可以幫你做這件事,例如 hub)。所以當你準備好時,到 GitHub(或其他 Git 託管服務)上創建拉取請求。

之後也別忘了回覆程式碼的檢閱回饋。

透過修補建議程式碼

另一個建議更動的方法是用 git format-patch,這不依賴於如 GitHub 的第三方服務。

format-patch 會為你的提交創建一個 .patch 檔案,本質上就是類似於能在 GitHub 上看到的提交差異的更動列表。

git am 可以用於查看、甚至編輯、套用修補檔。

例如,要基於前一個提交創建修補檔,你可以執行 git format-patch HEAD^,來創建一個名字類似於 0001-提交訊息.patch 的修補檔。

將修補檔套用至你的版本庫則需執行 git am ./0001-提交訊息.patch

修補檔也可以以 git send-email 命令透過 email 傳送。使用與組態資訊參見:https://git-send-email.io

我需要將分叉更新到原版的最新進度

在一段時間後,upstream 版本庫可能更新了,而這些更新需要拉取至你的 origin 版本庫。

你可能已經設定了指向原版的遠端。如果還沒的話,執行以下命令。通常我們用 upstream 作為名稱:

$ git remote add upstream [原版的 URL]

現在你可以從 upstream 抓取,並取得最新更新了。

(main)$ git fetch upstream
(main)$ git merge upstream/main

# 或者:
(main)$ git pull upstream main

編輯提交

我剛才提交了什麼?

如果你盲目地用 git commit -a 提交了更動,而不確定到底提交了哪些內容,可以用以下命令顯示目前 HEAD 上的最近一次的提交:

(main)$ git show

或者

$ git log -n1 -p

如果你想查看特定提交的特定檔案,你可以用:([提交] 是你想要的提交)

$ git show [提交]:filename

我的提交訊息寫錯了

如果你的提交訊息寫錯了,且這次提交還沒有推送,可以透過下面的方法來修改提交訊息:

$ git commit --amend --only

這會開啟你的預設編輯器來編輯訊息。你也可以選擇只靠一個命令來做這些事:

$ git commit --amend --only -m 'xxxxxxx'

如果你已經推送了提交,可以在修改後強制推送,但是不推薦這麼做。

我提交裡的使用者名稱和信箱不對

如果只是單個提交有錯,修正它:

$ git commit --amend --no-edit --author "New Authorname <[email protected]>"

另一個方法是在 git config --global author.(name|email) 正確配置你的作者資訊,然後用:

$ git commit --amend --no-edit --reset-author

如果你需要修改所有歷史記錄,參考 git filter-branch 的手冊頁。

我想從一個提交裡移除一個檔案

要從一個提交裡移除一個檔案:

$ git checkout HEAD^ filename
$ git add filename
$ git commit --amend --no-edit

如果該檔案是新加入的,而你想要(從 Git)刪除它,用:

$ git rm --cached filename
$ git commit --amend --no-edit

當你有一個開放的修補,而你往上面提交了一個不必要的檔案,需要強制推送去更新這個遠端修補時,這非常有用。--no-edit 選項將保留現有提交訊息。

我想將更動從一個提交移到另一個

如果你在一個提交作了一個更動,而它更符合另一個提交做的事,你可以用互動式重定基底將更動移動過去。這節來自 Stack Overflow

假設你有三個提交,abcb 變更了 file1file2,你想要把 file1 的更動從 b 移到 a

首先,互動式重定基底:

$ git rebase -i HEAD~3

這會打開包含以下內容的編輯器:

pick a
pick b
pick c

ab 那行改為 edit

edit a
edit b
pick c

儲存並關閉編輯器後,你會被帶到 b。重設 file1 的更動:

$ git reset HEAD~1 file1

這會取消暫存 file1 的更動。貯存更動然後繼續重定基底:

$ git stash
$ git rebase --continue

現在要編輯 a。彈出貯存,將更動加入這個提交,然後繼續重定基底:

$ git stash pop
$ git add file1
$ git commit --amend --no-edit
$ git rebase --continue

現在你完成重定基底,並將更動從 b 移到 a 了。如果你要將更動從 b 移到 c,因為 cb 之前,你會需要重定基底兩次,一次將更動從 b 取出,一次將更動加入到 c

我想刪除我最後一次提交

如果你需要刪除推送了的提交,你可以使用以下方法。但是,這將不可逆的改變你的歷史記錄,也會搞亂那些已經從該版本庫拉取了的人的歷史記錄。簡而言之,如果你不是很確定,千萬不要這麼做。

$ git reset HEAD^ --hard
$ git push --force-with-lease [遠端] [分支]

如果你還沒有推送到遠端,重設到你最後一次提交前的狀態就可以了(同時保存暫存的更動):

(my-branch)$ git reset --soft HEAD^

這只能在推送之前使用。如果你已經推送了,唯一安全的做法是 git revert [不要的提交],那會創建一個新的提交來還原前一個提交的所有更動;或者,如果這個分支是重定基底安全的(即其他開發者不會從這個分支拉取),只需要使用 git push --force-with-lease,參見這一節前半部分。

刪除任意提交

同樣,除非必須,否則不要這麼做。

$ git rebase --onto [不要的提交]^ [不要的提交]
$ git push --force-with-lease [遠端] [分支]

或者使用互動式重定基底刪除那些你想要刪除的提交所對應的行。

我嘗試推送一個修正後的提交到遠端,但是報錯

To https://github.com/yourusername/repo.git
! [rejected]        mybranch -> mybranch (non-fast-forward)
error: failed to push some refs to 'https://github.com/tanay1337/webmaker.org.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

注意,重定基底和修正會用新的提交取代舊的,所以如果舊的提交已經推送到遠端上了,那你必須強制推送。注意:總是確保你指明一個分支!

(my-branch)$ git push --force-with-lease origin mybranch

一般來說,應該避免強制推送。最好是創建和推送一個新的提交,而不是強制推送一個修正後的提交。後者會使其他開發者該分支或其子分支的歷史記錄,與遠端歷史記錄產生衝突。--force-with-lease 仍然可能失敗,如果有人在同樣的分支上推送了提交,而你的推送會覆蓋他們的更動。

如果你完全確定沒有人在同一個分支上工作,或者你想要無條件更新分支的頂端,你可以用 --force-f),但通常應該避免。

我意外地硬性重設了,我想找回我的內容

如果你意外地做了 git reset --hard,你通常能找回你的提交,因為 Git 對每件事都會有日誌,且都會保存幾天。

注意,只適用於你的作業備份了的情況,即有提交或貯存。git reset --hard 會移除未提交的更動,所以請謹慎使用。(更安全的選擇是 git reset --keep。)

(main)$ git reflog

你將會看到一個你過去提交的列表,和一個重設的提交。選擇你想要回到的提交的 SHA,再重設一次:

(main)$ git reset --hard [SHA]

我意外地提交並推送了合併

如果你意外地將功能分支在準備好合併之前,合併進了主開發分支,你可以撤銷合併。但有個問題:合併提交有多於一個親代(通常是兩個)。

使用命令:

(feature-branch)$ git revert -m 1 [提交]

-m 1 選項的意思是選擇 1 號親代(合併進去的分支)作為要還原到的親代。

注意,親代編號不是提交識別符。合併提交有一行 Merge: 8e2ce2d 86ac2e7,親代編號是這行上的親代從 1 開始的索引,第一個識別符是 1 號親代,第二個是 2 號,以此類推。

我意外地提交並推送了敏感資料

如果你意外地推送了包含敏感或私人資料(密碼、金鑰等)的檔案,你可以修正該提交。記得,你應該認定其中的所有資料都外洩了。以下步驟可以從你的本機複本和公開版本庫移除敏感資料,但你不能從別人拉取的複本移除敏感資料。如果你提交了密碼,立刻變更,如果你提交了金鑰,立刻重新生成。修正推送的提交並不夠,任何人都可能拉取了包含敏感資料的原提交。

如果你編輯檔案移除了敏感資料,執行:

$ git add [編輯過的檔案]
$ git commit --amend --no-edit
$ git push --force-with-lease [遠端] [分支]

或是將敏感資料儲存在本機環境變數。

如果你希望移除整個檔案(但保留在本機),執行:

$ git rm --cached [敏感的檔案]
$ echo [敏感的檔案] >> .gitignore
$ git add .gitignore
$ git commit --amend --no-edit
$ git push --force-with-lease [遠端] [分支]

如果你同時做了其他提交(即,敏感資料不是在上一個提交引入的),你需要重定基底。

我想要從現有的版本庫歷史記錄中移除大檔案

如果你想移除的檔案是機密或敏感的,見〈我意外地提交並推送了敏感資料〉

即使你在最近的提交中刪除了大或不想要的檔案,它仍然存於 Git 歷史記錄,存於版本庫的 .git 目錄中,且 git clone 也會下載到不需要的檔案。

這節中提到的操作需要強制推送,且會重寫版本庫大部分的歷史,所以如果你與其他人在進行遠端協作,請先確認他們的本機複本都推送了。

有兩個方法可以重寫歷史記錄:內建的 git filter-branchBFG Repo-Cleaner。BFG 能以更好的效能顯著清理,但是是第三方,且需要 Java。這裡兩種選擇都會描述。最後一步是強制推送,這需要比起一般強制推送更再三考慮,因為這會永久變更版本庫大量的歷史記錄。

推薦的工具:第三方的 BFG

BFG Repo Cleaner 需要 Java。到[這裡]下載 BFG 的 JAR 檔。以下例子中將假設檔案名稱為 bfg.jar,位於 ~/Downloads/

刪除特定檔案:

$ git rm path/filename
$ git commit
$ java -jar ~/Downloads/bfg.jar --delete-files filename

注意,你應該直接給 BFG 檔案名稱,即使其在子目錄中。

你也可以用 glob 模式刪除檔案:

$ git rm *.jpg
$ git commit
$ java -jar ~/Downloads/bfg.jar --delete-files *.jpg

BFG 不會影響在最新提交中存在的檔案。例如,版本庫中有幾個大 .tga 檔,在之前的提交移除了其中一些,在最新的提交還存在的檔案則不會觸及。

注意,如果你重新命名了檔案,例如原本是 LargeFileFirstName.mp4 的檔案在一次提交中重新命名為 LargeFileSecondName.mp4java -jar ~/Downloads/bfg.jar --delete-files LargeFileSecondName.mp4 並不會從 Git 歷史記錄中刪除這個檔案。需要對兩個檔案名都執行一次,或使用匹配到兩個檔案名的模式。

內建的工具:git filter-branch

git filter-branch 較為麻煩且功能較少,但如果你不能安裝或執行 BFG,你可以使用它。

將以下例子中的 filepattern 替換為某個檔案名稱或模式,例如 *.jpg。所有分支的歷史記錄中匹配到的檔案都會被移除。

$ git filter-branch --force --index-filter 'git rm --cached --ignore-unmatch filepattern' --prune-empty --tag-name-filter cat -- --all

解釋:

--tag-name-filter cat 是使用 cat 命令將原本的標籤套用至新提交上,麻煩、但最簡單的方法。

--prune-empty 移除任何變為空的提交。

最後一步:推送你變更過的歷史

一旦你移除了檔案,細心地測試一下你沒有弄壞版本庫的任何東西——如果有,最簡單的方法是重新複製一個,然後重頭來過。最後,可以用 Git 垃圾收集來最小化你本機 .git 目錄的大小,然後強制推送。

$ git reflog expire --expire=now --all && git gc --prune=now --aggressive
$ git push --force --tags [遠端]

由於你重寫了整個歷史記錄,git push 可能會需要推送太多資料,導致回傳錯誤 The remote end hung up unexpectedly(遠端意外掛斷了)。如果出現這個問題,可以嘗試增加 Git post 緩衝區大小:

$ git config http.postBuffer 524288000
$ git push --force

如果沒有用的話,你需要手動將提交分成多塊推送歷史記錄。試著增加以下命令中的 <數量> 直到成功推送:

(main)$ git push -u [遠端] HEAD~<數量>:refs/head/main --force

第一次成功推送後,逐步減少 <數量>,直到可以成功使用常規的 git push

我需要變更非最新者的提交的內容

如果你創建了好幾個提交,然後發現自己少做了一些應該在其中的第一個提交做的事。若創建新提交來放這些少做的更動,你會有乾淨的版本庫,但是你的提交便不是「原子」的(也就是說,應該在一起的更動沒有在同一個提交)。因此,你可能會想變更第一個提交,也就是這些更動應該在的地方,並保持其後的提交不變。這種情況下,重定基底或許能幫上忙。

假設你要變更第三新的提交。

$ git rebase -i HEAD~4

開始互動式重定基底後,你可以編輯最新的三個提交。啟動的文字編輯器會顯示類似這樣的內容:

pick 9e1d264 第三新的提交
pick 4b6e19a 第二新的提交
pick f4037ec 最新的提交

將其改為:

edit 9e1d264 第三新的提交
pick 4b6e19a 第二新的提交
pick f4037ec 最新的提交

然後儲存並退出編輯器。這表示你想要編輯第三新的提交,並保持其他兩者不變。重定基底會在你想編輯的提交停下,你可以做原本少了的更動,和往常一樣編輯、暫存,然後:

$ git commit --amend

這會修正這個提交,也就是用原本的更動和新做出的更動,重新創建一個提交來替換掉舊的。接著繼續重定基底就好了:

$ git rebase --continue

暫存

我想暫存所有追蹤的檔案

$ git add -u

只暫存一部分追蹤的檔案

$ git add -u *.txt # 只暫存副檔名 .txt 的檔案。
$ git add -u src/ # 只暫存目錄 src/ 中的檔案。

我需要把暫存的內容添加到上一次的提交

(my-branch*)$ git commit --amend

如果你沒有要修改提交訊息,可以讓 Git 重用訊息:

(my-branch*)$ git commit --amend -C HEAD

我想要暫存一個新檔案的一部分,而不是這個檔案的全部

一般來說,如果你想暫存一個檔案的一部分,你可以使用以下命令來開啟互動式介面,並使用 s 選項來選擇想要的行。

$ git add --patch filename.x # 或 `-p`。

然而,當這個檔案是新的,則需改用以下命令:

$ git add -N filename.x

然後,你需要用 e 選項來選擇需要添加的行。執行 git diff --cached 將會顯示哪些行暫存了、哪些行只儲存在本機。

我想把在一個檔案裡的更動加到兩個提交裡

git add 會把整個檔案加入到一個提交,git add -p 則允許你互動式地選擇想要提交的部分。

我暫存了太多更動,而想把它們分到多個提交中

git reset -p 會打開修補模式重設對話框。類似於 git add -p,但選擇 yes 會取消暫存更動。

我想把暫存的內容變成未暫存,把未暫存的內容暫存起來

多數情況下,你應該將所有的內容變為未暫存,然後再加入你想要的內容提交。但如果你就是想這麼做,你可以創建一個臨時的提交來儲存你已暫存的內容,然後加入未暫存的內容並貯存起來。最後,重設最後一個提交將原本暫存的內容變為未暫存,最後彈出貯存。

$ git commit -m "WIP"     # 將之前已暫存的內容提交。
$ git add .               # 加入未暫存的內容。
$ git stash               # 貯存剛剛加入的內容。
$ git reset HEAD^         # 重設到父提交。
$ git stash pop --index 0 # 彈出貯存。

註一:這裡使用 pop 僅僅是因為想盡可能保持冪等。

註二:假如不加上 --index,會把暫存的檔案標記為未暫存。這裡解釋得比較清楚。(其大意是說,這是一個較為底層的問題,貯存時會創建兩個提交,一個記錄索引狀態、暫存的內容等,另一個紀錄工作區和其他的一些東西,如果你不在套用時指定索引,Git 會把兩個一起銷毀,所以暫存區裡就空了)。

未暫存的更動

我想把未暫存的更動移動到新分支

$ git checkout -b my-branch

我想把未暫存的更動移動到另一個現有分支

$ git stash
$ git checkout my-branch
$ git stash pop

我想捨棄未提交的更動

取消暫存所有更動:

$ git reset

捨棄所有未提交的更動(在版本庫根目錄執行):

$ git checkout .

捨棄特定檔案未提交的更動:

$ git checkout [檔案名稱]

另一個捨棄所有未提交的更動的方法(比較長,但可以在任何子目錄執行):

$ git reset --hard HEAD

移除所有未追蹤的本機檔案,只有 Git 追蹤的會保留:

$ git clean -fd

-x 能將忽略的檔案也移除。

我想捨棄某些未暫存的更動

如果只想捨棄某些更動,而不是全部,簽出不要的、保留要的:

$ git checkout -p
# 回答 y 捨棄不要的更動。

另外一個方法是使用貯存。貯存所有要的更動,重設工作複本,然後把貯存彈出。

$ git stash -p
# Select all of the snippets you want to save
$ git reset --hard
$ git stash pop

或者,貯存你不需要的部分,然後捨棄貯存。

$ git stash -p
# Select all of the snippets you don't want to save
$ git stash drop

分支

我想列出所有分支

列出本機分支:

$ git branch

列出遠端分支:

$ git branch -r

列出兩者:

$ git branch -a

我想從一個提交創建分支

$ git checkout -b [分支名稱] [提交 SHA]

我從錯誤的分支拉取,或拉取到錯誤的分支了

這是另外一種可以使用 git reflog 情況。找到在這次錯誤拉取之前 HEAD 的指向。

(main)$ git reflog
ab7555f HEAD@{0}: pull origin wrong-branch: Fast-forward
c5bc55a HEAD@{1}: checkout: checkout message goes here

然後,重設分支到所需的提交:

$ git reset --hard c5bc55a

完成。

我想捨棄本機上的提交,以讓分支與遠端保持一致

首先,確認你沒有推送到遠端。

git status 會顯示本機領先遠端多少個提交:

(my-branch)$ git status
# On branch my-branch
# Your branch is ahead of 'origin/my-branch' by 2 commits.
#   (use "git push" to publish your local commits)
#

一種方法是:

(my-branch)$ git reset --hard origin/my-branch

我需要提交到一個新分支,但錯誤的提交到了 main

main 下創建一個新分支:

(main)$ git branch my-branch

main 重設到前一個提交:

(main)$ git reset --hard HEAD^

HEAD^HEAD^1 的縮寫,表示 HEAD 的第一個親代,而 HEAD^2 則是其第二個親代(合併提交會有兩個親代)。

請注意 HEAD^2 不同於 HEAD~2,詳見 這篇部落格

或者,如果你不想用 HEAD^,找到想要將 main 重設到的提交的雜湊值(git log 可以做到這件事)。然後重設到那個提交。例如,要將 main 重設到 a13b85e,執行 git reset --hard a13b85e

簽出剛才新建的分支繼續工作:

(main)$ git checkout my-branch

我想從另一個引用之類保留整個檔案

假設你正在做一個探針解決方案(註),有成百上千的更動,一切都運作良好。現在,你提交到另一個分支來儲存該工作:

(solution)$ git add -A && git commit -m "Adding all changes from this spike into one big commit."

當你想要把它放到一個分支裡(可能是功能分支,可能是 develop),你希望保持整個檔案的完整,並將大的提交分割成數個小的。

假設有:

  • 分支 solution,擁有原型方案,領先 develop
  • 分支 develop,你想套用解決方案的分支。

假如檔案名稱是 file1.txt,你可以將 solution 分支的那個檔案的內容放到 develop 分支。

(develop)$ git checkout solution -- file1.txt
(develop)$ git status
# On branch develop
# Your branch is up-to-date with 'origin/develop'.
# Changes to be committed:
#  (use "git reset HEAD <file>..." to unstage)
#
#        modified:   file1.txt

然後,普通地提交。

註:探針解決方案(spike solution)旨在分析或解決問題。當所有人都清楚瞭解問題後,這些方案將被評估或捨棄。參見 Wikipedia

我把在同一分支提交了幾次,而這些提交應該在不同的分支上

假設在 main 分支,執行 git log 的結果如下:

(main)$ git log

commit e3851e817c451cc36f2e6f3049db528415e3c114
Author: Alex Lee <[email protected]>
Date:   Tue Jul 22 15:39:27 2014 -0400

    Bug #21 - Added CSRF protection

commit 5ea51731d150f7ddc4a365437931cd8be3bf3131
Author: Alex Lee <[email protected]>
Date:   Tue Jul 22 15:39:12 2014 -0400

    Bug #14 - Fixed spacing on title

commit a13b85e984171c6e2a1729bb061994525f626d14
Author: Aki Rose <[email protected]>
Date:   Tue Jul 21 01:12:48 2014 -0400

    First commit

要將 e3851e85ea5173 分別移到新的分支,首先,要把 main 分支重設到正確的提交(a13b85e):

(main)$ git reset --hard a13b85e
HEAD is now at a13b85e

新增一個分支:

(main)$ git checkout -b 21

接著,將提交揀選到這個分支的頂端。這意味著套用、且只套用該提交,直接在 HEAD 的頂端。

(21)$ git cherry-pick e3851e8

這可能會造成衝突,參見〈互動式重定基底可能出現的問題衝突〉來解決衝突。

同樣地,為 5ea5173 也創建一個分支,並把提交揀選到其上:

(main)$ git checkout -b 14
(14)$ git cherry-pick 5ea5173

我想刪除上游刪除了的本機分支

比方說,在 GitHub 中,拉取請求合併後,會有一個選項可以刪除你分叉中合併了的分支。如果你並未計劃在該分支上繼續工作,將該分支的本機複本刪除會更乾淨,而不會有一堆陳舊分支。

$ git fetch -p [遠端]

我不小心刪除了分支

如果你定期推送到遠端,多數情況下應該是安全的。但有時可能刪除了還沒推送的分支。為了模擬這種情況,首先,創建一個分支和一個檔案:

(main)$ git checkout -b my-branch
(my-branch)$ touch foo.txt
(my-branch)$ ls
README.md foo.txt

加入更動並提交:

(my-branch)$ git add .
(my-branch)$ git commit -m 'foo.txt added'
(my-branch)$ git log

commit 4e3cd85a670ced7cc17a2b5d8d3d809ac88d5012
Author: siemiatj <[email protected]>
Date:   Wed Jul 30 00:34:10 2014 +0200

    foo.txt added

commit 69204cdf0acbab201619d95ad8295928e7f411d5
Author: Kate Hudson <[email protected]>
Date:   Tue Jul 29 13:14:46 2014 -0400

    Fixes #6: Force pushing after amending commits

現在,切回 main 分支,並「不小心」刪除了 my-branch

(my-branch)$ git checkout main
Switched to branch 'main'
Your branch is up-to-date with 'origin/main'.
(main)$ git branch -D my-branch
Deleted branch my-branch (was 4e3cd85).
(main)$ echo oh noes, deleted my branch!
oh noes, deleted my branch!

你應該想起了 reflog,它記錄了所有動作。

(main)$ git reflog
69204cd HEAD@{0}: checkout: moving from my-branch to main
4e3cd85 HEAD@{1}: commit: foo.txt added
69204cd HEAD@{2}: checkout: moving from main to my-branch

如你所見,其中包含了刪除分支的提交的雜湊值。可以藉此把提交找回來:

(main)$ git checkout -b my-branch-help
Switched to a new branch 'my-branch-help'
(my-branch-help)$ git reset --hard 4e3cd85
HEAD is now at 4e3cd85 foo.txt added
(my-branch-help)$ ls
README.md foo.txt

看!我們把遺失的檔案找回來了。Git 的 reflog 在重定基底出錯時也同樣有用。

我想刪除一個分支

刪除一個遠端分支:

(main)$ git push origin --delete my-branch

或:

(main)$ git push origin :my-branch

刪除一個本機分支:

(main)$ git branch -D my-branch

刪除一個合併進目前分支或上游的分支:

(main)$ git branch -D my-branch

我想刪除多個分支

假如你想刪除以 fix/ 開頭的所有分支:

(main)$ git branch | grep 'fix/' | xargs git branch -d

我想重新命名一個分支

重新命名目前的本機分支:

$ git branch -m [新名稱]

重新命名另一個本機分支:

$ git branch -m [原名稱] [新名稱]

刪除遠端上原名稱的分支,並推送新名稱的分支:

$ git push [遠端] :[原名稱] [新名稱]

我想簽出別人正在其上工作的遠端分支

首先,從遠端抓取所有分支:

(main)$ git fetch --all

假設你想要從遠端 origin 簽出分支 daves

(main)$ git checkout --track origin/daves
Branch daves set up to track remote branch daves from origin.
Switched to a new branch 'daves'

--trackgit checkout -b [分支] [遠端]/[分支] 的縮寫。)

這樣就有 daves 的本機複本了。

我想從本機分支創建一個遠端的

$ git push [遠端] HEAD

如果你亦希望將該遠端分支設為上游,則改用:

$ git push -u [遠端] HEAD

push.default 組態為 upstream 模式或 simple 模式(Git 2.0 的預設值),以下命令會將目前分支推送到先前以 -u 註冊的遠端分支:

$ git push

git push 其他模式的行為參見 push.default 的文件

我想設定本機分支的上游

設定目前的本機分支的上游:

$ git branch --set-upstream-to [遠端]/[分支]
#
$ git branch -u [遠端]/[分支]

設定另一個本機分支的上游:

$ git branch -u [遠端]/[分支] [本機分支]

我想設定 HEAD 追蹤預設遠端分支

透過檢查遠端分支,可以看到 HEAD 在追蹤哪個遠端分支。在某些情況下,可能不會是希望的分支。

$ git branch -r
origin/HEAD -> origin/gh-pages
origin/main

設定 origin/HEAD 追蹤 origin/main

$ git remote set-head origin --auto
origin/HEAD set to main

我在錯誤的分支上做了更動

如果你有未提交的更動,而其應該在另一個分支上,貯存更動然後在正確的分支上套用:

(wrong-branch)$ git stash
(wrong-branch)$ git checkout correct-branch
(correct-branch)$ git stash apply

我想將分支一分為二

你在一個分支上提交了幾次,而你想其一分為二,一個結束於較早的某個提交,一個包含所有提交。

git log 找到你想作為分開點的提交的 SHA,然後:

(original-branch)$ git branch new-branch
(original-branch)$ git reset --hard [分開點]

如果你先前已推送 original-branch,你將需要強制推送,參見 Stack Overflow

重定基底與合併

撤銷重定基底或合併

你可能對一個錯誤的分支做了重定基底或合併,或者無法完成重定基底或合併。Git 在進行危險操作時,會將原本的 HEAD 存成 ORIG_HEAD,因此可以很容易的恢復到之前的狀態。

(my-branch)$ git reset --hard ORIG_HEAD

我重定了基底,但我不想強制推送

不幸的是,如果你想把重定基底的結果反映在遠端分支上,你必須強制推送。因為歷史記錄變更了,遠端不會接受使用快轉,而必須強制推送。這就是許多人使用合併工作流程、而不是重定基底工作流程的主要原因之一,強制推送會使大團隊陷入麻煩。

一種安全的方式是,不要推送到遠端:

(main)$ git checkout my-branch
(my-branch)$ git rebase -i main
(my-branch)$ git checkout main
(main)$ git merge --ff-only my-branch

參見此 Stack Overflow 討論串

我需要組合幾個提交

假設你在(將)對 main 分支拉取請求的分支上工作。最簡單的例子中,你只是想將所有的提交組合成一個單獨的提交,且你不關心提交的時間戳,你可以重設和重新提交。確保 main 是最新的,且你的更動都提交了,然後:

(my-branch)$ git reset --soft main
(my-branch)$ git commit -am "New awesome feature"

如果你想更精細地控制、保留時間戳,你需要互動式重定基底:

(my-branch)$ git rebase -i main

如果沒有相對於另一個分支,你將不得不相對於 HEAD 重定基底。例如,要壓縮最近的兩次提交,需對 HEAD~2 重定基底;組合最近三次提交,則是對 HEAD~3;以此類推。

(main)$ git rebase -i HEAD~2

在執行了互動式重定基底的命令後,你會在編輯器裡看到類似以下的內容:

pick a9c8a1d Some refactoring
pick 01b2fd8 New awesome feature
pick b729ad5 fixup
pick e3851e8 another fix

# Rebase 8074d12..b729ad5 onto 8074d12
#
# 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's log message
#  x, exec = run command (the rest of the line) using shell
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

# 開頭的行是註解,不影響重定基底。

你可以用註解中列出的命令替換 pick,也可以刪除一行來刪除對應的提交。

例如,如果要保留最舊(第一個)的提交,並將其餘的組合成第二個提交,應該將第二個提交之後所有提交的命令改為 f

pick a9c8a1d Some refactoring
pick 01b2fd8 New awesome feature
f b729ad5 fixup
f e3851e8 another fix

如果要組合並重新命名這個提交,應該在第二個提交加上 r,或使用 s 取代 f

pick a9c8a1d Some refactoring
pick 01b2fd8 New awesome feature
s b729ad5 fixup
s e3851e8 another fix

你可以在接著彈出的編輯器中重新命名那個提交:

Newer, awesomer features

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# rebase in progress; onto 8074d12
# You are currently editing a commit while rebasing branch 'main' on '8074d12'.
#
# Changes to be committed:
#	modified:   README.md
#

應該會看到如下的成功訊息:

Successfully rebased and updated refs/heads/main.

安全合併的策略

--no-commit 選項會合併但不會自動提交,給使用者在提交前檢查和修改的機會。--no-ff 會留下功能分支存在過的證據,保持歷史記錄一致。

(main)$ git merge --no-ff --no-commit my-branch

我需要將整個分支合併成單一提交

(main)$ git merge --squash my-branch

我只想組合未推送的提交

假設在推送到上游前,你有幾個在製的提交,這時候不希望把已推送的提交也組合進來,因為其他人可能已經有提交引用它們了。

(main)$ git rebase -i @{u}

這會進行一次互動式重定基底,只會列出還沒推送的提交。對這些提交重新排序或做 squashfixup 都是安全的。

我需要中止合併

有時合併會導致某些檔案有問題,這種情況下可以用 --abort 選項中止衝突解決,嘗試回復到先前的狀態。

$ git merge --abort

此命令在 Git 1.7.4 及以後可用。

我需要更新分支的親代提交

假設有分支:

  • main
  • feature-1,從 main 分支出來
  • feature-2,從 feature-1 延伸出來

如果在 feature-1 上提交,feature-2 的親代提交便不對了(因為是其的延伸,應該要在 feature-1 的頂端)。可以用 git rebase --onto 來修正這個問題。

(feature-2)$ git rebase --onto feature-1 [feature-2 上第一個不想帶來的提交] feature-2

如果你正在一個尚未合併的功能上編寫新的功能,而前者的漏洞修復需要反映到後者的分支,這會很有用。

檢查分支上的所有提交是否都合併了

要檢查一個分支上的所有提交是否都已經合併進了另一個分支,應該在這些分支的 HEAD(或任何提交)之間檢查差異:

(main)$ git log --graph --left-right --cherry-pick --oneline HEAD...feature/120-on-scroll

這會顯示一個分支有而另一個分支沒有的提交,和分支之間不共享的提交的列表。

另一個方法是:

(main)$ git log main ^feature/120-on-scroll --no-merges

互動式重定基底可能出現的問題

編輯介面出現「noop」

如果你看到:

noop

表示重定基底的分支和目前分支在同一個提交,或領先目前分支。你可以嘗試:

  • 確保 main 分支沒有問題
  • HEAD~2 或更早的提交重定基底

衝突

如果不能成功的完成重定基底,你可能必須要解決衝突。

首先用 git status 檢查哪些檔案有衝突:

(my-branch)$ git status
On branch my-branch
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   README.md

在這個例子中,README.md 有衝突。打開衝突的檔案會看到類似下面的內容:

   <<<<<<< HEAD
   some code
   =========
   some code
   >>>>>>> new-commit

你必須解決新提交的內容和 HEAD 中的內容的衝突。

如果你想要保留其中一個分支的版本,你可以用 --ours--theirs

$ git checkout --ours README.md
  • 合併時,--ours 代表保留本機分支的更動,--theirs 則是另一個分支的更動。
  • 重定基底時,--theirs 代表保留本機分支的更動,--ours 則是另一個分支的更動。

關於為什麼互換了,參見 Git 文件的此註記

有時候衝突非常複雜,你可以使用可視化差異編輯器:

(main*)$ git mergetool -t opendiff

解決所有衝突後,加入變更了的檔案,然後用 git rebase --continue 繼續重定基底:

(my-branch)$ git add README.md
(my-branch)$ git rebase --continue

如果在解決所有衝突過後,得到了與提交前一樣的結果,可以使用 git rebase --skip

如果想放棄重定基底,回到之前的狀態,可以在任何時候用:

(my-branch)$ git rebase --abort

貯存

貯存所有更動

貯存工作目錄下所有更動:

$ git stash

也貯存未追蹤的檔案:

$ git stash -u

貯存指定檔案

貯存一個檔案:

$ git stash push working-directory-path/filename.ext

貯存多個檔案:

$ git stash push working-directory-path/filename1.ext working-directory-path/filename2.ext

貯存時附加訊息

$ git stash save <message>

$ git stash push -m <message>

如此可以在使用 stash list 時看到訊息。

套用指定貯存

先列出貯存:

$ git stash list

然後,將以下命令的 n 替換成貯存在堆疊中的位置(最上方為 0),套用指定貯存:

$ git stash apply "stash@{n}"

除此之外,也可以使用時間標記(假如你能記住的話),如:

$ git stash apply "stash@{2.hours.ago}"

貯存時保留未暫存的內容

你需要先手動創建一個貯存提交,然後使用 git stash store

$ git stash create
$ git stash store -m "commit-message" CREATED_SHA1

尋找

我想找到引入特定字串的提交

$ git log -S [要尋找的字串]

常見的選項:

  • --source 顯示引用名稱。
  • --all 尋找每個分支。
  • --reverse 以相反順序印出,即第一個為最早如此變更的提交。

我想找到特定作者/提交者的提交

尋找特定作者/提交者的提交:

$ git log --author=[名字或 email]
$ git log --committer=[名字或 email]

注意作者與提交者並不相同,--author 是寫下程式碼的人,--committer 則是提交程式碼的人。

我想找到包含特定檔案的提交

$ git log -- [檔案路徑]

也可以在檔案路徑中使用萬用字元,如:

$ git log -- **/*.js

使用萬用字元時,以 --name-status 列出提交的檔案會有幫助:

$ git log --name-status -- **/*.js

我想找到特定函式的歷史記錄

$ git log -L :[函式名稱]:[檔案路徑]

也可以結合其他 git log 選項使用,例如修訂版範圍提交限制

我想找到引用特定提交的標籤

$ git tag --contains [提交]

子模組

複製所有子模組

$ git clone --recursive git://github.com/foo/bar.git

如果已經複製了:

$ git submodule update --init --recursive

移除子模組

創建子模組很直覺,但刪除不是,你需要:

$ git submodule deinit [子模組名稱]
$ git rm [子模組名稱]
$ git rm --cached [子模組名稱]
$ rm -rf .git/modules/[子模組名稱]

雜項

從另一個分支拷貝檔案

$ git checkout [分支] -- [檔案名稱]

恢復刪除的檔案

先找到該檔案最後存在的提交:

$ git rev-list -n 1 HEAD -- [檔案名稱]

然後簽出該檔案:

$ git checkout [刪除檔案的提交]^ -- [檔案名稱]

刪除標籤

$ git tag -d [標籤名稱]
$ git push [遠端] :refs/tags/[標籤名稱]

恢復已刪除標籤

如果想恢復一個已刪除標籤,首先,找到無法觸及的標籤:

$ git fsck --unreachable | grep tag

記下這個標籤的雜湊值,然後用 git update-ref

$ git update-ref refs/tags/[標籤名稱] [雜湊值]

已刪除修補檔

如果有人在 GitHub 上向你提出了拉取請求,但他接著刪除了他的分叉,因為 .diff.patch URL 失效,你無法複製他的提交或使用 git am。但你可以透過 GitHub 的特殊引用簽出拉取請求本身。例如將拉取請求 #1 的內容抓取到名為 pr_1 的新分支:

$ git fetch [遠端] refs/pull/1/head:pr_1
From github.com:foo/bar
 * [new ref]         refs/pull/1/head -> pr_1

將版本庫導出為 Zip 檔

$ git archive --format zip --output [zip 的完整檔案路徑] main

推送有相同名稱的分支與標籤

如果遠端有與分支同名的標籤,若試圖以標準的 git push [遠端] [分支] 命令推送該分支時會得到以下錯誤:

$ git push [遠端] [分支]
error: dst refspec same matches more than one.
error: failed to push some refs to '<git server>'

指明要推送 HEAD 引用來修正這個問題:

$ git push [遠端] refs/heads/[分支名稱]

相對地,推送標籤使用:

$ git push [遠端] refs/tags/[標籤名稱]

追蹤檔案

我只想改變一個檔案名稱的大小寫,而不修改內容

(main)$ git mv --force [原名稱] [新名稱]

我想在拉取時覆蓋本機檔案

(main)$ git fetch --all
(main)$ git reset --hard origin/main

我想將檔案從 Git 移除,但保留檔案

(main)$ git rm --cached [檔案名稱]

我想將特定檔案還原至某個修訂版

$ git checkout [提交] -- [檔案名稱]

還原多個檔案:

$ git checkout [提交] -- [檔案名稱1] [檔案名稱2]

我想列出提交或分支之間特定檔案的差異

$ git diff [提交1]:[檔案名稱] [提交2]:[檔案名稱]
#
$ git diff [提交1] [提交2] -- [檔案名稱]

當然,也可以用分支名稱來表示分支頂端的提交。

我想 Git 忽略特定檔案的更動

這可以用於不應該提交的組態模板或其他需在本機加入憑證的檔案。

$ git update-index --assume-unchanged [要忽略的檔案]

注意,這並不會將檔案從版本控制移除——只是在本機上忽略。要取消這個設定,以下命令清除此旗標:

$ git update-index --no-assume-unchanged [要取消忽略的檔案]

用 Git 除錯

git bisect 命令透過二分搜尋找到哪個提交引入了漏洞。

假設你在 main 分支上,想找到哪個提交導致程式出錯。你開始二分搜尋:

$ git bisect start

接著你應該指定哪個提交已經包含了這個漏洞,哪個沒有。例如,你目前所在的修訂版是壞的,而 v1.1.1 是好的:

$ git bisect bad
$ git bisect good v1.1.1

接著 Git 會簽出你提供的範圍中間的提交,並詢問該提交是好是壞。你應該會看到類似這樣的訊息:

Bisecting: 5 revision left to test after this (roughly 5 step)
[c44abbbee29cb93d8499283101fe7c8d9d97f0fe] Commit message
(c44abbb)$

然後你可以檢查這個提交是好是壞。然後用以下命令告訴 Git:

$ git bisect good # 如果是好的。
$ git bisect bad  # 如果是壞的。

Git 會從範圍中選擇另一個提交,這個過程將重複直到沒有剩下的修訂版需要檢查,而命令最後會印出第一個壞提交。

組態

我想為 Git 命令設定別名

在 OS X 和 Linux 下,Git 組態檔案位於 ~/.gitconfig。可以在 [alias] 部分設定一些快捷別名(以及容易拼錯的),如:

[alias]
    a = add
    amend = commit --amend
    c = commit
    ca = commit --amend
    ci = commit -a
    co = checkout
    d = diff
    dc = diff --changed
    ds = diff --staged
    f = fetch
    loll = log --graph --decorate --pretty=oneline --abbrev-commit
    m = merge
    one = log --pretty=oneline
    outstanding = rebase -i @{u}
    s = status
    unpushed = log @{u}
    wc = whatchanged
    wip = rebase -i @{u}
    zap = fetch -p

我想將空目錄加入到版本庫

你無法這麼做!Git 不支援,但有一個技巧——你可以在該目錄創建包含以下內容的 .gitignore 檔案:

# 忽略這個目錄中所有檔案。
.
# 除了這個檔案自身。
!.gitignore

另一個慣例是在該目錄中創建一個名為 .gitkeep 的空檔案:

$ mkdir 空目錄
$ touch 空目錄/.gitkeep

你也可以改稱其為 .keep,將第二個命令改為 touch 空目錄/.keep 即可。

我想快取一個版本庫的使用者名稱和密碼

假設有一個版本庫需要身分認證,這時你可以快取使用者名稱和密碼,而不用每次推送和拉取時都輸入一次。憑證協助程式可以做到這點:

# 設定 Git 使用憑證記憶快取。
$ git config --global credential.helper cache
# 設定快取在 1 小時後過期(以秒為單位)。
$ git config --global credential.helper 'cache --timeout=3600'

顯示可能的憑證協助程式:

$ git help -a | grep credential

作業系統特定的憑證快取協助程式:

# OS X:
$ git config --global credential.helper osxkeychain
# Windows 2.7.3+:
$ git config --global credential.helper manager
# Ubuntu 或其他使用 GNOME 的發行版:
$ git config --global credential.helper gnome-keyring

其他作業系統和發行版可能有不同的協助程式。

我想 Git 忽略權限與檔案模式更動

$ git config core.fileMode false

如果要設定為目前登入的使用者的預設行為:

$ git config --global core.fileMode false

我想設定全域使用者資訊

設定跨版本庫的使用者資訊:

$ git config --global user.name [名字]
$ git config --global user.email [email]

我不知道我做錯了什麼

如果你把事情搞砸了:你錯誤地重設、合併、或強制推送後,找不到自己的提交了;抑或你做得很好,但你想回到以前的某個狀態。

這時 git reflog 就派上用場了。reflog 記錄對分支頂端的任何改變,即使沒有任何分支或標籤參考那個頂端。基本上,只要 HEAD 改變,reflog 就會記錄下來。然而,這只能用於本機分支,且它只追蹤動作(例如,不會追蹤一個沒被記錄的檔案的任何改變)。

(main)$ git reflog
0a2e358 HEAD@{0}: reset: moving to HEAD~2
0254ea7 HEAD@{1}: checkout: moving from 2.2 to main
c10f740 HEAD@{2}: checkout: moving from main to 2.2

上面的 reflog 顯示了曾經從 main 分支簽出到 2.2 分支,然後再簽出回去,還有硬性重設到一個較舊的提交。最新的動作出現在最上面,並以 HEAD@{0} 標示。

如果你不小心移回了提交,reflog 會包含移回前 main 參考的提交(在這個例子中是 0254ea7)。只要硬性重設就能恢復到之前的狀態,這提供了歷史記錄不小心被變更時的安全網。

$ git reset --hard 0254ea7

摘自這裡

Git 快捷命令

Git Bash

如果你已經很熟悉以上命令的用法了,你可能會想創建一些快捷方式,讓你可以用很短的命令完成複雜的任務。

alias sq=squash

function squash() {
    git rebase -i HEAD~$1
}

將以上命令複製至你的 .bashrc.bash_profile

Windows 上的 PowerShell

Windows 上的 PowerShell 也可以設定別名與函式。將以下命令加到你位於 $profile 變數的設定檔,詳見微軟文件網頁的 關於設定檔

Set-Alias sq Squash-Commit

function Squash-Commit {
    git rebase -i HEAD~$1
}

其他資源

書籍

教學

腳本和工具

  • firstaidgit.io——一個可搜尋的 Git 常見問題集
  • git-extra-commands——一堆有用的額外 Git 腳本
  • git-extras——Git 工具集,版本庫概要、repl、歷史記錄、提交百分比和更多
  • git-fire——git-fire 是一個 Git 插件,用於在緊急情況下幫助加入目前所有檔案、提交、推送到一個新分支(防止合併衝突)。
  • git-tips——Git 小撇步
  • git-town——通用、高級 Git 工作流程支援!http://www.git-town.com

GUI 客戶端

  • GitKraken——豪華的 Git 客戶端,適用於 Windows、Mac、Linux
  • git-cola——又一個 Git 客戶端,適用於 Windows、OS X
  • GitUp——一個新的 Git 客戶端,在解決 Git 的複雜問題上有自己的特點
  • gitx-dev——又一個圖形化的 Git 客戶端,適用於 OS X
  • Sourcetree——簡單而強大的免費 Git GUI 客戶端,適用於 Windows、OS X
  • Tower——圖形化 Git 客戶端,適用於 OS X(付費)
  • tig——Git 的終端文本模式介面
  • Magit——Emacs 的 Git 介面
  • GitExtensions——殼層插件、Visual Studio 2010-2015 插件、獨立的 Git 版本庫工具
  • Fork——快速且友善的 Git 客戶端,適用於 Mac(beta)
  • gmaster——包含三路合併、分析重構、語意化差異、合併等的 Git 客戶端,適用於 Windows(beta)
  • gitk——可以簡單查看版本庫狀態的 Git 客戶端,適用於 Linux
  • SublimeMerge——極速、可擴展的客戶端,有三路合併、強大的搜尋、語法高亮功能,活躍開發中