English ▾ 主題 ▾ 最新版本 ▾ gitfaq 最後更新於 2.53.0

名稱

gitfaq - 關於使用 Git 的常見問題

概要

gitfaq

描述

本 FAQ 中的範例假設使用標準 POSIX shell(例如 bashdash),以及一位名為 A U Thor 的使用者,其在託管服務商 git.example.org 上擁有帳號 author

組態設定

我應該在 user.name 中填入什麼?

您應該填入您的個人姓名,通常使用名與姓的格式。例如,目前的 Git 維護者使用的是 "Junio C Hamano"。這將作為姓名部分儲存在您所做的每一次提交中。

此設定對遠端服務的驗證沒有任何影響;若要設定驗證,請參閱 git-config[1] 中的 credential.username

http.postBuffer 到底有什麼作用?

此選項會改變 Git 透過 HTTP 或 HTTPS 將資料推送到遠端時所使用的緩衝區大小。如果資料大於此大小,負責處理 Git HTTP 支援的 libcurl 將使用區塊傳輸編碼(chunked transfer encoding),因為推送資料的總大小無法提前得知。

除非您知道遠端伺服器或中間的代理伺服器不支援 HTTP/1.1(該版本引入了區塊傳輸編碼)或者已知處理區塊資料時會出錯,否則保留此值的預設大小即可。這通常被(錯誤地)視為解決一般推送問題的方法,但由於幾乎每個伺服器和代理伺服器至少都支援 HTTP/1.1,提高此值通常無法解決大多數的推送問題。一個無法正確支援 HTTP/1.1 和區塊傳輸編碼的伺服器或代理伺服器在現今的網際網路上並無太大用處,因為它會導致大量流量中斷。

請注意,增加此值會增加 Git 透過 HTTP 或 HTTPS 進行相關推送時所使用的記憶體,因為無論是否用完,整個緩衝區都會被配置。因此,除非您確信需要不同的值,否則最好將其保留為預設值。

如何設定不同的編輯器?

如果您尚未為 Git 指定特定的編輯器,它預設會使用您透過 VISUALEDITOR 環境變數設定的編輯器;如果兩者皆未指定,則使用系統預設值(通常是 vi)。由於有些人覺得 vi 很難使用或偏好不同的編輯器,因此可能需要更改所使用的編輯器。

如果您想為大多數需要編輯器的程式設定一個通用編輯器,您可以編輯您的 shell 設定檔(例如 ~/.bashrc~/.zshenv),加入一行將 EDITORVISUAL 環境變數設定為合適的值。例如,如果您偏好使用 nano 編輯器,則可以寫入以下內容:

export VISUAL=nano

如果您想為 Git 設定特定的編輯器,可以設定 core.editor 設定值或 GIT_EDITOR 環境變數。您可以參閱 git-var[1] 以了解這些選項的查詢順序。

請注意,在所有情況下,編輯器值都會傳遞給 shell,因此任何包含空格的參數都應適當地加上引號。此外,如果您的編輯器在呼叫時通常會與終端機分離,您應該指定一個參數使其不會發生這種情況,否則 Git 將無法看到任何變更。在 Windows 上解決這兩個問題的設定範例是 "C:\Program Files\Vim\gvim.exe" --nofork,它為帶有空格的檔名加上了引號,並指定了 --nofork 選項以避免將程序放到背景執行。

為什麼沒有 commit.signoff 和其他設定變數?

Git 有意不(且未來也不會)提供像 commit.signoff 這樣的設定變數來預設自動加入 --signoff。其原因是為了保護簽署(sign-off)在法律與意圖上的重要性。如果有更多自動化且廣泛宣傳的方式來附加簽署,日後更容易有人辯稱「Signed-off-by」追蹤標記僅是出於習慣或自動化而添加的,而提交者並未充分意識到或無意證明他們同意《開發者原創認證》(DCO)或類似聲明。這可能會削弱簽署在法律或合約情況下的可信度。

雖然存在 format.signoff,但這是一個歷史錯誤,且不應成為在上面添加更多類似錯誤的藉口。

認證憑證

當透過 HTTP 推送時,如何指定我的認證憑證?

最簡單的方法是透過 credential.helper 設定使用憑證協助程式。大多數系統提供與系統憑證管理員整合的標準選擇。例如,Git for Windows 提供 wincred 憑證管理員,macOS 有 osxkeychain 憑證管理員,而具有標準桌面環境的 Unix 系統可以使用 libsecret 憑證管理員。所有這些都會將憑證儲存在加密儲存庫中,以保護您的密碼或權杖(token)安全。

此外,您可以使用將憑證儲存在家目錄檔案中的 store 憑證管理員,或是不會永久儲存您的憑證,但可避免在一段時間內重複提示輸入憑證的 cache 憑證管理員。

您也可以在收到提示時直接輸入密碼。雖然可以將密碼(必須經過百分比編碼)放在 URL 中,但這並不安全,且可能導致憑證意外洩露,因此不建議這樣做。

如何從環境變數讀取密碼或權杖?

credential.helper 設定選項也可以接收一個任意的 shell 指令,該指令會在標準輸出中產生憑證協定。例如,這在將憑證傳遞給容器時非常有用。

此類 shell 指令可以透過在選項值開頭加上驚嘆號來指定。如果您的密碼或權杖儲存在 GIT_TOKEN 中,您可以執行以下指令來設定您的憑證協助程式:

$ git config credential.helper \
	'!f() { echo username=author; echo "password=$GIT_TOKEN"; };f'
如何更改已儲存在憑證管理員中的密碼或權杖?

通常,如果密碼或權杖無效,Git 會將其刪除並提示輸入新的。然而,有時這種情況並不會發生。要更改密碼或權杖,您可以刪除現有的憑證,Git 隨後會提示輸入新的。若要刪除憑證,請使用類似以下的語法(替換您的使用者名稱和主機名稱):

$ echo url=https://author@git.example.org | git credential reject
如何在使用 HTTP 時針對同一個託管服務商使用多個帳號?

區分這些帳號最簡單的方法通常是在 URL 中使用使用者名稱。例如,如果您在 git.example.org 上擁有 authorcommitter 帳號,您可以使用 URL https://author@git.example.org/org1/project1.githttps://committer@git.example.org/org2/project2.git。這樣一來,當您使用憑證協助程式時,它會自動嘗試尋找您帳號的正確憑證。如果您已經設定好遠端,可以使用類似 git remote set-url origin https://author@git.example.org/org1/project1.git 的指令來更改 URL(詳情請參閱 git-remote[1])。

如何在使用 SSH 時針對同一個託管服務商使用多個帳號?

對於大多數支援 SSH 的託管服務商,單一金鑰對唯一識別一個使用者。因此,要使用多個帳號,必須為每個帳號建立一個金鑰對。如果您使用的是較現代的 OpenSSH 版本,可以使用類似 ssh-keygen -t ed25519 -f ~/.ssh/id_committer 的指令建立新的金鑰對。然後,您可以將公開金鑰(此例中為 ~/.ssh/id_committer.pub;注意 .pub 副檔名)註冊到託管服務商。

大多數託管服務商使用單一 SSH 帳號進行推送;也就是說,所有使用者都推送到 git 帳號(例如 git@git.example.org)。如果是這種情況,您可以在 SSH 中設定多個別名,以明確指定要使用哪個金鑰對。例如,您可以在 ~/.ssh/config 中寫入以下內容,並替換為正確的私密金鑰檔案:

# This is the account for author on git.example.org.
Host example_author
	HostName git.example.org
	User git
	# This is the key pair registered for author with git.example.org.
	IdentityFile ~/.ssh/id_author
	IdentitiesOnly yes
# This is the account for committer on git.example.org.
Host example_committer
	HostName git.example.org
	User git
	# This is the key pair registered for committer with git.example.org.
	IdentityFile ~/.ssh/id_committer
	IdentitiesOnly yes

然後,您可以調整您的推送 URL,使用 git@example_authorgit@example_committer,而不是 git@example.org(例如 git remote set-url git@example_author:org1/project1.git)。

傳輸

如何跨系統同步工作目錄(working tree)?

首先,請決定您是否真的需要這樣做。當您使用典型的 git pushgit fetch 指令推送或提取工作時,Git 的運作效果最好,且它並非設計用於跨系統共享工作目錄。這具有潛在風險,在某些情況下可能導致儲存庫毀損或資料遺失。

通常,這樣做會導致 git status 需要重新讀取工作目錄中的每一個檔案。此外,Git 的安全模型不允許在不受信任的使用者之間共享工作目錄,因此只有在所有機器上都由單一使用者使用時,同步工作目錄才是安全的。

切勿使用雲端同步服務來同步 Git 儲存庫的任何部分,這一點非常重要,因為這可能導致儲存庫毀損,例如遺失物件、檔案被變更或新增、參考(refs)損毀,以及其他各種問題。這些服務傾向於持續地逐個檔案同步,並不理解 Git 儲存庫的結構。如果它們在儲存庫更新過程中進行同步,情況會特別嚴重,因為這極有可能導致更新不完整或部分更新,從而造成資料遺失。

可能發生的一種毀損範例是參考狀態發生衝突,導致雙方在同一個分支上擁有不同的提交,而另一方卻沒有。這可能導致重要物件變成未被引用,並可能被 git gc 修剪,進而導致資料遺失。

因此,最好使用正常的推送和提取機制,將您的工作推送到另一個系統或中央伺服器。在 Git 2.51 中,Git 增加了匯入和匯出暫存(stashes)的功能,因此可以透過 git stash 將工作目錄狀態暫存,然後使用 git stash export --to-ref refs/heads/stashes(假設您想匯出到 stashes 分支)匯出所有暫存,或者透過在指令末尾添加數字來選擇特定的暫存。您也可以在最初暫存資料時使用 --include-untracked 參數來包含未追蹤的檔案,但如果其中包含敏感資訊,請務必小心。

然後,您可以推送 stashes 分支(或您匯出到的任何分支),將其提取到本機系統(例如使用 git fetch origin +stashes:stashes),並在另一個系統上使用 git stash import stashes(同樣,根據需要更改名稱)匯入這些暫存。應用變更到工作目錄可以使用 git stash popgit stash apply 完成。這是最穩健且最能避免意外問題的方法。

話雖如此,在某些情況下人們仍然偏好跨系統共享工作目錄。如果您這樣做,建議的方法是在儲存庫根目錄使用 rsync -a --delete-after(最好配合加密連線,例如 ssh)。執行此操作時,您應確保以下幾點:

  • 如果您有額外的工作樹(worktrees)或獨立的 Git 目錄,它們必須與主工作目錄和儲存庫同時同步。

  • 您能接受目標目錄成為來源目錄的精確副本,刪除已存在於該處的所有資料

  • 在傳輸期間,儲存庫(包括所有工作樹和 Git 目錄)必須處於靜止狀態(即不進行任何類型的操作,包括像 git gc 這樣的背景操作,以及由您的編輯器所引發的操作)。

    請注意,即使有這些建議,以這種方式同步工作目錄仍有風險,因為它繞過了 Git 對儲存庫正常的完整性檢查,因此建議進行備份。同步後,您可能還希望執行 git fsck 以驗證目標系統上資料的完整性。

常見問題

我在上一次提交中犯了錯誤,該如何更改?

您可以對工作目錄進行適當更改,執行 git add <file>git rm <file>(視情況而定)將其暫存,然後執行 git commit --amend。您的更改將被包含在提交中,並會提示您再次編輯提交訊息;如果您希望直接使用原始訊息,除了可以使用 git commit--no-edit 選項外,也可以在編輯器開啟時直接儲存並離開。

我所做的變更包含錯誤,且已包含在主分支中,該如何復原?

處理此問題的常用方法是使用 git revert。這保留了原始變更已完成且是一項有價值貢獻的歷史紀錄,但也引入了一個撤銷這些變更的新提交,因為原始變更存在問題。復原提交的提交訊息會指出被復原的提交,並且通常會編輯以包含為什麼進行復原的解釋。

如何忽略對已追蹤檔案的變更?

Git 沒有提供執行此操作的方法。原因是如果 Git 需要覆寫此檔案(例如在 checkout 期間),它不知道對檔案的變更是否珍貴且應該保留,還是不重要且可以安全銷毀。因此,它必須採取安全的路徑,始終保留它們。

使用 git update-index 的某些功能(即 assume-unchanged 和 skip-worktree 位元)可能會很誘人,但這些功能在此目的下無法正常運作,且不應以這種方式使用。

如果您的目標是修改設定檔,通常將一個模板或預設值檔案檢入儲存庫會很有幫助,然後將其複製並進行相應修改。這個經過修改的第二個檔案通常會被忽略,以防止意外提交它。

我要求 Git 忽略各種檔案,但它們仍然被追蹤

gitignore 檔案可確保某些未被 Git 追蹤的檔案保持不被追蹤。然而,有時特定的檔案可能在添加到 .gitignore 之前就已經被追蹤,因此它們仍然保持被追蹤狀態。若要取消追蹤並忽略檔案/模式,請使用 git rm --cached <file/pattern>,並將與 <file> 相符的模式添加到 .gitignore 中。詳情請參閱 gitignore[5]

我如何知道我該執行 fetch 還是 pull?

fetch 會儲存遠端儲存庫最新變更的副本,而不修改工作目錄或目前分支。然後,您可以隨時檢查、合併、在之上進行重定基底,或忽略上游的變更。pull 則包含一個 fetch,隨後立即進行合併或重定基底。請參閱 git-pull[1]

我可以在 Git 中使用代理伺服器嗎?

可以,Git 支援使用代理伺服器。Git 會尊重 Unix 上常用的標準 http_proxyhttps_proxyno_proxy 環境變數,也可以透過 http.proxy 和類似選項為 HTTPS 進行設定(參閱 git-config[1])。http.proxy 及相關選項可依 URL 模式進行客製化。此外,理論上 Git 可以與網路上的透明代理伺服器正常運作。

對於 SSH,Git 可以透過 OpenSSH 的 ProxyCommand 支援代理。常用的工具包括 netcatsocat。然而,必須設定它們在標準輸入中看到 EOF 時不會退出,這通常意味著 netcat 需要 -q,而 socat 需要類似 -t 10 的逾時設定。這是必須的,因為 Git SSH 伺服器得知不再有更多請求的方式是標準輸入上的 EOF,但當這種情況發生時,伺服器可能尚未處理完最後一個請求,因此在那一點斷開連線會中斷該請求。

~/.ssh/config 中使用 HTTP 代理的設定範例可能如下所示:

Host git.example.org
    User git
    ProxyCommand socat -t 10 - PROXY:proxy.example.org:%h:%p,proxyport=8080

請注意,在所有情況下,為了使 Git 正常運作,代理伺服器必須是完全透明的。代理伺服器不能以任何方式修改、篡改或緩衝連線,否則 Git 幾乎肯定會無法運作。請注意,許多代理伺服器,包括許多 TLS 中間盒(middleboxes)、除了 Windows Defender 和 Windows 防火牆之外的 Windows 防毒軟體與防火牆程式,以及過濾型代理伺服器,都無法達到此標準,導致 Git 運作中斷。由於有許多關於問題的報告及其不良的安全歷史,我們建議不要使用這些類別的軟體和裝置。

合併與重定基底

在對長期存在的分支使用壓縮合併(squash merge)時,會發生什麼類型的問題?

總體而言,當多次使用壓縮合併來合併兩個分支時,可能會發生各種問題。這些可能包括在 git log 輸出、GUI 中,或當使用 ... 符號來表示範圍時,看到額外的提交,以及可能需要反覆重新解決衝突。

當 Git 在兩個分支之間進行正常合併時,它考慮三個精確點:兩個分支和一個稱為「合併基準」(merge base)的第三個提交,這通常是提交的共同祖先。合併的結果是合併基準與每個分支頂端(head)之間變更的總和。當您使用一般的合併提交來合併兩個分支時,這會產生一個新的提交,當它們再次合併時,該提交最終會成為合併基準,因為現在有一個新的共同祖先。Git 不必考慮合併基準之前發生的變更,因此您不必重新解決之前已經解決過的任何衝突。

當您執行壓縮合併時,不會建立合併提交;相反,一側的變更作為一般的提交應用到另一側。這意味著這些分支的合併基準不會改變,因此當 Git 下次執行合併時,它會考慮上次考慮的所有變更加上新的變更。這意味著可能需要重新解決任何衝突。同樣地,任何在 git diffgit log 中使用 ... 符號的內容,或 GUI 都將顯示自原始合併基準以來的所有變更。

因此,如果您想反覆合併兩個長期存在的分支,最好始終使用一般的合併提交。

如果我在兩個分支上都進行了變更,但在其中一個分支上復原了它,為什麼合併這些分支時會包含該變更?

預設情況下,當 Git 執行合併時,它會使用一種稱為 ort 的策略,這是一種精巧的三方合併(three-way merge)。在這種情況下,當 Git 執行合併時,它考慮三個精確點:兩個頂端和一個稱為「合併基準」的第三點,這通常是這些提交的共同祖先。Git 完全不會考慮在這些分支上發生的歷史紀錄或個別提交。

因此,如果雙方都有變更,且其中一方復原了該變更,結果就是包含該變更。這是因為程式碼在一側發生了變更,而在另一側沒有淨變更,在這種情況下,Git 會採用該變更。

如果這對您造成問題,您可以改為進行重定基底,將帶有復原的分支重定基底到另一個分支上。在這種情況下,重定基底將會復原該變更,因為重定基底會應用每個個別的提交,包括復原提交。請注意,重定基底會改寫歷史紀錄,因此除非您確定自己能夠接受,否則應避免重定基底已發布的分支。詳情請參閱 git-rebase[1] 中的 NOTES 章節。

勾子(Hooks)

我該如何使用勾子來防止使用者進行某些變更?

進行這些變更唯一安全的地方是在遠端儲存庫(即 Git 伺服器)上,通常是在 pre-receive 勾子或持續整合(CI)系統中。這些位置才能有效地執行政策。

嘗試使用 pre-commit 勾子(或者對於提交訊息,使用 commit-msg 勾子)來檢查這些事項是很常見的做法,如果您是獨立開發者並希望工具協助您,這非常好。然而,在開發人員的機器上使用勾子作為政策控制並不有效,因為使用者可以使用 --no-verify 繞過這些勾子而不被察覺(除此之外還有各種其他方法)。Git 假設使用者可以控制他們本機的儲存庫,且不會試圖阻止這一點或向使用者打小報告。

此外,一些進階使用者發現 pre-commit 勾子會阻礙使用臨時提交來暫存進度或建立修正提交的工作流程,因此無論如何,將這類檢查推送到伺服器會更好。

跨平台問題

我正在使用 Windows,但我的文字檔案被偵測為二進位檔。

當您將文字檔案儲存為 UTF-8 時,Git 的運作效果最好。Windows 上的許多程式支援 UTF-8,但有些不支援,僅使用小端序 UTF-16 格式,Git 會將其偵測為二進位檔。如果您無法在程式中使用 UTF-8,您可以指定工作目錄編碼,指示您的檔案應以哪種編碼進行檢出(checkout),同時在儲存庫中仍將這些檔案儲存為 UTF-8。這使得像 git-diff[1] 這樣的工具能按預期運作,同時仍允許您的工具正常工作。

為此,您可以指定一個具有 working-tree-encoding 屬性的 gitattributes[5] 模式。例如,以下模式設定所有 C 檔案使用 UTF-16LE-BOM,這是在 Windows 上常見的編碼:

*.c	working-tree-encoding=UTF-16LE-BOM

您需要執行 git add --renormalize 才能使其生效。請注意,如果您是在跨平台使用的專案上進行這些變更,您可能希望在每位使用者的設定檔中或 $GIT_DIR/info/attributes 中的檔案中進行,因為在儲存庫中的 .gitattributes 檔案中進行設定,將適用於儲存庫的所有使用者。

請參閱下一個項目以了解關於標準化換行符號的資訊,並參閱 gitattributes[5] 以取得關於屬性檔案的更多資訊。

我正在使用 Windows,git diff 顯示我的檔案結尾有 ^M

預設情況下,Git 預期檔案儲存為 Unix 換行符號。因此,作為 Windows 換行符號一部分的歸位字元(carriage return,^M)會被顯示出來,因為它被視為行尾空白。Git 預設僅在新行上顯示行尾空白,而不是在已存在的行上。

您可以將檔案以 Unix 換行符號儲存在儲存庫中,並自動將它們轉換為您平台的換行符號。若要這樣做,請將設定選項 core.eol 設定為 native,並參閱關於推薦儲存設定的問題,了解如何將檔案設定為文字或二進位格式的資訊。

如果您不希望從換行符號中刪除歸位字元,也可以透過 core.whitespace 設定來控制此行為。

為什麼我有一個檔案總是顯示為已修改?

在內部,Git 始終將檔名儲存為位元組序列,且不執行任何編碼或大小寫折疊。然而,Windows 和 macOS 預設都會對檔名執行大小寫折疊。因此,最終可能會出現多個檔案或目錄,其名稱僅在大小寫上有所不同。Git 可以很好地處理這種情況,但檔案系統只能儲存其中一個檔案,因此當 Git 讀取另一個檔案以查看其內容時,它看起來就像是被修改了。

最好刪除其中一個檔案,只保留一個檔案。您可以在其他方面乾淨的工作目錄中使用類似以下的指令(假設有兩個檔案 AFile.txtafile.txt):

$ git rm --cached AFile.txt
$ git commit -m 'Remove files conflicting in case'
$ git checkout .

這避免了觸碰磁碟,但刪除了額外的檔案。您的專案可能偏好採用命名慣例,例如全小寫名稱,以避免此問題再次發生;可以使用 pre-receive 勾子或作為持續整合(CI)系統的一部分來檢查此慣例。

如果您的系統正在使用 smudge 或 clean 篩選器,但檔案先前在未執行 smudge 或 clean 篩選器的情況下被提交,那麼在任何平台上都可能出現永久被修改的檔案。若要修復此問題,請在其他方面乾淨的工作目錄中執行以下指令:

$ git add --renormalize .

GIT

git[1] 套件的一部分