1、分支的概念和使用
1.1、什么是分支?
分支(Branch)是在版本控制中非常重要的概念。幾乎所有版本控制系統都支持某種形式的分支。在 Git 中,分支是 Git 強大功能之一,它允許我們從主開發線分離出來,在不影響主分支的情況下并行地進行開發 (Git 分支管理 | 菜鳥教程)。你可以把分支想象成代碼開發的平行宇宙:在一個分支上進行的修改,不會立即出現在另一個分支上。開發者可以利用分支來嘗試新功能、修復 Bug 或進行實驗,而無需擔心弄亂主分支(通常是用于發布的穩定分支)。完成工作后,再把分支的改動合并回主分支,這樣主分支就得到了更新,而整個過程主分支始終保持可用和穩定。
簡而言之,使用分支可以讓團隊并行開發成為可能:每個人或每個新功能都在自己的分支上進行,互不干擾。等到需要整合時,再將分支合并。下面我們將介紹 Git 中有關分支操作的常用命令:創建分支、切換分支以及合并分支。
1.2、創建和切換分支(git branch, git checkout)
在 Git 中,新建分支使用命令 git branch
,切換分支使用命令 git checkout
(在較新的 Git 版本中,也可以使用 git switch
切換分支)。我們先來看如何創建一個分支:
-
git branch <分支名>
:創建一個新的分支。此命令將在當前所在提交的基礎上創建出一個分支指針。注意,僅執行git branch
命令不會切換到新分支,只是新建了一個分支。
例如,如果我們當前在主分支 main
上,希望創建一個新分支來開發登陸功能,可以執行:
git branch login-feature
此操作會在當前提交的基礎上建立一個名為 login-feature
的新分支。但是此時我們仍停留在 main
分支上。通過 git branch
不帶參數可以列出所有本地分支,*
會標識當前所在的分支。
-
git checkout <分支名>
:切換到指定的分支。切換分支會更改工作區的文件內容,以匹配該分支最近一次提交的狀態。執行切換前,確保當前工作區沒有未提交的修改,否則 Git 會拒絕切換(或者要求先 stash 暫存修改)以防止未提交的更改被覆蓋。
繼續上面的例子,我們創建了 login-feature
分支后,可以切換過去:
git checkout login-feature
執行后,Git 會提示已經切換到 login-feature
分支。現在開始的所有提交都會在 login-feature
分支上,而對 main
分支沒有影響。
一個常用的快捷方式是將創建和切換合為一步完成:git checkout -b <分支名>
。例如:
git checkout -b login-feature
如果 login-feature
不存在,這條命令會創建該分支并馬上切換過去,相當于依次執行了 git branch
和 git checkout
。這樣更方便一些。
成功切換到新分支后,你可以像往常一樣編輯代碼、添加和提交。所有這些提交將屬于 login-feature
分支,與主分支main
的提交歷史分離開。你可以隨時使用 git checkout main
切換回主分支,如果此時打開項目文件,會發現那些在 login-feature
分支上做的改動不見了(因為此刻看到的是主分支上的內容)。別擔心,這些改動仍然安全地保存在 login-feature
分支,只是當前未合并到主分支而已。
小貼士:在多人協作中,通常每個功能或修復都會新建一個分支進行開發,分支命名應簡潔明了,如
feature/login-ui
或bugfix/issue-101
等。主分支一般用來保存已經穩定并準備發布的代碼。
1.3、合并分支(git merge)
當一個分支上的開發告一段落,需要將其成果合并回主分支(或其他分支)時,就可以使用 git merge
命令。git merge
用于將另一條分支的修改合并到當前分支上。
最常見的場景是將功能分支合并回主分支。例如,我們在 login-feature
分支上完成了登陸功能的開發和測試,現在希望把它合并到 main
。步驟如下:
-
首先,切換到目標分支(即我們希望將改動合并到的分支)。這里是主分支
main
:git checkout main
-
然后執行合并命令,將功能分支合并過來:
git merge login-feature
這條命令表示:“把分支
login-feature
合并到當前分支(main)”。
執行 git merge
后,可能出現兩種情況:
-
快速前進(Fast-forward)合并: 如果主分支在功能開發期間沒有新的提交,那么主分支實際上停留在功能分支的起點上。在這種情況下,Git 不需要創建新的合并提交,而只會簡單地將主分支指向功能分支的最新提交。這種合并被稱為 “快進”,表現出來就是主分支的歷史直接向前推進,包含了功能分支的提交記錄。
-
非快進合并: 如果主分支在功能分支開發期間有自己的新提交,那么兩個分支的歷史出現了分叉。Git 會進行一次“三方合并”(three-way merge),自動創建一個新的合并提交來把分叉的歷史合并起來。合并提交包含兩個父提交,一個來自主分支原來的末尾,一個來自功能分支的末尾。合并提交的默認說明一般是“Merge branch 'login-feature' into main”。
在大多數情況下,如果沒有沖突,Git 會自動完成以上兩類合并。合并后,可以使用 git log --graph --oneline
來查看歷史,分支的提交記錄現在都出現在主分支的歷史中了。完成合并后,功能分支的代碼已經融合進主分支,通常我們可以選擇刪除那個已完成的功能分支以保持倉庫整潔。刪除本地分支的命令是:
git branch -d login-feature
-d
選項表示刪除分支(該操作不會影響已經合并到的內容)。如果分支尚未合并就嘗試刪除,Git 會發出警告并拒絕刪除,以免丟失未合并的修改。若確定要強行刪除,可以用 -D
(不推薦輕易使用)。
注意:合并前請確保將待合并的分支最新內容拉取下來(如果是多人協作且遠程有更新的話)。比如在合并遠程分支時,先
git pull origin login-feature
更新本地的login-feature
,再切換到 main 合并。這樣可以減少合并沖突的概率。
2、合并沖突及解決方法
當兩個分支都有各自的提交,并且修改了同一個文件的同一部分時,Git 在合并它們時就會遇到合并沖突(merge conflict)。簡單來說,Git 無法自動判斷以哪個分支的修改為準,需要人工介入決策。沖突最常發生于多人修改了相同代碼的情形,或自己在不同分支對同一文件做了不同改動。
**沖突發生時的現象:**執行 git merge
后,終端會輸出類似“Automatic merge failed; fix conflicts and commit the result.”的信息。這表示自動合并未能完成,需要手動解決沖突。此時,可以運行 git status
查看哪些文件存在沖突,輸出中會列出沖突文件并標記為“both modified”。
解決合并沖突的一般步驟:
-
定位沖突區域: 用文本編輯器打開存在沖突的文件。你會看到特殊的沖突標記。例如,假設
config.txt
文件在兩個分支有沖突,打開后可能出現這樣的內容:<<<<<<< HEAD timeout=30 ======= timeout=60 >>>>>>> login-feature
上述標記表示:
HEAD
(當前分支,即 main 分支)的內容是timeout=30
,而要合并進來的login-feature
分支的內容是timeout=60
。沖突區域被<<<<<<<
、=======
、>>>>>>>
分隔成兩部分,開發者需要決定哪一側(或如何合并兩者)才是正確的。 -
手動合并修改: 編輯沖突標記所在位置,根據需要保留或修改代碼。例如,如果確定以
login-feature
分支的設置為準,就把timeout=60
保留下來,刪去其他沖突標記。如果兩側的修改都需要,可以手動合并成一個新的內容。總之,要編輯成我們想要的最終結果,并刪除所有<<<<<<
,======
,>>>>>>
標記,使文件恢復正常的代碼格式。 -
標記沖突已解決并暫存: 修改完所有沖突文件后,保存文件并退出編輯器。然后使用
git add <文件>
將剛剛解決沖突的文件添加到暫存區。標記為已解決意味著告訴 Git:“這文件的沖突我已經處理好了,可以納入下一次提交”。 -
完成合并提交: 當所有沖突都解決并
git add
后,再次執行git status
檢查確認沒有剩余未解決的沖突。接下來,運行git commit
提交合并結果。值得注意的是,如果當前正處于合并過程,Git 在提交時會自動帶上合并的默認提交信息(也可以自行編輯提交說明)。提交成功后,合并沖突就算解決完成,倉庫歷史中會出現這次合并提交。
如果在處理沖突過程中發現合并的方向有誤或者想放棄此次合并,可以使用:
git merge --abort
該命令會中止合并操作,將分支恢復到合并開始前的狀態,就像從未執行過合并一樣。然后你可以重新嘗試合并或者做其他處理。
小提示:合并沖突雖然聽起來可怕,但有些圖形化工具可以幫助解決。例如,使用
git mergetool
可以調起圖形化沖突解決工具(如 KDiff3、P4Merge 等)對比沖突雙方的差異,輔助你選擇保留方案。另外,在團隊協作中,減少沖突的最好辦法是勤奮地溝通和同步:經常git pull
同步他人修改,及時分享自己的改動。當沖突真的發生時,也盡量和相關開發者商量確認最終修改方案。
3、使用 git stash 暫存變更
在日常開發中,有時我們會遇到這樣一種情況:正在一個分支上開發新功能,代碼寫到一半,突然接到任務需要立即切換到另一分支去修復緊急問題。但當前分支的修改尚未完成也不適合提交,這時該怎么辦?直接切換分支會被 Git 拒絕(因為有未提交修改),把不完整的工作硬提交顯然也不妥當。git stash 就是為了解決這種場景而設計的。
git stash
命令可以將當前工作目錄和暫存區的修改暫時儲藏起來,恢復到干凈狀態,好讓你切換到別的分支去處理其他事務。等需要恢復時,再將暫存的修改取出,繼續先前的工作。git stash
類似于一個堆棧(stack),可以儲存多組未完成的修改。
常用的 stash 操作為:
-
保存當前修改: 在有未提交改動的情況下運行:
git stash
這會把所有未提交的修改(包含已經
git add
暫存的和尚未暫存的)保存到一個匿名的儲藏當中。Git 會輸出類似 “Saved working directory and index state WIP on branchName...” 的信息,并將工作區恢復到最近一次提交的狀態。你也可以加說明信息,例如git stash save "message"
,但較新的 Git 版本中直接git stash
也可以自動保存。 -
查看暫存列表: 可以運行:
git stash list
來查看所有儲藏的記錄。最新的儲藏在最上面,每條記錄都有一個索引(如
stash@{0}
)和描述(包含當時所在的分支名和提交信息的摘要)。 -
恢復暫存的修改: 當你要繼續之前的工作時,在相應的分支上運行:
git stash pop
這條命令會將最近一次保存的修改取出應用到當前工作目錄,同時從儲藏列表中移除該記錄。假如你想應用但保留儲藏記錄,可以使用
git stash apply stash@{n}
(其中n
是編號)來應用某個特定儲藏但不刪除它。
舉個例子:你在 feature-X
分支上開發,寫了一半代碼。這時需要切換到 main
分支修復緊急 Bug。你可以執行 git stash
將 feature-X
的修改暫存起來,然后 git checkout main
切換分支(現在不會有未提交內容的阻礙)。修復完 Bug 并提交推送后,回到 feature-X
分支,用 git stash pop
將之前的工作恢復出來,繼續完成剩下的開發。整個過程就像把工作“按暫停”,處理完其他事情后再“恢復播放”。
git stash
非常適合處理工作中斷、上下文切換的場景。需要注意的是,stash 默認會儲存未提交的所有內容(包括已暫存和未暫存的部分)。如果你只想暫存尚未暫存的修改而保留已暫存部分,可以使用 git stash -k
(keep index)。另外,git stash
也能暫存新建但未追蹤的文件(加 -u
參數),不過初學者一開始用默認行為即可。
最后,當某個儲藏的修改不再需要時,可以用 git stash drop stash@{n}
刪除,或者 git stash clear
清空所有暫存記錄。合理地使用 stash,可以幫助你保持倉庫提交歷史的整潔,不會因為臨時切換任務而到處留下零碎的“WIP”提交。
4、git reset 與 git revert 的區別
在使用 Git 的過程中,難免會遇到需要“撤銷”更改的時候。Git 提供了多個命令來撤銷或回退提交,其中最常見的是 git reset
和 git revert
。它們都能達到“讓項目回到之前狀態”的目的,但原理和使用場景有所不同。理解兩者區別有助于我們在不同情況下選擇正確的方法去撤銷修改。
4.1、git reset —— 重置提交歷史
git reset
通常用于“回退”本地倉庫的HEAD到某個舊的提交。通過 reset,我們可以舍棄最近的一些提交,使倉庫退回到指定的歷史節點。根據參數不同,git reset
既可以影響提交歷史,也可以影響暫存區和工作區。
git reset [模式] <提交>
其中常用的模式有三種:
-
--mixed
(默認模式):重置 HEAD 到指定提交,同時保留工作區改動但清除暫存區。簡言之,就是把選定提交之后的修改全部撤出提交歷史,放回工作區未暫存的狀態。 -
--soft
:更溫和的重置,只重置 HEAD 提交,暫存區和工作區都保留這些改動。也就是說撤銷最近的提交,但改動仍然保持在暫存區,幾乎相當于把提交“取消”回暫存狀態。 -
--hard
:強制重置,提交歷史、暫存區和工作區都同步到指定提交的狀態。所有在那個提交之后的改動都會被徹底丟棄,從當前目錄中消失(無法通過 Git 恢復)。
一個常見的用法是撤銷最近一次提交。例如你發現剛才的提交有問題,不想要了,可以使用:
git reset HEAD~1
這條命令將倉庫重置到上一個提交(即舍棄了最新提交)。默認模式下(mixed),最新提交的更改內容會留在工作區,這樣你可以修改后重新提交,或者也可以放棄掉。相當于“退一步,但保留現場改動,允許你修正后再提交”。
如果你完全確定最近的提交是錯誤的且不需要其中的改動,可以用:
git reset --hard HEAD~1
警告:--hard
非常危險,它會抹掉工作區中自上一個提交以來的所有改動!除非你在其他地方還有這些改動的備份,否則此操作不可逆。因此務必謹慎使用。
git reset
更多地被視為一種本地操作,設計用于調整尚未公開(push)的歷史。例如,你提交了一些臨時代碼想回退,或者想將上一個 commit 拆分/合并,這些都可以通過 reset 在本地實現,然后重新整理 commit,再推送。但是如果你已經把提交推送到遠程倉庫,其他人也基于它展開了工作,貿然 reset 自己的分支并強制推送(force push)會導致團隊其他人的倉庫歷史不一致。因此,git reset
一般不應用于已發布到公用倉庫的提交。
除了回退整個提交,git reset
還可以用于取消暫存(unstage)。比如不小心 git add
了不想提交的文件,可以用 git reset HEAD <文件>
將其從暫存區移除,回到未暫存狀態。
4.2、git revert —— 還原提交
與 reset 不同,git revert
是通過創建一個新的提交來抵消某個歷史提交的影響,從而達到“撤銷更改”的目的。git revert <提交>
會根據指定的提交,產生一個內容相反的新提交,應用這個新提交后,倉庫狀態看起來就像撤回了指定的提交一樣。但是,原先的提交記錄依然保留在歷史中,只是后面附加了一個“反向提交”來消除它的效果。
git revert
常用于已經推送到遠程倉庫并分享給他人的提交的撤銷。因為 revert 不會重寫歷史,它是往歷史里添加記錄,所以對團隊其他成員不會產生破壞性的影響。實際上,revert 后大家同步代碼時,會拿到一個新的提交,該提交做了撤銷更改的操作。
舉例來說,假如在主分支有一個錯誤的提交 C
已經推送,現在想撤銷它。如果用 reset,我們必須強制修改歷史,會給協作者帶來困擾;但使用 revert:
git revert <提交C的哈希>
Git 會自動創建一個新提交 C'
(內容為將提交 C 引入的改動反向修改回去),提交消息會注明它是一次 revert。例如 “Revert "添加XX功能"”。推送這個新提交后,其他人拉取更新,他們的倉庫會保留提交 C
和 C'
兩條記錄,但最終文件內容與 C
未發生前一致了。
git revert
的語法通常是指定具體的提交哈希,也可以使用諸如 HEAD
、HEAD~2
這類引用來表示相對位置。還可以連續 revert 多個提交(需要逐個執行或使用...
范圍)。需要注意,如果要 revert 的提交不是最新的,或者涉及文件改動與后續提交有重疊,可能也會出現沖突,處理方式和合并沖突類似:修改沖突文件然后繼續 git revert --continue
完成操作(revert 一個提交序列的情況)。
小結比較:git revert
和 git reset
的根本區別在于是否保留歷史。git revert
是用一次新的 commit 來回滾之前的 commit,而 git reset
是直接刪除指定的 commit 。換句話說:
-
使用
git reset
,倉庫會“丟棄”某段歷史,就好像那些提交從未發生過。而使用git revert
,倉庫歷史會完整保留每一次改變,只是附加記錄某次改動被撤銷了。 -
git reset
改變了當前分支的 commit 鏈(HEAD 向后移動),而git revert
則讓 HEAD 繼續前進,只是新增的那個提交的內容與被還原的提交相反,從效果上抵消了它 。 -
從協作角度,reset 要求所有協作者都配合修改歷史(否則會出現分歧),而 revert 則是一個正常的提交,其他人只需拉取即可。因此,撤銷公開發布的提交建議使用
git revert
;僅在本地調整尚未發布的提交時可以使用git reset
。
使用場景建議:
-
當你提交后立刻發現錯誤,且該提交尚未推送,想徹底抹除這次錯誤提交,可以考慮
git reset
將 HEAD 回退(或者用git commit --amend
更正提交,這也是改歷史的方法之一)。但如果該提交已經分享出去,不要用 reset 撤銷。 -
當某次提交已經推送并被他人拉取,如果后來證明需要撤銷,就用
git revert
來安全地回滾。這樣所有人的歷史都會保持一致,只是多了一條修正記錄。 -
如果需要清除多個無用的提交(比如試驗性質的提交),而這些提交尚未推送,可以用
git reset
一次性回退。不過Git還有更高級的工具如交互式 rebase 可以用于整理多個提交(見下一節)。
總之,謹記:不要在公共倉庫的分支上使用
git reset --hard
等破壞性命令;撤銷公開提交請選擇git revert
。兩者各有用途:reset 修剪歷史,revert 保持歷史連續性地進行撤銷。
5、使用 git rebase 優化提交歷史
在多人協作和長期開發的項目中,提交歷史很容易變得復雜。頻繁的合并會產生很多分支交叉點和合并提交,而且每個人的提交粒度和風格也不盡相同。Git 提供的 git rebase
命令可以在一定程度上優化和整理提交歷史,使之更加線性和簡潔。不過,rebase 修改歷史的特性也需要我們小心使用。
5.1、變基(Rebase)簡介
git rebase
的直譯是“變基”。它的作用是改變提交的基底。具體來說,就是將一系列提交“剪下來”,然后重新應用(Replay)在另一位置。結果就是提交歷史發生了重排或重構。在實際操作中,rebase 常用于兩種情況:
-
在分支合并前,更新分支以基于最新的主分支: 假設我們有一個功能分支
feature
從主分支main
分出。一段時間后,main 上有了新的提交,而 feature 也有自己的提交。這時,我們可以在將 feature 合并回 main 之前,先切到 feature 分支執行git rebase main
。這會把 feature 分支上的提交轉移到 main 分支最新提交之后,仿佛 feature 是從當前最新的 main 開始開發的一樣。這樣處理后,當我們再合并 feature 到 main 時,可以避免產生額外的合并提交,使歷史呈現為一條直線。例如:最初 main 有 A 提交,feature 分支從 A 分出做了提交 B;期間 main 上又有了提交 C。此時 feature 分支執行
git rebase main
后,feature 上的 B 提交會被移到 C 的后面,形成一個新的提交 B'。現在 main 的歷史是 A - C,feature 的歷史是 A - C - B'。隨后如果 fast-forward 合并,主分支將線性包含 A - C - B',看起來就像 B'是在 C 之后提交的一樣,消除了分叉記錄。Rebase 后由于提交被重新應用,其 SHA-1 哈希值會發生變化(B 變成 B'),所以這個過程重寫了分支的歷史。Git 會逐個應用 feature 分支的每個 commit,因此如果這些提交與 main 的更改有沖突,會暫停并要求解決沖突(類似前面合并沖突的過程,但命令變為
git rebase --continue
繼續下一個,或者git rebase --abort
放棄變基)。 -
整理本地的多次碎片提交: 在開發一個功能時,可能我們會進行很多次臨時的提交(比如調試信息、實驗嘗試等)。在提交到主分支前,團隊往往希望這些歷史被整理得整潔一些,例如合并相關的改動、編輯規范提交消息等。
git rebase -i
(交互式變基)可以幫助實現這一點。通過交互式 rebase,我們可以選擇**壓縮(squash)**多個提交為一個、修改(edit)某些提交的內容或消息、甚至改變提交順序等。例如,假設我們在當前分支有最近的5個提交需要整理,只需執行:
git rebase -i HEAD~5
Git 會打開一個文本界面,列出這5個提交以及可選的操作(pick, squash, edit 等)。根據需要編輯保存后,Git 就會按照新的指令重新依次應用這5個提交(期間如果有沖突需解決)。整理完成后,分支歷史就被改寫成了我們想要的樣子。常用的操作包括將多次“小提交”合并成一次有意義的大提交(squash),或修改提交信息(reword/edit)以更清晰地描述改動。
rebase vs merge: Rebase 和 Merge 是實現分支集成的兩種方式,各有優劣。Merge保留了完整的歷史軌跡(包括分叉點和合并節點),能看出代碼是如何在分支上演進再合流的;而Rebase則讓歷史看上去像一條直線,沒有分叉(就像所有改動都串行發生在一個分支上)。Rebase后的歷史往往更加簡潔、線性,使用 git log
瀏覽時不會被大量的合并提交干擾。但另一方面,Merge不改變已有歷史,而Rebase通過重寫歷史來達成線性,可能會讓歷史記錄失去分支上下文。
**使用注意:由于 rebase 會重寫提交歷史,所以有一個“變基黃金法則”**必須牢記:絕不要在公共分支上對已經推送的提交執行 rebase 。換句話說,僅對尚未分享給別人的本地分支使用 rebase。一旦你在協作的分支(比如 origin/main 或團隊共享的feature分支)上做了變基,然后強制推送,會導致他人的倉庫出現沖突和混亂(因為他們的提交歷史與你的不一致)。因此,在共享環境下,rebase 只能用于更新自己本地的開發分支,再正常推送,或用于自己fork后在PR合并前整理提交。遵循“變基黃金法則”:絕不在公有倉庫的分支上執行 rebase。
5.2、舉例:使用 rebase 更新分支
假設你正在開發分支 feature-A
上工作,此時需要同步主分支 main
最新的更新。在 merge 和 rebase 兩種做法中,如果選擇 rebase:
# 確保主分支是最新的
git checkout main
git pull origin main# 切回功能分支,執行變基
git checkout feature-A
git rebase main
執行上述操作后,Git 會將 feature-A
分支上的提交一個接一個地轉移到 main
最新提交之后。如果沒有沖突,變基會自動完成。若有沖突,Git 會停止并讓你解決沖突,解決完畢后用 git add
將更改暫存,然后 git rebase --continue
繼續剩下的提交。全部結束后,切換到 main,可以使用 git merge feature-A
快速合并(fast-forward)或者直接 git push origin feature-A:main
推送 feature-A 分支作為 main 分支的新更新(比如在pull request被接受時)。這樣 main 的歷史就是線性的,沒有額外的分叉點。相比直接 merge,這種方法避免了合并提交,使歷史更干凈。
5.3、舉例:交互式 rebase 整理提交
假如在開發一個功能過程中,你做了3次提交:第一次提交遺漏了一些文件,第二次補上遺漏,第三次修正了前面提交信息的一個拼寫錯誤。最終這些其實都是為實現同一個功能,理想情況下可以合并成一個提交。使用 git rebase -i
可以實現:
git rebase -i HEAD~3
在打開的編輯界面中,你會看到類似:
pick f1a2b3c Implement feature X
pick a3d4e5f Add missing files for feature X
pick b4c6d7e Fix typo in feature X description
將后兩行的 pick
改為 squash
(或縮寫 s
),表示將它們壓縮到第一個提交中。保存退出后,Git 會應用第一個提交,然后將后兩個提交的改動合并進第一個。在這個過程中會讓你編輯合并后的提交消息,你可以整合原先三個提交的信息為一條完整描述。完成后,歷史中就只剩下一條提交,包含所有改動。這就是一次提交壓縮操作。類似地,你可以重新排序提交順序(調整行的先后)、丟棄某些提交(改為 drop
),或者標記 edit
手動修改某個提交的內容(在 rebase 過程中暫停讓你修正然后繼續),等等。
通過 rebase 的這些技巧,我們能夠在不影響最終代碼結果的情況下,讓提交歷史更符合閱讀和維護的需要。例如,將“修復拼寫錯誤”“調整格式”這類雜碎 commit 合并到主要 commit 中,保持主干歷史的清晰。當然,這種修改歷史的動作只應對自己本地分支進行,在與團隊協作前完成整理。
6、推薦的 Git 使用流程與團隊協作技巧
掌握了上述 Git 高級功能后,我們來討論一下在團隊協作中,如何高效地使用 Git 工作,以及一些值得遵循的實踐規范。良好的協作流程可以避免很多問題,確保團隊所有成員各司其職又步調一致。
下面是一套常見的 Git 團隊協作開發流程(以功能開發為例):
-
同步主分支: 在開始新的工作之前,先切換到主分支(例如
main
或develop
),執行git pull
拉取遠程最新代碼。這樣可以確保你的主分支是最新狀態,為后續創建新分支打好基礎。 -
新建功能分支: 從主分支創建一個新的功能分支用于開發你的任務:
git checkout -b feature/some-feature
分支命名應清晰反映工作內容,比如
feature/login-auth
或bugfix/issue-101
。在這個分支上進行你的開發工作,與此同時主分支的代碼保持不變。 -
在分支上進行開發并頻繁提交: 在功能分支上編寫代碼。建議遵循“早提交、勤提交”的原則,每實現一個獨立的小功能或達到一個里程碑就使用
git add
和git commit
提交一次。提交信息要清晰描述修改目的,例如 “Add login form UI” 而不是 “update code”。頻繁提交有助于記錄開發軌跡,也使得出現問題時更容易定位和回溯。 -
將分支推送到遠程: 雖然還在開發中,但也最好經常將本地分支推送到遠程倉庫備份,并讓團隊可見你的進展:
git push -u origin feature/some-feature
使用
-u
將本地分支與遠程同名分支建立跟蹤關系。之后如果繼續有新的提交,可定期git push
更新遠程。這樣即使你的電腦發生故障,代碼也不會丟失,同時同事也能看到并拉取你的最新代碼(如果需要協作)。 -
與主分支保持同步: 開發過程中,主分支可能也在推進其他更新。為避免分支長期偏離主線,最好定期用主分支的變化更新你的功能分支。例如可以執行
git pull origin main
(或git fetch
然后在分支上git merge origin/main
)將主分支的修改合并過來。如果希望更整潔,可以用git rebase origin/main
將你的提交變基到主分支后。這能減少最終合并時的沖突概率。 -
提交合并請求(Pull Request)并代碼審查: 功能開發完成并經過自測后,將你的分支代碼合并回主分支。通常做法是在托管平臺上創建一個 Pull Request(合并請求),描述你的更改,通知團隊其他成員審核代碼。代碼審查(Code Review)可以發現潛在問題并確保代碼質量。在 PR 通過后,維護者會將你的功能分支合并到主分支。合并可以采用 Squash Merge(壓縮成一次提交)或者普通 Merge,根據團隊約定。
-
清理分支并部署: 功能成功合并進主分支后,可以刪除遠程和本地的功能分支(以避免過多分支混亂倉庫):
git branch -d feature/some-feature # 刪除本地分支 git push origin --delete feature/some-feature # 刪除遠程分支
之后,在本地切回主分支并
git pull
獲取最新的代碼。如果需要部署或發布,可以根據主分支最新代碼進行。
在上述流程中,有一些值得強調的團隊協作技巧和最佳實踐:
-
確保主分支始終可用: 主分支(如
main
/master
)通常代表產品的穩定版本。不在主分支上直接開發新功能,所有對主分支的修改都通過分支合并來完成。這樣主分支上的代碼始終是可以隨時發布或部署的。如果需要緊急修復,也使用熱修復分支來完成并盡快合并。 -
善用分支命名和標簽: 清晰的分支命名有助于協作。例如見到
feature/login-auth
自然知道是開發登錄認證功能。對于重要的里程碑或發布版本,使用 Git 標簽(tag)來標記提交,方便以后查找對應版本的代碼。 -
編寫有意義的提交消息: 一個好的提交消息能夠簡潔概括更改內容或原因,方便日后查閱歷史時了解每次改動的目的。避免使用含糊不清的描述如“修改代碼”“更新了幾個文件”。常見格式是在簡短的標題后可選地補充細節描述,團隊可以采用一致的 commit message 風格(比如遵循 Angular 提交信息規范等)。
-
及時拉取和合并: 團隊協作中,每個人應經常同步遠程倉庫的最新變化(
git pull
),特別是在開始新的任務或準備將自己的修改推送前。這可以最大限度減少沖突。當發現與他人有沖突時,積極溝通,共同決定如何解決。 -
不要在公共分支上強制推送: 避免使用
git push --force
強制推送主分支或他人也在用的分支。如果必須(例如更正錯誤的歷史),也要提前與團隊說明,確保別人沒有基于舊歷史繼續工作。強制推送可能覆蓋他人的提交,通常只有倉庫管理員在特殊情況下才執行,而且通常通過 Pull Request 來協同完成。 -
使用
.gitignore
保持倉庫整潔: 在協作項目中,制定.gitignore
文件很重要,確保編譯產物、臨時文件、不需要納入版本控制的文件不被提交。例如日志文件、IDE配置、依賴生成的文件等應被忽略。這可以減少不必要的沖突和倉庫臃腫。 -
定期備份和維護倉庫: 雖然遠程倉庫本身就是備份,但也可以克隆鏡像倉庫作為額外備份。對長期項目,定期清理無用的分支,避免倉庫中累積過多過期分支。對于超大的歷史(比如大量二進制文件意外提交),可以考慮使用 Git 工具(如 BFG Repo-Cleaner)清理,但這些都是進階話題了。