共存:SSH 多密鑰與提交身份隔離)
一臺(tái)機(jī)器上同時(shí)伺候 Gitee 和 GitHub幾乎是每個(gè)國(guó)內(nèi)開(kāi)發(fā)者繞不開(kāi)的日常。我最早的做法很簡(jiǎn)單哪個(gè)平臺(tái)用得多就把git config --global的user.email設(shè)成哪個(gè)remote 地址復(fù)制粘貼push 的時(shí)候彈出密碼框就輸一次能跑就行。直到有一天一個(gè)倉(cāng)庫(kù)的提交記錄里混進(jìn)了另一個(gè)平臺(tái)的郵箱代碼評(píng)審時(shí)被人指出來(lái)才發(fā)現(xiàn)這套能跑就行的配置其實(shí)一直在埋雷。后來(lái)幫幾個(gè)朋友處理過(guò)類(lèi)似問(wèn)題也見(jiàn)過(guò)有人把兩把私鑰覆蓋成一把握手失敗才意識(shí)到這件事值得單獨(dú)寫(xiě)一篇。這篇內(nèi)容講的是同一臺(tái)機(jī)器、同一套 Git 環(huán)境里讓 Gitee 和 GitHub 各自用各自的密鑰、各自的提交身份、互不干擾的完整配置方法。適合已經(jīng)用過(guò) Git 基本命令、但在多平臺(tái)切換時(shí)總遇到權(quán)限報(bào)錯(cuò)或者提交信息錯(cuò)亂的朋友剛接觸 Git 的新手也能照著步驟走我會(huì)把每一步為什么這么做講清楚而不是只丟一串命令讓你抄。1. 兩平臺(tái)共存的真實(shí)痛點(diǎn)與配置總思路1.1 一個(gè) remote 地址背后其實(shí)藏著三件事很多人以為配置兩個(gè)平臺(tái)就是加兩個(gè) remote真出問(wèn)題時(shí)才發(fā)現(xiàn)一個(gè)遠(yuǎn)程倉(cāng)庫(kù)地址背后至少綁著三件互相獨(dú)立的事認(rèn)證憑據(jù)決定你有沒(méi)有權(quán)限推拉代碼提交身份決定別人看到的作者是誰(shuí)遠(yuǎn)程地址的解析方式?jīng)Q定 Git 到底往哪臺(tái)服務(wù)器發(fā)請(qǐng)求。這三件事在單平臺(tái)時(shí)代被默認(rèn)配置糊在一起一旦變成雙平臺(tái)任何一件沒(méi)拆開(kāi)都會(huì)出亂子。舉個(gè)最常見(jiàn)的場(chǎng)景你用 SSH 方式克隆了 Gitee 的倉(cāng)庫(kù)又克隆了 GitHub 的倉(cāng)庫(kù)兩邊的 remote 都是gitxxx.com:user/repo.git這種形式。Git 本身不區(qū)分域名背后的身份它只認(rèn) SSH 握手時(shí)遞過(guò)去的那把私鑰。如果你的~/.ssh目錄里只有一把id_rsa那么兩個(gè)平臺(tái)收到的都是同一把公鑰其中一個(gè)平臺(tái)沒(méi)登記過(guò)這把公鑰握手就會(huì)直接失敗報(bào)Permission denied (publickey)。這不是網(wǎng)絡(luò)問(wèn)題也不是賬號(hào)被封純粹是密鑰沒(méi)分家。提交身份的問(wèn)題更隱蔽。Git 在提交時(shí)會(huì)讀取user.name和user.email如果全局配置只寫(xiě)了一個(gè)郵箱那么你在另一個(gè)平臺(tái)提交的代碼作者信息依然是那個(gè)郵箱。平臺(tái)側(cè)通常會(huì)用郵箱去匹配賬號(hào)匹配不上就顯示成一個(gè)沒(méi)有頭像、沒(méi)有主頁(yè)的陌生提交者。短期看只是不好看長(zhǎng)期看會(huì)影響貢獻(xiàn)統(tǒng)計(jì)、代碼歸屬甚至在團(tuán)隊(duì)協(xié)作時(shí)引發(fā)這段代碼到底誰(shuí)寫(xiě)的的爭(zhēng)議。所以配置雙平臺(tái)的核心不是記住幾條命令而是把認(rèn)證、身份、地址這三條線分別理清楚。1.2 兩條技術(shù)路線SSH 多密鑰與 HTTPS 憑據(jù)分流真正落地時(shí)主流做法就兩條路線選哪條取決于你的使用習(xí)慣和所在網(wǎng)絡(luò)環(huán)境。我把它們的差異整理成一張表你可以對(duì)著自己的情況選。對(duì)比維度SSH 多密鑰方案HTTPS 憑據(jù)分流方案認(rèn)證載體本地私鑰文件賬號(hào)密碼或訪問(wèn)令牌是否需要每次輸入配置 ssh-agent 后免輸入首次輸入后由憑據(jù)助手緩存多平臺(tái)區(qū)分方式靠 SSH config 里的 Host 段匹配靠 URL 域名匹配憑據(jù)條目網(wǎng)絡(luò)適應(yīng)性需要 22 端口可通走 443 端口適應(yīng)性更廣遷移成本拷貝密鑰并重新授權(quán)重新登錄并緩存令牌常見(jiàn)故障密鑰權(quán)限、config 匹配錯(cuò)誤令牌過(guò)期、憑據(jù)緩存串號(hào)SSH 方案的優(yōu)勢(shì)是一次配好、長(zhǎng)期安靜適合經(jīng)常在同一臺(tái)機(jī)器上長(zhǎng)期開(kāi)發(fā)的人。它的門(mén)檻在于要理解 SSH config 的匹配邏輯尤其是 Host 別名和 HostName 的區(qū)別很多人卡在這里。HTTPS 方案的優(yōu)勢(shì)是對(duì)網(wǎng)絡(luò)環(huán)境更寬容公司內(nèi)網(wǎng)、臨時(shí)網(wǎng)絡(luò)都能用代價(jià)是要管理訪問(wèn)令牌令牌一過(guò)期就得重新配而且憑據(jù)助手如果配置不當(dāng)兩個(gè)平臺(tái)的賬號(hào)容易互相覆蓋。我的建議是主力開(kāi)發(fā)機(jī)用 SSH臨時(shí)環(huán)境或者受限網(wǎng)絡(luò)用 HTTPS。兩者不沖突可以同時(shí)存在后面我會(huì)講混用時(shí)要注意什么。關(guān)鍵是先把其中一條走通再考慮另一條不要一上來(lái)就兩套一起配出問(wèn)題時(shí)排查面會(huì)翻倍。1.3 配置分層全局、倉(cāng)庫(kù)、條件包含Git 的配置是分層覆蓋的理解這個(gè)層次比記住命令更重要。最底層是系統(tǒng)級(jí)配置一般不用動(dòng)中間是全局配置--global存在用戶目錄下的.gitconfig最上層是倉(cāng)庫(kù)級(jí)配置--local存在倉(cāng)庫(kù)的.git/config里。上層會(huì)覆蓋下層的同名配置項(xiàng)這就是我們做雙平臺(tái)隔離的基礎(chǔ)。舉個(gè)實(shí)際例子全局配置里寫(xiě)user.email meexample.com某個(gè) GitHub 倉(cāng)庫(kù)里寫(xiě)user.email megithub.example那么在這個(gè)倉(cāng)庫(kù)里提交就用后者其他倉(cāng)庫(kù)仍然用全局值。這個(gè)機(jī)制讓我們可以做到默認(rèn)身份 特例覆蓋不需要每次提交都手動(dòng)指定。再往上一層是條件包含也就是includeIf。它允許你按目錄路徑自動(dòng)加載某段配置比如所有放在~/work/gitee-projects/下的倉(cāng)庫(kù)自動(dòng)使用 Gitee 的身份放在~/work/github-projects/下的自動(dòng)使用 GitHub 的身份。這個(gè)功能很多人不知道但它是長(zhǎng)期維護(hù)雙平臺(tái)配置最省心的方式。我一般建議目錄規(guī)劃和includeIf一起做前期多花十分鐘后面幾年都不用再想這件事。2. 動(dòng)手前的環(huán)境盤(pán)點(diǎn)和基礎(chǔ)準(zhǔn)備2.1 先確認(rèn) Git 版本和配置來(lái)源動(dòng)手改配置之前先花兩分鐘看清楚當(dāng)前環(huán)境。不同版本的 Git 對(duì)includeIf、credential.helper的支持程度不完全一樣尤其是老版本可能存在行為差異。先跑這兩條命令git --version git config --list --show-origin第一條確認(rèn)版本建議在 2.20 以上includeIf的目錄匹配在較新版本里更穩(wěn)定。第二條會(huì)列出所有生效的配置項(xiàng)并且標(biāo)注每一項(xiàng)來(lái)自哪個(gè)文件。這一步非常關(guān)鍵因?yàn)楹芏嗳烁牧税胩鞗](méi)生效最后發(fā)現(xiàn)是某個(gè)配置文件里有重復(fù)項(xiàng)或者某個(gè)倉(cāng)庫(kù)的.git/config里殘留了舊值。看輸出時(shí)重點(diǎn)盯三類(lèi)信息user.name和user.email出現(xiàn)在哪個(gè)文件、credential.helper當(dāng)前是什么值、有沒(méi)有已經(jīng)存在的url.*.insteadOf重寫(xiě)規(guī)則。最后這個(gè)規(guī)則很容易被忽略它會(huì)把某個(gè)前綴的地址自動(dòng)替換成另一個(gè)如果你之前配過(guò)但忘了后面改 remote 地址時(shí)會(huì)發(fā)現(xiàn)怎么改都不對(duì)。提示--show-origin這個(gè)參數(shù)值得養(yǎng)成習(xí)慣排查任何配置問(wèn)題都先看它。沒(méi)有來(lái)源信息的配置列表等于看一本沒(méi)有頁(yè)碼的書(shū)。2.2 舊密鑰的清理要謹(jǐn)慎先備份再動(dòng)手很多人一上來(lái)就rm -rf ~/.ssh這是最危險(xiǎn)的操作。舊密鑰可能還綁著其他服務(wù)刪掉之后恢復(fù)不了只能重新授權(quán)。正確做法是先列目錄、看內(nèi)容、做備份ls -la ~/.ssh cat ~/.ssh/config 2/dev/null看看現(xiàn)有文件里有沒(méi)有id_rsa、id_ed25519、config、known_hosts。known_hosts不要?jiǎng)h它記錄的是你信任過(guò)的服務(wù)器指紋刪了之后首次連接會(huì)重新詢問(wèn)雖然不影響使用但會(huì)多一道確認(rèn)。備份建議把整個(gè).ssh目錄復(fù)制一份到安全位置命名帶上日期比如ssh-backup-20240115。備份時(shí)注意權(quán)限目錄本身應(yīng)該是700私鑰文件是600。如果你在 Windows 上權(quán)限模型不一樣但原則相同私鑰文件不要被其他用戶可讀。我見(jiàn)過(guò)有人把私鑰放在共享目錄里結(jié)果被同步工具傳到了不該去的地方這種事故排查起來(lái)非常麻煩。如果確實(shí)要重新生成密鑰也不要直接覆蓋同名文件。ssh-keygen在目標(biāo)文件已存在時(shí)會(huì)詢問(wèn)是否覆蓋手快一回車(chē)舊密鑰就沒(méi)了。我的習(xí)慣是給新密鑰起帶平臺(tái)后綴的名字從源頭上避免覆蓋。2.3 目錄與命名約定為后面的自動(dòng)化打基礎(chǔ)命名約定看起來(lái)是小節(jié)實(shí)際上是整套方案能不能長(zhǎng)期維護(hù)的關(guān)鍵。我的建議是密鑰文件帶平臺(tái)標(biāo)識(shí)倉(cāng)庫(kù)目錄按平臺(tái)分文件夾配置文件和這兩者保持一致的命名邏輯。密鑰文件我一般這樣命名id_ed25519_gitee和id_ed25519_gitee.pubid_ed25519_github和id_ed25519_github.pub倉(cāng)庫(kù)目錄這樣分~/projects/gitee/放 Gitee 相關(guān)倉(cāng)庫(kù)~/projects/github/放 GitHub 相關(guān)倉(cāng)庫(kù)這樣命名的好處是SSH config 里的IdentityFile和includeIf里的路徑都能一眼對(duì)應(yīng)上出問(wèn)題時(shí)不需要回憶這個(gè)文件到底是干嘛的。而且以后寫(xiě)腳本做批量操作比如批量檢查 remote 地址、批量切換提交身份有規(guī)律的命名能讓腳本簡(jiǎn)單很多。目錄規(guī)劃還有一個(gè)隱性收益它讓身份錯(cuò)亂這類(lèi)問(wèn)題在物理層面就很難發(fā)生。你在這個(gè)目錄下新建倉(cāng)庫(kù)條件配置自動(dòng)生效不需要每次手動(dòng)檢查git config user.email。人都會(huì)忘讓目錄結(jié)構(gòu)替你記住比靠自律靠譜。3. SSH 多密鑰方案一次配置長(zhǎng)期省心3.1 生成兩把獨(dú)立密鑰參數(shù)怎么選生成密鑰用ssh-keygen關(guān)鍵是給每把密鑰獨(dú)立的文件名和注釋。注釋-C建議寫(xiě)清楚用途它會(huì)出現(xiàn)在公鑰末尾方便你在平臺(tái)側(cè)辨認(rèn)。ssh-keygen -t ed25519 -C gitee-dev-machine -f ~/.ssh/id_ed25519_gitee ssh-keygen -t ed25519 -C github-dev-machine -f ~/.ssh/id_ed25519_github這里選ed25519而不是默認(rèn)的rsa原因是 ed25519 密鑰更短、簽名更快現(xiàn)代平臺(tái)基本都支持。如果你維護(hù)的是很老的系統(tǒng)可能只支持 RSA那就用-t rsa -b 4096。算法選擇不用糾結(jié)太多關(guān)鍵是兩把密鑰必須獨(dú)立不能一把通吃兩個(gè)平臺(tái)。執(zhí)行過(guò)程中會(huì)詢問(wèn)是否設(shè)置口令。設(shè)口令的好處是私鑰文件即使泄露攻擊者也需要口令才能用壞處是每次使用都要輸入除非配合 ssh-agent。我的建議是開(kāi)發(fā)機(jī)設(shè)口令并配置 ssh-agent臨時(shí)環(huán)境可以不設(shè)。如果你選擇不設(shè)口令直接回車(chē)兩次即可但要確保私鑰文件權(quán)限嚴(yán)格。生成完成后你會(huì)得到四個(gè)文件兩把私鑰、兩個(gè)公鑰。公鑰以.pub結(jié)尾內(nèi)容可以公開(kāi)私鑰沒(méi)有后綴內(nèi)容絕對(duì)不能外傳。下一步的 config 文件里引用的是私鑰路徑登記到平臺(tái)的是公鑰內(nèi)容別搞反了。3.2 config 文件的關(guān)鍵字段與匹配邏輯~/.ssh/config是 SSH 客戶端的行為說(shuō)明書(shū)它按順序匹配 Host 段找到第一個(gè)匹配的就用那段配置。理解這一點(diǎn)就能明白為什么有時(shí)候配置寫(xiě)了卻不生效——可能是前面的通配段先匹配上了。一個(gè)可用的雙平臺(tái)配置大概長(zhǎng)這樣Host gitee.com HostName gitee.com User git IdentityFile ~/.ssh/id_ed25519_gitee IdentitiesOnly yes Host github.com HostName github.com User git IdentityFile ~/.ssh/id_ed25519_github IdentitiesOnly yes逐字段解釋一下。Host是你連接時(shí)使用的名字也是匹配的鍵HostName是真實(shí)服務(wù)器地址User對(duì)這兩個(gè)平臺(tái)固定是git不是你的賬號(hào)名這一點(diǎn)新手最容易寫(xiě)錯(cuò)IdentityFile指定私鑰路徑IdentitiesOnly yes表示只使用這里指定的密鑰不要讓 SSH 挨個(gè)嘗試默認(rèn)密鑰。最后這個(gè)參數(shù)很關(guān)鍵沒(méi)有它SSH 可能先拿id_rsa去試試失敗了才輪到你指定的密鑰某些服務(wù)器會(huì)因?yàn)閲L試次數(shù)過(guò)多直接拒絕。還有一種寫(xiě)法是用別名區(qū)分比如Host gitee-work配HostName gitee.com這樣 remote 地址就要寫(xiě)成gitgitee-work:user/repo.git。別名適合需要為同一平臺(tái)配置多套身份的場(chǎng)景比如個(gè)人號(hào)和工作號(hào)。但如果只是兩個(gè)平臺(tái)各一套直接用域名做 Host 更直觀remote 地址和平臺(tái)官網(wǎng)給的完全一致復(fù)制粘貼不會(huì)錯(cuò)。注意config 文件里的縮進(jìn)不是必須的但建議統(tǒng)一用四個(gè)空格方便閱讀。文件名必須是config放在~/.ssh/下權(quán)限建議600。權(quán)限過(guò)寬時(shí) SSH 會(huì)忽略這個(gè)文件而且不一定報(bào)錯(cuò)排查起來(lái)很隱蔽。3.3 公鑰登記與文件權(quán)限的一次性檢查公鑰登記在兩個(gè)平臺(tái)的操作路徑不同但邏輯一樣找到 SSH 公鑰設(shè)置頁(yè)粘貼.pub文件的完整內(nèi)容保存。粘貼時(shí)注意不要帶多余的空格或換行有些終端復(fù)制會(huì)帶上換行符導(dǎo)致平臺(tái)側(cè)解析失敗。建議用cat命令輸出后整體選中復(fù)制cat ~/.ssh/id_ed25519_gitee.pub cat ~/.ssh/id_ed25519_github.pub登記完成后做一次權(quán)限檢查。這一步很多人跳過(guò)結(jié)果遇到密鑰明明對(duì)卻認(rèn)證失敗的怪問(wèn)題。Linux 和 macOS 下執(zhí)行chmod 700 ~/.ssh chmod 600 ~/.ssh/id_ed25519_gitee ~/.ssh/id_ed25519_github chmod 644 ~/.ssh/id_ed25519_gitee.pub ~/.ssh/id_ed25519_github.pub chmod 600 ~/.ssh/config權(quán)限的原則是目錄只允許自己進(jìn)入私鑰只允許自己讀寫(xiě)公鑰和 config 可以稍微寬松但也不建議放開(kāi)。Windows 下沒(méi)有這套權(quán)限模型但要確保密鑰文件不在同步盤(pán)或者共享目錄里。我見(jiàn)過(guò)因?yàn)榘?ssh放進(jìn)了會(huì)自動(dòng)同步的文件夾導(dǎo)致私鑰被傳到云端的情況這類(lèi)問(wèn)題不是權(quán)限命令能解決的得從存放位置入手。3.4 連接測(cè)試與首次握手的確認(rèn)流程配置寫(xiě)完后用ssh -T測(cè)試。兩個(gè)平臺(tái)返回的提示文案不一樣但成功時(shí)都會(huì)表明身份認(rèn)證通過(guò)ssh -T gitgitee.com ssh -T gitgithub.com如果看到類(lèi)似歡迎你某某或者successfully authenticated的提示說(shuō)明認(rèn)證通了。如果看到Permission denied (publickey)先別急著改配置按下面的順序排查公鑰是否完整粘貼、IdentityFile路徑是否正確、文件權(quán)限是否合規(guī)、IdentitiesOnly是否加上。這四個(gè)點(diǎn)覆蓋了絕大多數(shù)情況。首次連接還會(huì)詢問(wèn)是否信任服務(wù)器指紋輸入yes回車(chē)即可。這個(gè)指紋會(huì)被寫(xiě)入known_hosts以后不再詢問(wèn)。如果你在自動(dòng)化腳本里跑可以用-o StrictHostKeyCheckingaccept-new參數(shù)但手動(dòng)操作時(shí)建議還是看一眼指紋再確認(rèn)養(yǎng)成習(xí)慣沒(méi)壞處。測(cè)試通過(guò)后別忘了把私鑰加入 ssh-agent這樣設(shè)了口令的密鑰不用每次輸入eval $(ssh-agent -s) ssh-add ~/.ssh/id_ed25519_gitee ssh-add ~/.ssh/id_ed25519_githubmacOS 可以把密鑰存入鑰匙串用ssh-add --apple-use-keychain參數(shù)Linux 桌面環(huán)境一般配合自啟動(dòng)腳本。Windows 的 Git Bash 里 agent 行為略有不同建議單獨(dú)確認(rèn)。這一步不做也能用只是每次 push 都要輸口令時(shí)間長(zhǎng)了容易煩最后干脆把口令去掉反而降低安全性。4. 倉(cāng)庫(kù)級(jí)身份與遠(yuǎn)程地址的正確寫(xiě)法4.1 遠(yuǎn)程地址到底該用別名還是原域名這是雙平臺(tái)配置里最容易出錯(cuò)的地方因?yàn)?remote 地址必須和 SSH config 里的Host對(duì)得上。如果你 config 里寫(xiě)的是Host gitee.com那么 remote 就用gitgitee.com:user/repo.git如果你寫(xiě)的是Host gitee-workremote 就必須用gitgitee-work:user/repo.git寫(xiě)成原域名反而匹配不到你指定的密鑰。我傾向于直接用原域名做 Host理由是平臺(tái)文檔、復(fù)制按鈕、同事分享的地址都是原域名形式不需要二次轉(zhuǎn)換減少出錯(cuò)機(jī)會(huì)。只有在需要多套身份時(shí)才用別名比如同一個(gè) GitHub 賬號(hào)既有個(gè)人倉(cāng)庫(kù)又有組織倉(cāng)庫(kù)但要求不同的提交郵箱這時(shí)候用別名區(qū)分會(huì)更清晰。修改已有倉(cāng)庫(kù)的 remote 地址用git remote set-urlgit remote set-url origin gitgitee.com:user/repo.git git remote -v改完一定要用git remote -v確認(rèn)別只看命令有沒(méi)有報(bào)錯(cuò)。我遇到過(guò)改了一個(gè) remote、另一個(gè)同名 remote 還在的情況push 的時(shí)候 Git 選了錯(cuò)的那個(gè)排查了半天。remote -v會(huì)把 fetch 和 push 兩個(gè)地址都列出來(lái)如果它們不一致說(shuō)明之前配過(guò)單獨(dú)的 push 地址需要一并處理。4.2 用局部配置覆蓋提交身份認(rèn)證解決的是能不能推身份解決的是推上去是誰(shuí)。全局配置只能有一個(gè)默認(rèn)身份所以每個(gè)倉(cāng)庫(kù)要單獨(dú)設(shè)置cd ~/projects/gitee/some-repo git config --local user.name 你的顯示名 git config --local user.email 你的 Gitee 綁定郵箱 cd ~/projects/github/another-repo git config --local user.name 你的顯示名 git config --local user.email 你的 GitHub 綁定郵箱郵箱必須和平臺(tái)賬號(hào)綁定的郵箱一致否則提交不會(huì)關(guān)聯(lián)到你的賬號(hào)。有些人用隱私郵箱比如 GitHub 提供的xxxusers.noreply.github.com形式這也是可以的但要確保平臺(tái)側(cè)開(kāi)啟了相應(yīng)選項(xiàng)。用錯(cuò)郵箱的后果不是提交失敗而是提交成功但不歸屬你這種靜默錯(cuò)誤最難發(fā)現(xiàn)。顯示名可以兩個(gè)平臺(tái)用同一個(gè)不影響歸屬判斷歸屬主要看郵箱。但如果你的工作身份和個(gè)人身份需要區(qū)分顯示名也可以不同比如工作倉(cāng)庫(kù)用真實(shí)姓名個(gè)人倉(cāng)庫(kù)用昵稱(chēng)。這些都屬于倉(cāng)庫(kù)級(jí)配置互不干擾。4.3 用 includeIf 按目錄自動(dòng)切換身份倉(cāng)庫(kù)多了之后每個(gè)都手動(dòng)git config --local太累而且容易漏。includeIf可以按目錄自動(dòng)加載配置一勞永逸。先在全局.gitconfig里加兩段[includeIf gitdir:~/projects/gitee/] path ~/.gitconfig-gitee [includeIf gitdir:~/projects/github/] path ~/.gitconfig-github然后分別建兩個(gè)文件。~/.gitconfig-gitee內(nèi)容[user] name 你的顯示名 email 你的 Gitee 綁定郵箱~/.gitconfig-github內(nèi)容類(lèi)似郵箱換成 GitHub 的。這樣放在對(duì)應(yīng)目錄下的倉(cāng)庫(kù)會(huì)自動(dòng)使用對(duì)應(yīng)身份新建倉(cāng)庫(kù)也不用再手動(dòng)設(shè)。這里有幾個(gè)細(xì)節(jié)要注意。gitdir:后面的路徑建議用絕對(duì)路徑用~時(shí)不同 Git 版本展開(kāi)行為可能不一致。路徑末尾的斜杠表示目錄前綴匹配加上它~/projects/gitee/下的所有子目錄都會(huì)命中。如果你只想匹配特定后綴可以用gitdir:~/projects/**/gitee-*這種通配寫(xiě)法但通配越復(fù)雜越容易出意外建議先從簡(jiǎn)單的前綴匹配開(kāi)始。驗(yàn)證是否生效進(jìn)入一個(gè)倉(cāng)庫(kù)執(zhí)行g(shù)it config user.email git config --show-origin user.email第二條會(huì)告訴你這個(gè)值來(lái)自哪個(gè)文件。如果顯示的是includeIf加載的那個(gè)文件說(shuō)明配置生效了。如果還是全局值檢查路徑拼寫(xiě)、斜杠、以及全局配置里includeIf段的位置——它應(yīng)該放在[user]段之后因?yàn)楹竺娴呐渲脮?huì)覆蓋前面的。4.4 克隆與存量倉(cāng)庫(kù)的改造步驟新克隆倉(cāng)庫(kù)時(shí)直接按目錄放好身份配置會(huì)自動(dòng)生效cd ~/projects/gitee git clone gitgitee.com:user/repo.git cd ~/projects/github git clone gitgithub.com:user/repo.git存量倉(cāng)庫(kù)的改造分三步移目錄、改 remote、驗(yàn)身份。移目錄用mv即可Git 倉(cāng)庫(kù)對(duì)路徑不敏感移完照樣能用。改 remote 用前面說(shuō)的set-url。驗(yàn)身份就是git config user.email加一次測(cè)試提交。測(cè)試提交建議用git commit --allow-empty -m test identity不產(chǎn)生實(shí)際文件改動(dòng)推上去看一眼作者信息對(duì)不對(duì)確認(rèn)沒(méi)問(wèn)題再刪除這條提交記錄。直接拿真實(shí)代碼試也行但萬(wàn)一身份錯(cuò)了還得改寫(xiě)歷史成本更高??仗峤贿@個(gè)小技巧在驗(yàn)證配置時(shí)很好用值得記住。5. HTTPS 憑據(jù)分流與混合使用場(chǎng)景5.1 credential.helper 的選擇與差異HTTPS 方式下Git 需要知道把賬號(hào)密碼存在哪、怎么取。這就是credential.helper的作用。常見(jiàn)取值有store、cache、manager幾類(lèi)。store把憑據(jù)明文存在文件里簡(jiǎn)單但不安全cache存在內(nèi)存里有過(guò)期時(shí)間manager是跨平臺(tái)的憑據(jù)管理器Windows 上常見(jiàn)安全性更好。查看當(dāng)前值git config --global credential.helper如果沒(méi)有輸出說(shuō)明沒(méi)配每次操作都會(huì)詢問(wèn)。設(shè)置成store的話憑據(jù)會(huì)寫(xiě)到用戶目錄下的.git-credentials文件格式是https://user:tokenhost。這個(gè)文件是明文的機(jī)器多人共用時(shí)要注意。我在個(gè)人開(kāi)發(fā)機(jī)上用store在共享環(huán)境里一律用cache或者系統(tǒng)的憑據(jù)管理器。需要強(qiáng)調(diào)的是現(xiàn)在兩個(gè)平臺(tái)基本都不建議用賬號(hào)密碼直接認(rèn)證而是用訪問(wèn)令牌。令牌有獨(dú)立的作用域和過(guò)期時(shí)間泄露了可以單獨(dú)撤銷(xiāo)比密碼安全。配置時(shí)把令牌當(dāng)密碼用即可但要注意令牌權(quán)限別開(kāi)太大只給倉(cāng)庫(kù)讀寫(xiě)就夠不要給賬號(hào)管理權(quán)限。5.2 按域名區(qū)分憑據(jù)條目的寫(xiě)法HTTPS 方式下兩個(gè)平臺(tái)的憑據(jù)是按域名存的。store模式下.git-credentials文件里會(huì)有兩行分別對(duì)應(yīng)兩個(gè)域名。正常情況下 Git 會(huì)根據(jù) remote 地址的域名自動(dòng)選對(duì)應(yīng)的條目不需要手動(dòng)干預(yù)。但有兩種情況會(huì)串號(hào)一是域名寫(xiě)錯(cuò)比如把gitee.com寫(xiě)成www.gitee.com匹配不上就會(huì)用錯(cuò)條目二是憑據(jù)助手緩存了錯(cuò)誤的值需要手動(dòng)清除。清除單個(gè)域名的緩存不同助手命令不同。store模式直接編輯文件刪對(duì)應(yīng)行cache模式用git credential-cache exit清空系統(tǒng)憑據(jù)管理器則要在系統(tǒng)設(shè)置里刪。清完之后下次操作會(huì)重新詢問(wèn)輸入正確的令牌即可。還有一種更精細(xì)的控制方式用credential.url.helper給特定域名單獨(dú)指定助手git config --global credential.https://gitee.com.helper store git config --global credential.https://github.com.helper store這種寫(xiě)法適合你想讓某個(gè)平臺(tái)用不同存儲(chǔ)策略的場(chǎng)景比如一個(gè)用內(nèi)存緩存、一個(gè)用持久存儲(chǔ)。配置項(xiàng)里的 URL 要帶協(xié)議匹配是按前綴來(lái)的。5.3 SSH 與 HTTPS 混用的注意事項(xiàng)同一個(gè)倉(cāng)庫(kù)里 SSH 和 HTTPS 混用是允許的比如 fetch 用 SSH、push 用 HTTPS但不建議這么做。混用會(huì)讓排查變復(fù)雜認(rèn)證失敗時(shí)你分不清是密鑰問(wèn)題還是令牌問(wèn)題日志里的錯(cuò)誤信息也不一樣。我的原則是一個(gè)倉(cāng)庫(kù)只用一種協(xié)議要換就整體換。如果確實(shí)需要混用比如公司網(wǎng)絡(luò)限制 22 端口只能用 HTTPS push那就把 remote 的 fetch 和 push 地址分開(kāi)設(shè)置git remote set-url origin gitgithub.com:user/repo.git git remote set-url --push origin https://github.com/user/repo.git git remote -vremote -v會(huì)顯示兩行不同地址這是正常的。但要記得HTTPS push 需要憑據(jù)SSH fetch 需要密鑰兩套配置都得對(duì)。換機(jī)器時(shí)也容易漏掉其中一套遷移前最好把git remote -v的輸出記下來(lái)。6. 常見(jiàn)問(wèn)題與排查速查6.1 Permission denied 的幾類(lèi)原因與定位順序Permission denied (publickey)是最高頻的報(bào)錯(cuò)原因至少有五類(lèi)。按下面的順序排查基本能覆蓋排查項(xiàng)檢查方法典型現(xiàn)象公鑰是否登記平臺(tái)設(shè)置頁(yè)對(duì)照公鑰指紋平臺(tái)側(cè)顯示未使用過(guò)的密鑰私鑰路徑是否正確ssh -vT githost看加載了哪個(gè)文件日志顯示嘗試了錯(cuò)誤的密鑰文件權(quán)限是否合規(guī)ls -l ~/.ssh權(quán)限過(guò)寬導(dǎo)致密鑰被忽略Host 是否匹配ssh -G githost看最終配置實(shí)際生效的配置與預(yù)期不符是否被其他規(guī)則攔截檢查url.*.insteadOf請(qǐng)求發(fā)到了意料之外的地址ssh -vT的-v會(huì)輸出詳細(xì)握手過(guò)程重點(diǎn)看Offering public key和Server accepts key這兩行。如果提供的密鑰根本不對(duì)說(shuō)明 config 匹配或路徑有問(wèn)題如果提供了正確的密鑰但服務(wù)器不接受說(shuō)明平臺(tái)側(cè)沒(méi)登記或者登記的不是這把。還有一個(gè)容易忽略的點(diǎn)有些網(wǎng)絡(luò)環(huán)境會(huì)攔截 22 端口表現(xiàn)也是連接超時(shí)或者拒絕但錯(cuò)誤信息不同。區(qū)分方法是看報(bào)錯(cuò)是Connection refused還是Permission denied前者是網(wǎng)絡(luò)層后者是認(rèn)證層。網(wǎng)絡(luò)層的問(wèn)題不在本文討論范圍但知道這個(gè)區(qū)分能讓你少走彎路。6.2 提交作者信息錯(cuò)亂的修復(fù)思路發(fā)現(xiàn)提交作者不對(duì)先確認(rèn)是哪個(gè)環(huán)節(jié)錯(cuò)了。用git log -1 --format%an %ae看最近一次提交的作者如果這里就錯(cuò)了說(shuō)明提交時(shí)的配置不對(duì)如果這里對(duì)但平臺(tái)上顯示不對(duì)說(shuō)明平臺(tái)沒(méi)匹配上郵箱。修復(fù)最近一次提交的作者用git commit --amend --author顯示名 正確郵箱 --no-edit這只改最近一次。如果錯(cuò)了好幾次需要交互式變基或者批量改寫(xiě)操作前一定先備份分支。改寫(xiě)歷史會(huì)影響已經(jīng)推送的提交團(tuán)隊(duì)協(xié)作的倉(cāng)庫(kù)要謹(jǐn)慎改完需要強(qiáng)制推送并且通知其他人重新拉取。個(gè)人倉(cāng)庫(kù)隨便改團(tuán)隊(duì)倉(cāng)庫(kù)建議先溝通。預(yù)防比修復(fù)重要。我現(xiàn)在的習(xí)慣是新建倉(cāng)庫(kù)后第一件事就是確認(rèn)git config user.email配合includeIf之后這一步基本可以省略但第一次在新目錄建倉(cāng)庫(kù)時(shí)還是會(huì)掃一眼。這個(gè)動(dòng)作花不了十秒鐘能省掉后面改寫(xiě)歷史的麻煩。6.3 遠(yuǎn)程地址寫(xiě)錯(cuò)與 remote 管理git remote -v是排查 remote 問(wèn)題的第一命令沒(méi)有之一。地址寫(xiě)錯(cuò)的表現(xiàn)通常是推到了錯(cuò)誤的倉(cāng)庫(kù)或者提示倉(cāng)庫(kù)不存在。常見(jiàn)錯(cuò)誤包括用戶名拼錯(cuò)、倉(cāng)庫(kù)名大小寫(xiě)不一致、協(xié)議和配置不匹配、多了或少了.git后綴。修改用set-url刪除用remove重命名用rename。一個(gè)倉(cāng)庫(kù)可以有多個(gè) remote比如origin指向自己的 forkupstream指向原倉(cāng)庫(kù)。多 remote 場(chǎng)景下 push 要指定名字git push origin main和git push upstream main去的是不同地方別順手就推。還有一個(gè)細(xì)節(jié)insteadOf重寫(xiě)規(guī)則會(huì)改變實(shí)際請(qǐng)求的地址但remote -v顯示的還是你寫(xiě)的地址。如果懷疑被重寫(xiě)用git config --get-regexp url\..*\.insteadof查一下有沒(méi)有相關(guān)規(guī)則。這個(gè)規(guī)則一般在遷移平臺(tái)或者統(tǒng)一協(xié)議時(shí)才會(huì)配平時(shí)不會(huì)有但接手別人機(jī)器時(shí)值得看一眼。6.4 換機(jī)遷移時(shí)哪些要帶走、哪些不能帶換機(jī)器時(shí).ssh目錄下的私鑰、公鑰、config、known_hosts 都可以帶走但要注意新機(jī)器的權(quán)限設(shè)置要重做一遍。.gitconfig和兩個(gè)條件配置文件也要帶走否則身份配置全丟。倉(cāng)庫(kù)目錄直接拷貝即可Git 倉(cāng)庫(kù)是自包含的。不能隨便帶的是憑據(jù)緩存文件。store模式下的.git-credentials里是明文令牌拷貝過(guò)程如果經(jīng)過(guò)不安全渠道等于泄露。建議到新機(jī)器重新登錄一次讓?xiě){據(jù)助手重新生成。令牌本身也要檢查有效期遷移前順手輪換一次是個(gè)好習(xí)慣。遷移后按這個(gè)順序驗(yàn)證ssh -T測(cè)兩個(gè)平臺(tái)、進(jìn)一個(gè)倉(cāng)庫(kù)看git config user.email、做一次空提交測(cè)試推送。三步都過(guò)說(shuō)明環(huán)境完整。少做一步可能在幾天后某個(gè) push 時(shí)才暴露問(wèn)題到時(shí)候還得回頭查。7. 實(shí)操心得與長(zhǎng)期維護(hù)建議7.1 我踩過(guò)的幾個(gè)坑你可以直接跳過(guò)第一個(gè)坑是密鑰覆蓋。早期我用ssh-keygen不加-f參數(shù)默認(rèn)生成id_rsa第二次生成時(shí)提示覆蓋我習(xí)慣性回車(chē)結(jié)果把第一次的密鑰覆蓋了另一個(gè)平臺(tái)瞬間失去訪問(wèn)權(quán)限。后來(lái)改成帶平臺(tái)后綴的命名再也沒(méi)出過(guò)這個(gè)問(wèn)題。這個(gè)習(xí)慣的成本幾乎為零收益是避免一次很難恢復(fù)的事故。第二個(gè)坑是IdentitiesOnly沒(méi)加。有段時(shí)間某個(gè)平臺(tái)總是間歇性認(rèn)證失敗重試幾次又能過(guò)。后來(lái)用ssh -vT看日志發(fā)現(xiàn) SSH 在挨個(gè)嘗試所有密鑰嘗試次數(shù)多了被服務(wù)器限流。加上IdentitiesOnly yes之后問(wèn)題消失。這個(gè)參數(shù)不會(huì)讓配置變得更好看但能讓行為變得確定。第三個(gè)坑是郵箱不一致。我在一個(gè)倉(cāng)庫(kù)里用了備用郵箱提交平臺(tái)沒(méi)關(guān)聯(lián)上貢獻(xiàn)統(tǒng)計(jì)少了一大塊。發(fā)現(xiàn)時(shí)已經(jīng)過(guò)去兩個(gè)月只能改寫(xiě)歷史。從那以后我在includeIf的配置文件里寫(xiě)死郵箱不再依賴記憶。第四個(gè)坑是權(quán)限。有次在 Linux 上把.ssh目錄權(quán)限改成了755結(jié)果 SSH 直接忽略 config 文件而且不報(bào)錯(cuò)只是行為不對(duì)。查了半天才發(fā)現(xiàn)是權(quán)限問(wèn)題?,F(xiàn)在我的檢查清單里權(quán)限是必查項(xiàng)。7.2 讓配置長(zhǎng)期穩(wěn)定的幾個(gè)習(xí)慣配置這件事配好只是開(kāi)始能不能長(zhǎng)期穩(wěn)定取決于習(xí)慣。我總結(jié)了幾條自己一直在用的新機(jī)器先跑一遍git config --list --show-origin確認(rèn)沒(méi)有意外的全局配置新建倉(cāng)庫(kù)先看user.email確認(rèn)條件配置生效改完 remote 一定用remote -v復(fù)核定期檢查令牌和密鑰的有效期別等到過(guò)期那天才發(fā)現(xiàn)。還有一條是文檔化。我會(huì)在~/.ssh/下放一個(gè)簡(jiǎn)單的說(shuō)明文件記錄每把密鑰對(duì)應(yīng)哪個(gè)平臺(tái)、什么時(shí)候生成的、公鑰指紋是什么。這個(gè)文件不含任何私密內(nèi)容只有索引信息。換機(jī)器或者幫別人排查時(shí)看一眼就能對(duì)上號(hào)。很多人覺(jué)得這是多余動(dòng)作但真出問(wèn)題時(shí)能快速確認(rèn)這把鑰匙是開(kāi)哪扇門(mén)的價(jià)值很高。最后說(shuō)一個(gè)觀念上的轉(zhuǎn)變。雙平臺(tái)配置的目標(biāo)不是配一次就永遠(yuǎn)不管而是讓日常操作不需要思考。密鑰分家、身份分層、目錄規(guī)劃這些動(dòng)作的本質(zhì)都是把需要記憶的事情變成環(huán)境的一部分。環(huán)境對(duì)了人就不會(huì)錯(cuò)靠人記住遲早會(huì)錯(cuò)。我現(xiàn)在的狀態(tài)是新建倉(cāng)庫(kù)直接克隆到對(duì)應(yīng)目錄提交、推送全程不需要想我現(xiàn)在在哪個(gè)平臺(tái)這套配置才算真正配好了。