
Git 這東西屬于“單干時覺得沒必要、一協作就原形畢露”的工具。你一個人 commit、push永遠碰不到分支管理和沖突解決但只要你跟別人一起開發一個項目哪怕只是兩個人改同一個倉庫用不了多久你就會發現分支、PR、沖突這三件事搞不透協作效率基本為零。這篇文章我準備用一次完整的 Git 協作模擬把分支管理、PR 流程和沖突解決這三座大山一次性講透。我會從環境準備開始帶你模擬兩個協作者在同一個倉庫里開發、合代碼、制造沖突、解決沖突的完整鏈路。全程實操向每一步都有命令、有輸出、有解釋不只是告訴你“怎么做”還會告訴你“為什么這么做”。不管你是剛裝好 Git 的大學生還是已經用了一段時間但遇到沖突只會慌的開發者這篇都能給你一套可以直接照搬的協作方案。1. 環境準備與倉庫初始化1.1 Git 安裝與全局配置先解決環境問題。Windows 用戶直接去 Git 官網下載 Git for Windows一路 Next 裝完。這里有兩個安裝環節容易踩坑一是安裝向導里有個“Adjusting your PATH environment”選項一定要選 “Git from the command line and also from 3rd-party software”不然后面在 PowerShell 或 CMD 里敲git會直接報 “無法將‘git’項識別為 cmdlet、函數、腳本文件或可運行程序的名稱”二是行尾轉換那里選默認的 “Checkout Windows-style, commit Unix-style line endings” 就行后面沖突跟它關系不大但保持默認最省事。macOS 用戶直接brew install gitLinux 用戶apt install git或yum install git。裝完后打開終端驗證一下git --version看到版本號就說明裝好了。接下來做全局配置這一步是很多新手最容易漏的但如果不配commit 記錄里會帶著一串亂碼用戶名PR 也沒法關聯到你的賬號git config --global user.name 你的名字 git config --global user.email 你的郵箱這里建議直接用 GitHub/GitLab 注冊郵箱因為平臺是靠郵箱關聯提交記錄的。再順手配一下默認分支名和常用別名git config --global init.defaultBranch main git config --global alias.lg log --oneline --graph --all --decorateinit.defaultBranch main是為了避免 GitHub 用main、本地還老默認master兩邊不一致的情況。alias.lg是我個人非常推薦的一個別名后面看分支圖譜幾乎是剛需一條命令就能看清整個倉庫的分叉情況。1.2 SSH 密鑰配置與遠程倉庫連接配置完身份之后下一步是把本地 Git 和遠程倉庫打通。現在 GitHub 和 GitLab 都不推薦用密碼 push 了最穩的方式是 SSH 密鑰。生成密鑰ssh-keygen -t ed25519 -C 你的郵箱一路回車就好。生成的公鑰在~/.ssh/id_ed25519.pub把它復制出來cat ~/.ssh/id_ed25519.pub然后去 GitHub 的 Settings - SSH and GPG keys 里 New SSH key粘貼保存。這個動作的本質是把你的公鑰存在服務器上之后每次 push 的時候Git 用私鑰簽名、服務器用公鑰驗簽建立了免密通道。它跟你登錄網銀用證書的原理是一樣的理解成“本地持有鑰匙服務器存鎖”就行。驗證是否通了ssh -T gitgithub.com出現Hi xxx! Youve successfully authenticated就代表配置完成。這一步非常關鍵——如果你后面 push 的時候頻繁輸密碼或者報Permission denied (publickey)回來檢查這一節。踩過太多次坑了十個新人里至少有三個卡在密鑰配置上。1.3 搭建協作模擬環境這篇文章的實戰目標是模擬兩個人協作開發所以我們先建一個遠程裸倉庫再分別從兩個“身份”的角度去操作。創建一個模擬遠程倉庫mkdir git-collab-demo cd git-collab-demo git init --bare--bare的意思是初始化一個沒有工作區的純倉庫只存 Git 歷史這就是遠程倉庫的標準形態。實際工作中你在 GitHub 上點 “New repository” 創建出來的倉庫底層就是這個東西。然后在本地克隆出兩個工作目錄模擬兩個開發者git clone gitgithub.com:你的用戶名/git-collab-demo.git alice git clone gitgithub.com:你的用戶名/git-collab-demo.git bob如果你用本地裸倉庫模擬就把 URL 替換成裸倉庫的本地路徑。alice和bob這兩個目錄就是兩個協作者的“電腦”后面所有沖突和 PR 就是在這兩個目錄之間產生的。這個模擬環境搭好之后后面的操作才能有真實的感知——不是一個人在單倉庫里自娛自樂而是真的有兩套工作區在同一個遠程倉庫上協同。2. 分支管理從主分支到功能分支2.1 為什么一定要用分支開發很多新手剛學 Git 的時候不太理解分支的意義覺得“我直接在主分支上改不就行了”。等你跟別人協作一次就知道問題了如果所有人都直接在main上開發那main上的代碼永遠是“半成品”可能你今天提交了一個沒寫完的功能別人拉下來跑都跑不起來。而且兩個人同時改動同一個文件動不動就沖突改出問題想回滾都不知道從哪里下手。分支的核心價值是給每個功能或任務建立一條獨立的開發線。你在自己的分支上可以隨便 commit、隨便折騰失敗了直接刪掉分支重來完全不干擾別人的工作功能開發完、測試通過之后再通過 PR 合并回主分支。這等于把“破壞性操作”隔離在主干之外main永遠保持穩定可發布的狀態。我見過一個比喻說得很好分支就像修路。你不能為了修一條支路就把主路全挖了。你得在旁邊臨時開一條輔道施工完了再接回主路。Git 的分支就是這個輔道。2.2 三種主流的分支工作流對比光知道“要開分支”還不夠還得知道分支怎么組織。目前業界主流的分支工作流有三種我做了個表直接對比它們的適用范圍工作流核心特征優勢適用場景Git Flow長期分支master/develop 短期分支feature/release/hotfix流程嚴謹版本發布可控有明確版本節奏的正式項目GitHub Flow只有 main feature 分支feature 完成后直接 PR 合并輕量、靈活適合持續交付互聯網產品、Web 應用GitLab Flow在 GitHub Flow 上增加環境分支pre-production/production兼顧環境和發布流程需要多環境部署的項目如果是自己學習或小型項目我強烈建議從 GitHub Flow 開始。它的規則極簡main永遠是穩定的任何功能開發都開一個新分支分支名用feature/功能名或fix/修復名做完 PR 合并后立刻刪除分支。規則越簡單越不容易亂。2.3 從零創建分支與合并分支現在我們實際操作一下。用alice這個目錄創建一個新功能分支cd alice git checkout -b feature/login-page這個命令等于兩步git branch feature/login-page創建分支git checkout feature/login-page切到你新建的分支上。Git 的分支本質上是一個指向某個 commit 的指針創建分支只是新增了一個指針成本極低這也是 Git 鼓勵“頻繁開分支”的根本原因。在這個分支上做一些開發比如寫一個登錄頁的骨架文件login.htmlecho h1Login Page/h1 login.html git add login.html git commit -m feat: add login page skeleton提交后推送到遠程git push -u origin feature/login-page-u參數的完整寫法是--set-upstream它的作用是讓本地分支和遠程分支建立關聯之后在這個分支上直接git push就不用再寫遠程名和分支名了。在main上同樣看一下分支狀態感受一下git lg的威力git checkout main git lg這時候git lg會展示兩條提交線main停在最初的初始提交上feature/login-page則往前走了一步。分支的本質圖景你從這里能直接感受到。3. 拉取請求PR的完整流程3.1 PR 到底是什么PRPull Request拉取請求這個詞國內也叫“合并請求”。它跟直接 push 到主分支最大的區別是它提供了一種“審查-討論-合并”的機制。PR 不是 Git 原生的功能而是 GitHub、GitLab 這些平臺提供的協作能力。它的邏輯是你開發完一個功能分支推送到遠程之后向項目維護者發起一個“請把我的改動拉進去”的請求維護者可以查看 diff、提評論、要求修改都通過了點一下 Merge 按鈕代碼合入主分支。非要用一句話總結的話PR 是代碼合并的“儀式化”流程。它逼著你在代碼進入主干之前先過一個檢查關卡。對個人開發者來說這個流程可能顯得冗余但對團隊協作來說它是質量保障的關鍵一環。3.2 Fork 工作流與分支工作流的區別說到 PR有兩個概念容易懵Fork 和分支。分支是在同一個倉庫內開辟開發線你和同事都有這個倉庫的寫權限直接在倉庫里開分支、推分支。Fork 則是在你自己的 GitHub 賬號下復制一份別人的倉庫你在自己的副本里改改完之后發起 PR 給原始倉庫。開源項目一般都走 Fork 工作流因為路人沒有原始倉庫的寫權限公司內部項目一般走分支工作流因為團隊成員本來就共享一個倉庫。兩種流的核心區別可以理解為“你對目標倉庫有沒有寫權限”。有權限就用分支 PR沒權限就 Fork PR。理解這個之后你看到開源項目里的Fork按鈕就不會不知道點不點了。3.3 一次完整 PR 的實操演示我們把feature/login-page分支的改動通過 PR 合入main。如果用的是 GitHub直接 push 分支后命令行會給出一個Create a pull request for feature/login-page的提示或者你去倉庫頁面點 “Compare pull request” 按鈕。PR 的標題和描述建議寫清楚改了什么、為什么改、測試過什么。比如這次可以寫“feat: add login page skeleton”描述里補充一句“新增登錄頁基礎結構后續接表單驗證”。實際工作中PR 描述寫得好不好直接影響 review 效率。描述里的關鍵信息包括背景為什么要做、改動清單核心變化、測試方式怎么驗證、截圖或演示鏈接。這里我推薦一個簡單模板## 背景 [為什么做這個改動] ## 改動 - [具體改動點1] - [具體改動點2] ## 測試 - [ ] 本地驗證通過 - [ ] 相關用例覆蓋 ## 關聯 Closes #123Reviewer 看到這樣的 PR10 分鐘就能完成審查反之一個只有兩三行標題、沒有描述、沒有測試說明的 PR審查人根本不敢點 Merge。3.4 用 GitHub CLI 管理 PR如果你不想每次都點網頁操作可以用 GitHub 官方的命令行工具gh。裝好之后先認證gh auth login然后創建 PR 就一句話gh pr create --title feat: add login page skeleton --body 新增登錄頁基礎結構查看 PR 列表、看 CI 狀態、甚至直接合并都可以在命令行里完成gh pr list gh pr status gh pr merge --squashgh pr merge有三種合并方式--merge普通 merge commit、--squash把分支上所有 commit 壓成一個、--rebase變基合并。這三個的區別我后面會細講它們是區分“會用 Git”和“對 Git 有理解”的分水嶺。3.5 代碼評審的實戰心得評審階段是最能體現團隊協作水平的地方。我自己 review 代碼的經驗是先看整體再看細節。整體包括——這個 PR 有沒有必要、改動范圍是不是合理、有沒有把不相關的東西夾帶進來細節包括——命名、邊界條件、異常處理、有沒有明顯的性能問題。寫評語也有技巧。不要只說“你這里寫得不對”要給修改建議不要一次性提 50 條意見把作者淹沒挑最核心的問題點出來。對作者來說收到 review 意見不要覺得挫敗那是別人在幫你守質量底線。我見過很多新人第一次收到 review 意見就開始防御性解釋本質上沒必要——代碼評審討論的是代碼不是你這個人把心態擺正這輪流程你才能走順。4. 沖突解決從根源到實操4.1 沖突是怎么產生的沖突是 Git 協作中躲不開的話題。要說清楚怎么解決得先說清楚沖突為什么會發生。Git 合并代碼的基本單位是行。當 Git 合并兩個分支時它會自動嘗試把雙方的改動拼接起來如果兩個分支改的是不同的文件或者改的是同一文件的不同位置Git 可以直接自動合并完全不需要你參與。只有當兩個分支修改了同一個文件的同一個區域時Git 不知道到底該聽誰的這時候就會報沖突。一句話總結沖突的本質是“兩個人改了同一行代碼Git 不知道以誰為準”。打個比方你和室友合租你們倆對客廳裝修各有一份計劃。你打算把客廳刷成白色室友打算刷成灰色這個改動落在同一面墻上你總不能指望別人替你們做決定吧。Git 也是一樣它把決定權交給你只是把兩種改動都給你標了出來。4.2 手動沖突解決全流程演示現在我們就模擬一次真實的沖突場景。場景是alice和bob同時修改同一個文件的同一行。先讓alice基于main創建一個分支并修改config.txtcd alice git checkout -b feature/alice-config echo color blue config.txt git add config.txt git commit -m config: set color to blue git push -u origin feature/alice-config然后讓bob也基于main創建分支同樣修改config.txt的同一行cd bob git checkout -b feature/bob-config echo color red config.txt git add config.txt git commit -m config: set color to red git push -u origin feature/bob-config先把alice的分支通過 PR 合入maincd alice git checkout main git pull origin main git merge feature/alice-config git push origin main這時候main上config.txt的內容是color blue。接下來bob嘗試把分支合入main沖突來了cd bob git checkout main git pull origin main git merge feature/bob-config這時候 Git 會提示Auto-merging config.txt CONFLICT (content): Merge conflict in config.txt Automatic merge failed; fix conflicts and then commit the result.打開config.txt你會看到沖突標記 HEAD color blue color red feature/bob-config這個標記的含義很明確 HEAD到之間是你當前分支main的內容到 feature/bob-config之間是對方分支的內容。解決沖突就是把兩段內容決定成一段然后刪掉標記。假設你決定保留兩行配置color blue color red把文件里的沖突標記和其中一方內容清除后執行git add config.txt git commit -m merge: resolve conflict in config.txt git push origin main沖突解決完畢。整個過程不難難的是很多人第一次看到就懵了。記住一件事看到沖突標記先別慌——Git 不會吞掉你的代碼它只是把兩種意見原封不動擺在那里等你拍板。4.3 merge 沖突與 rebase 沖突的差異上面演示的是git merge的沖突。但日常協作中還有一種更常見的沖突來源——git rebase。Merge 和 rebase 達成的是同樣的目標把兩個分支的改動整合到一起。但路徑完全不同merge 是“創造一個合并提交把兩條開發線匯到一起”rebase 是“把我這分支的提交取下來在目標分支最新的提交上重新放一遍”。打個比方merge 像兩個人從兩個方向修路修到中間匯合rebase 像你把自己的那段進度搬到別人的進度前面重新施工。Rebase 的沖突解決流程和 merge 幾乎一樣標記格式也一樣唯一區別是merge 解決完沖突只需 commit 一次而 rebase 解決完每個沖突都要git add然后git rebase --continue一步步走完所有提交。舉個例子。你在feature分支上有兩個提交main分支在你開發期間也有新提交你執行git rebase main時如果兩個提交都碰到沖突就得連續解決兩次。每次解決完git add 沖突文件 git rebase --continue如果中途想放棄直接git rebase --abort回到 rebase 之前的狀態非常安全。那 merge 和 rebase 到底選哪個我的建議是合并功能分支優先用 merge它保留的提交歷史更真實保持主線整潔用 rebase但只 rebase 自己還沒推送到遠程的提交。永遠不要 rebase 一個已經推到遠程并被別人拉取過的分支——那會產生重復提交把所有人的歷史攪成一鍋粥。4.4 三種合并模式的取舍前面提到的 PR 合并有三種模式我詳細拆解一下。普通 merge commitGit 會把 feature 分支的提交歷史和 main 的提交歷史合并到一起保留兩條線的分叉和匯合點。好處是完整保留開發過程的“真實記錄”壞處是提交歷史會有一堆分叉和合流時間久了看起來比較亂。Squash and merge把 feature 分支上的所有提交壓縮成一個合入 main。好處是 main 的提交歷史非常干凈每個功能只占一個提交壞處是 feature 分支上細粒度的開發記錄全部丟失。適合“功能開發過程中的提交很碎、沒人關心過程細節”的場景。Rebase and merge先把 feature 分支變基到最新 main再用 fast-forward 方式合并。好處是歷史線呈線性沒有分叉壞處是變基會導致 commit hash 變化對一個已經公開的分支有影響。這三個模式的選擇本質上是在回答一個問題你希望 main 的歷史長什么樣我個人的項目習慣是個人開發用 squash團隊大項目用 merge commit需要嚴格線性歷史的項目用 rebase。4.5 沖突預防的四個技巧寫完沖突解決我再分享四個從源頭減少沖突的實操技巧這些是我實際項目中驗證過有效的策略。第一任務拆細、分支開早。一個分支做一件事一個 PR 改動盡量控制在幾百行以內。分支存活時間越短、改動的文件越少沖突概率天然就越低。第二勤拉遠程更新。開發過程中經常git fetch origin和git rebase origin/main或git merge origin/main別等所有代碼寫完再一次性合入把問題分散在過程中消化。第三模塊化代碼結構。兩個人盡量不同時修改同一個文件如果確實需要改同一個模塊先溝通誰負責哪一塊也能減少一半以上的沖突。第四讓 CI 幫你跑檢查。很多沖突不是 Git 層面的內容沖突而是語義沖突——兩人改了同一個函數Git 能自動合并但跑起來是壞的。這類沖突最危險所以一定要有自動化測試在 PR 合并前跑一遍。5. 故障恢復與日常排查5.1 誤刪分支與誤改代碼的恢復分支和沖突都玩熟了之后還有一個能力非常關鍵——出了事能把倉庫恢復回來。Git 最強大的地方不是它不會出錯而是它幾乎把所有操作都記錄下來了。你要學會的是出問題時怎么找回。誤刪分支是高頻事故。比如你刪了feature/login-page分支后來發現里面的一個提交還沒合入。別慌提交對象在 Git 里還有引用git reflog這個命令會列出 HEAD 的每一次變動記錄包括那些“并不存在于任何分支”上的操作。找到刪除分支前的那個 commit hash直接基于它新建分支git checkout -b feature/login-page-restored commit-hash同理如果你發現某個改動把代碼改壞了想回到之前的狀態git log --oneline git revert 壞提交的hashgit revert會生成一個新的反向提交把壞改動取消掉但不會改寫歷史。它跟git reset的區別在于reset 是“回去”且修改歷史revert 是“撤銷”且保留歷史。在團隊協作中請永遠優先考慮 revert因為 reset 會改變提交 hash一旦別人已經基于舊 hash 做了操作reset 就會導致歷史錯亂。5.2 stash 與臨時代碼處理日常開發還會碰到一種情況你正在feature分支開發一半突然需要切到main修個緊急 bug但現在的改動還不想提交。這時候git stash就是救命工具。git stash push -m wip: login page debug git checkout main # 修完 bug 回來 git checkout feature git stash popgit stash的邏輯是把工作區還沒提交的改動暫時存到一個獨立區域讓你可以干凈地切換分支。git stash list可以查看所有暫存git stash pop會把最近的暫存恢復到工作區并刪除記錄git stash apply則可以保留暫存記錄適合同一個臨時改動要應用到多個分支的場景。我用 stash 的經驗是給 stash 加個備注-m非常關鍵。如果你不加備注過幾天你會看到一長串沒有說明的 stash 列表完全想不起來哪個是哪個。加了備注一目了然。5.3 常見問題排查速查表最后分享一張我自己整理的問題排查速查表都是高頻問題問題現場核心原因解決方案提示not a git repository當前目錄不是 Git 倉庫git init初始化或確認進入了正確目錄push 時提示rejected遠程有本地沒有的新提交先git pull或git fetchgit rebase提示failed to push some refs遠程分支領先于本地git pull --rebase后再 pushPermission denied (publickey)SSH 密鑰未配置或不匹配檢查密鑰文件、重新添加公鑰提示fatal: refusing to merge unrelated histories合并的兩個分支沒有共同歷史確實要合并則加--allow-unrelated-histories提交后想改提交信息提交信息寫錯了或漏東西git commit --amend修改最近一次提交想撤銷某個文件的修改工作區文件改亂了git checkout -- 文件名恢復不記得之前做了什么操作操作歷史需要回溯git reflog查看完整 HEAD 變動這里面refusing to merge unrelated histories特別容易在新手把本地倉庫和遠程倉庫關聯時碰到原因是本地初始化和遠程初始化產生了兩個“祖先是空”的倉庫Git 拒絕把它們強行嫁接到一起。如果你確定要合并加--allow-unrelated-histories就好。5.4 分支清理與倉庫衛生協作時間長了遠程倉庫會堆積大量已經合并過的廢棄分支。保持倉庫整潔也是團隊協作里容易被忽略但很重要的一環。合并完 PR 后及時刪除遠程分支git push origin --delete feature/login-page本地分支同步清理git branch -d feature/login-page本地還有一批已經被遠程刪除但本地還存在的分支可以用這個命令一鍵清理git fetch --prune--prune參數的作用是清理本地緩存的“遠程已經不存在的分支引用”。維護好分支衛生能讓git branch -a的輸出干凈很多團隊里每個人看到的分支列表都是“正在進行的工作”而不是“歷史遺留的亂葬崗”。我個人在操作中的體會是Git 的很多命令就像停車場里的車車多了確實需要規劃和管理。你花三分鐘養成做一次清理的習慣省下來的遠不止三分鐘——找分支、看錯分支、合并錯內容、誤以為改動丟了這些坑我都踩過基本都是“倉庫不整潔”連帶出來的。最后再分享一個配合這個模擬環境的小練習把文中的alice和bob兩個人操作完整執行一遍然后在真實的 GitHub 或 GitLab 上走一次 PR 流程你會發現理論上的“會”和實際上的“熟”之間差了整整一段手動流程的距離。分支管理、PR 協作、沖突解決這三件事真的只有親手撞過一次墻才能變成你自己的東西。