😁 作者簡介:一名大四的學生,致力學習前端開發技術
??個人主頁:夜宵餑餑的主頁
? 系列專欄:Git等軟件工具技術的使用
👐學習格言:成功不是終點,失敗也并非末日,最重要的是繼續前進的勇氣
?🔥?前言:
這里是關于git的分支管理和多人協作時的知識,讓大家真正學會運用git的分支管理,而不是停留在命令上面,希望可以幫助到大家,歡迎大家的補充和糾正
文章目錄
- 第三章 分支管理
- 3.1 創建與合并沖突
- 3.2 解決沖突
- 3.3 分支管理策略
- 3.4 Bug分支
- 3.5 多人協作
- 3.5.1 推送分支
- 3.5.2 抓取分支
第三章 分支管理
3.1 創建與合并沖突
在版本回退里,你已經知道,每次提交,Git都把它們串成一條時間線,這條時間線就是一個分支。截止到目前,只有一條時間線,在Git里,這個分支叫主分支,即master
分支。HEAD
嚴格來說不是指向提交,而是指向master
,master
才是指向提交的,所以,HEAD
指向的就是當前分支。
一開始的時候,master
分支是一條線,Git用master
指向最新的提交,再用HEAD
指向master
,就能確定當前分支,以及當前分支的提交點:
HEAD││▼master││▼
┌───┐ ┌───┐ ┌───┐
│ │───?│ │───?│ │
└───┘ └───┘ └───┘
每次提交,master
分支都會向前移動一步,這樣,隨著你不斷提交,master
分支的線也越來越長。
當我們創建新的分支,例如dev
時,Git新建了一個指針叫dev
,指向master
相同的提交,再把HEAD
指向dev
,就表示當前分支在dev
上:
master││▼
┌───┐ ┌───┐ ┌───┐
│ │───?│ │───?│ │
└───┘ └───┘ └───┘▲││dev▲││HEAD
你看,Git創建一個分支很快,因為除了增加一個dev
指針,改改HEAD
的指向,工作區的文件都沒有任何變化!
不過,從現在開始,對工作區的修改和提交就是針對dev
分支了,比如新提交一次后,dev
指針往前移動一步,而master
指針不變:
master││▼
┌───┐ ┌───┐ ┌───┐ ┌───┐
│ │───?│ │───?│ │───?│ │
└───┘ └───┘ └───┘ └───┘▲││dev▲││HEAD
假如我們在dev
上的工作完成了,就可以把dev
合并到master
上。Git怎么合并呢?最簡單的方法,就是直接把master
指向dev
的當前提交,就完成了合并:
HEAD││▼master││▼
┌───┐ ┌───┐ ┌───┐ ┌───┐
│ │───?│ │───?│ │───?│ │
└───┘ └───┘ └───┘ └───┘▲││dev
所以Git合并分支也很快!就改改指針,工作區內容也不變!
合并完分支后,甚至可以刪除dev
分支。刪除dev
分支就是把dev
指針給刪掉,刪掉后,我們就剩下了一條master
分支:
HEAD││▼master││▼
┌───┐ ┌───┐ ┌───┐ ┌───┐
│ │───?│ │───?│ │───?│ │
└───┘ └───┘ └───┘ └───┘
其他的命令可以在命令小結查找到,我這里說一下有點難理解的合并分支的命令:
$ git merge dev
Updating e5dfae5..2d46c90
Fast-forwardreadme.txt | 3 ++-1 file changed, 2 insertions(+), 1 deletion(-)
git merge
命令用于合并指定分支到當前分支。合并后,再查看readme.txt
的內容,就可以看到,和dev
分支的最新提交是完全一樣的。
注意到上面的Fast-forward
信息,Git告訴我們,這次合并是“快進模式”,也就是直接把master
指向dev
的當前提交,所以合并速度非常快。
當然,也不是每次合并都能Fast-forward
,我們后面會講其他方式的合并
?? 命令小結
- 查看分支:
git branch
- 創建分支:
git branch <name>
- 切換分支:
git checkout <name>
或者git switch <name>
- 創建并切換分支:
git checkout -b <name>
或者git switch -c <name>
- 合并某個分支:
git merge <name>
- 刪除分支:
git branch -d <name>
3.2 解決沖突
當我們創建一個feature1的分支,并修改了工作區文件的內容,然后add和commit到版本庫
當我們切換為master分支的時候,也修改了工作區文件的內容,還add和commit到版本庫
于是就出現這種情況:
HEAD││▼master││▼┌───┐┌─?│ │
┌───┐ ┌───┐ ┌───┐ │ └───┘
│ │───?│ │───?│ │──┤
└───┘ └───┘ └───┘ │ ┌───┐└─?│ │└───┘▲││feature1
這種我們使用分支合并的時候,無法快速合并,因為有分支沖突的存在,我們試看看:
$ git merge feature1
Auto-merging readme.txt
CONFLICT (content): Merge conflict in readme.txt
Automatic merge failed; fix conflicts and then commit the result.
我們可以看看readme文件是什么情況
Git is a distributed version control system.
Git is free software distributed under the GPL.
<<<<<<< HEAD
Creating a new branch is quick & simple.
=======
Creating a new branch is quick AND simple.
>>>>>>> feature1
這種時候我們需要手動修改文件,然后再提交:
Git is distributed version control system
Git is free software distributed under the GPL
Creating a new branch is quick AND simple
再提交:
$ git add readme.txt
$ git commit -m "conflict fixed"
現在的分支圖變成了:
HEAD││▼master││▼┌───┐ ┌───┐┌─?│ │───?│ │
┌───┐ ┌───┐ ┌───┐ │ └───┘ └───┘
│ │───?│ │───?│ │──┤ ▲
└───┘ └───┘ └───┘ │ ┌───┐ │└─?│ │──────┘└───┘▲││feature1
我們可以使用git log
來查看分支的合并情況:
$ git log --graph --pretty=oneline --abbrev-commit
* 55a2d4f (HEAD -> master) conflict fixed
|\
| * 67d9043 AND simple
* | 5baf2da & simple
|/
* 2d46c90 brancj test
* e5dfae5 remove test
* d9d1dc0 add test.txt
* bbacedf append GPL
* c440278 add distributed
* 6fe56b1 wrote a readme file
最后,刪除feature1分支:
$ git branch -d feature1
Deleted branch feature1 (was 67d9043).
3.3 分支管理策略
我們通常在合并分支時,是有兩種模式的
-
Fast forward 模式(快速合并):
- 當你在合并分支時,Git 會嘗試使用 Fast forward 模式,如果可能的話。
- 如果兩個分支的提交歷史是線性的,也就是說,被合并的分支的所有提交都是基于當前分支的最新提交,那么 Git 可以簡單地將指針(HEAD)向前移動,指向被合并分支的最新提交,從而完成合并。
- 這種模式下,合并操作不會創建新的合并提交,因為歷史是線性的,只需移動指針即可。
bashCopy code# 在當前分支上合并名為 feature 的分支,使用 Fast forward 模式 git merge feature
-
普通合并(non-fast-forward)模式:
- 如果兩個分支的提交歷史不是線性的,即存在分叉,Git 將執行普通合并。這種情況下,Git 會創建一個新的合并提交,將兩個分支的修改整合在一起。
- 普通合并會保留每個分支的提交歷史,即使它們是并行的。
bashCopy code# 在當前分支上合并名為 feature 的分支,強制執行普通合并 git merge --no-ff feature
這兩種模式我們在使用時,如何選擇呢,我們可以來看看這兩種模式下產生的提交歷史:
-
Fast forward 模式(快速合并):
-
沒有刪除分支:
$ git log --graph --pretty=oneline --abbrev-commit * 62e7593 (HEAD -> master, dev) add dev * 55a2d4f conflict fixed |\ | * 67d9043 AND simple * | 5baf2da & simple |/ * 2d46c90 brancj test
-
刪除分支了:
$ git log --graph --pretty=oneline --abbrev-commit * 62e7593 (HEAD -> master) add dev * 55a2d4f conflict fixed |\ | * 67d9043 AND simple * | 5baf2da & simple |/ * 2d46c90 brancj test
-
圖解:
-
-
普通模式:
-
沒有刪除分支:
$ git log --graph --pretty=oneline --abbrev-commit * 16c6f76 (HEAD -> master) merge with no-ff |\ | * d70cf45 (dev) add dev history |/ * 62e7593 add dev * 55a2d4f conflict fixed |\ | * 67d9043 AND simple * | 5baf2da & simple |/ * 2d46c90 brancj test
-
刪除分支了:
$ git log --graph --pretty=oneline --abbrev-commit * 16c6f76 (HEAD -> master) merge with no-ff |\ | * d70cf45 add dev history |/ * 62e7593 add dev * 55a2d4f conflict fixed |\ | * 67d9043 AND simple * | 5baf2da & simple |/ * 2d46c90 brancj test
-
圖解:
-
?? 總結:
在實際開發中,我們應該按照幾個基本原則進行分支管理:
首先,master
分支應該是非常穩定的,也就是僅用來發布新版本,平時不能在上面干活;
那在哪干活呢?干活都在dev
分支上,也就是說,dev
分支是不穩定的,到某個時候,比如1.0版本發布時,再把dev
分支合并到master
上,在master
分支發布1.0版本;
你和你的小伙伴們每個人都在dev
分支上干活,每個人都有自己的分支,時不時地往dev
分支上合并就可以了。
所以,團隊合作的分支看起來就像這樣
合并分支時,加上--no-ff
參數就可以用普通模式合并,合并后的歷史有分支,能看出來曾經做過合并,而fast forward
合并就看不出來曾經做過合并
3.4 Bug分支
軟件開發中,bug就像家常便飯一樣。有了bug就需要修復,在Git中,由于分支是如此的強大,所以,每個bug都可以通過一個新的臨時分支來修復,修復后,合并分支,然后將臨時分支刪除。
當你接到一個修復一個代號101的bug的任務時,很自然地,你想創建一個分支issue-101
來修復它,但是,等等,當前正在dev
上進行的工作還沒有提交:
$ git status
On branch dev
Changes not staged for commit:(use "git add <file>..." to update what will be committed)(use "git restore <file>..." to discard changes in working directory)modified: readme.txtno changes added to commit (use "git add" and/or "git commit -a")
👨 我的誤解:
我只是想切換到master分支,然后在master分支上面創建bug修改分支issue-101,我為什么不能直接切換呢?
🌴 答,在切換分支時,如果工作區有修改的話,與切換分支會有沖突的,切換分支會不成功,所以說,我們在切換分支時,應該保證切換分支之前的分支是干凈的,什么叫分支干凈呢?:一個分支是基于工作目錄(working directory)的狀態來維護的。當我們說"切換分支之前是干凈的狀態"時,意味著工作目錄中沒有未提交的更改。這包括對文件的修改、新文件的添加和已跟蹤文件的刪除。
所以我們要先搞清楚怎么把工作區目錄整理干凈
現在的問題是提交是可以解決問題,但是并不是你不想提交,而是工作只進行到一半,還沒法提交,預計完成還需1天時間。但是,必須在兩個小時內修復該bug,怎么辦?
幸好,Git還提供了一個stash
功能,可以把當前工作現場“儲藏”起來,等以后恢復現場后繼續工作:
$ git stash
Saved working directory and index state WIP on dev: f52c633 add merge
現在,用git status
查看工作區,就是干凈的(除非有沒有被Git管理的文件),因此可以放心地創建分支來修復bug。
接下來就是正常的切換master分支,然后創建issue-101分支,在issue-101分支修復bug,然后合并分支到master
🆕 開始修復bug:
首先確定要在哪個分支上修復bug,假定需要在master
分支上修復,就從master
創建臨時分支:
$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 6 commits.(use "git push" to publish your local commits)$ git checkout -b issue-101
Switched to a new branch 'issue-101'
現在修復bug,需要把“Git is free software …”改為“Git is a free software …”,然后提交:
$ git add readme.txt
$ git commit -m "fix bug 101"
[issue-101 4c805e2] fix bug 1011 file changed, 1 insertion(+), 1 deletion(-)
修復完成后,切換到master
分支,并完成合并,最后刪除issue-101
分支:
$ git switch master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 6 commits.(use "git push" to publish your local commits)$ git merge --no-ff -m "merged bug fix 101" issue-101
Merge made by the 'recursive' strategy.readme.txt | 2 +-1 file changed, 1 insertion(+), 1 deletion(-)
🔚 結束修復bug。
太棒了,原計劃兩個小時的bug修復只花了5分鐘!現在,是時候接著回到dev
分支干活了!
$ git switch dev
Switched to branch 'dev'$ git status
On branch dev
nothing to commit, working tree clean
工作區是干凈的,剛才的工作現場存到哪去了?用git stash list
命令看看:
$ git stash list
stash@{0}: WIP on dev: f52c633 add merge
工作現場還在,Git把stash內容存在某個地方了,但是需要恢復一下,有兩個辦法:
-
一是用
git stash apply
恢復,但是恢復后,stash內容并不刪除,你需要用git stash drop
來刪除; -
另一種方式是用
git stash pop
,恢復的同時把stash內容也刪了:
$ git stash pop
On branch dev
Changes to be committed:(use "git reset HEAD <file>..." to unstage)new file: hello.pyChanges 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.txtDropped refs/stash@{0} (5d677e2ee266f39ea296182fb2354265b91b3b2a)
再用git stash list
查看,就看不到任何stash內容了:
$ git stash list
你可以多次stash,恢復的時候,先用git stash list
查看,然后恢復指定的stash,用命令:
$ git stash apply stash@{0}
🤔 在master分支上修復了bug后,我們要想一想,dev分支是早期從master分支分出來的,所以,這個bug其實在當前dev分支上也存在。
那怎么在dev分支上修復同樣的bug?重復操作一次,提交不就行了?
不,我們可以有更簡單的方法
同樣的bug,要在dev上修復,我們只需要把4c805e2 fix bug 101
這個提交所做的修改“復制”到dev分支。注意:我們只想復制4c805e2 fix bug 101
這個提交所做的修改,并不是把整個master分支merge過來。
👨 我的經驗:
🌴 就是一個問題,還是合并沖突的問題,如果我們把工作區給恢復之后,再去使用這個命令去合并分支會有錯誤:
$ git cherry-pick 3d22a83
error: Your local changes to the following files would be overwritten by merge:readme.txt
Please commit your changes or stash them before you merge.
Aborting
fatal: cherry-pick failed
依舊是工作區分支不干凈,導致無法合并分支,所以我們可以再合并分支之前,使用git status命令查看工作區是否干凈
所以,我們要先把工作區使用git stash存起來,然后在合并bug分支,在把工作區的內容給修復出來
對于合并沖突的情況有以下幾種:
- 同時修改同一行或同一片區域: 如果兩個不同的分支都修改了同一行代碼,或者在相鄰的行上做了修改,Git 無法判斷應該保留哪個修改。這就導致了沖突。
- 刪除與修改沖突: 一個分支刪除了某個文件,而另一個分支對該文件進行了修改。在合并時,Git 不知道是應該保留修改還是應該保留刪除。
- 合并基的變更: 如果兩個分支的合并基(共同的祖先 commit)上有修改,而這些修改分別被兩個分支采用,那么在合并時就會發生沖突
🌴
為了方便操作,Git專門提供了一個cherry-pick
命令,讓我們能復制一個特定的提交到當前分支:
$ git branch
* devmaster
$ git cherry-pick 4c805e2
[master 1d4b803] fix bug 1011 file changed, 1 insertion(+), 1 deletion(-)
Git自動給dev分支做了一次提交,注意這次提交的commit是1d4b803
,它并不同于master的4c805e2
,因為這兩個commit只是改動相同,但確實是兩個不同的commit。用git cherry-pick
,我們就不需要在dev分支上手動再把修bug的過程重復一遍。
?? 小結
修復bug時,我們會通過創建新的bug分支進行修復,然后合并,最后刪除;
當手頭工作沒有完成時,先把工作現場git stash
一下,然后去修復bug,修復后,再git stash pop
,回到工作現場;
在master分支上修復的bug,想要合并到當前dev分支,可以用git cherry-pick <commit>
命令,把bug提交的修改“復制”到當前分支,避免重復勞動。
3.5 多人協作
我們在對遠程倉庫克隆時,實際上Git自動把本地的master分支和遠程的master分支對應起來了,遠程倉庫的默認名稱是origin
我們可以使用git remote
來查看遠程看的信息:
$ git remote
origin
或者,用git remote -v
顯式更詳細的信息:
$ git remote -v
origin git@github.com:michaelliao/learngit.git (fetch)
origin git@github.com:michaelliao/learngit.git (push)
上面顯示了可以抓取和推送的
origin
的地址。如果沒有推送權限,就看不到push的地址
3.5.1 推送分支
推送分支,就是把該分支上的所有本地提交推送到遠程庫。推送時,要指定本地分支,這樣,Git就會把該分支推送到遠程庫對應的遠程分支上:
$ git push origin master
如果要推送其他分支,比如dev
,就改成:
$ git push origin dev
如果遠程倉庫不存在 dev
分支,Git 會嘗試創建該分支并將本地的 dev
分支推送到遠程倉庫。
📝 小提醒:在Git中,分支完全可以在本地自己玩,是否推送到遠程倉庫,你可以有選擇,要看需求的
3.5.2 抓取分支
在多人協作時,大家都會往master和dev分支上推送各自的修改
這種時候會有一種情況出現:A和B用戶都分別從遠程倉庫拉取或者克隆了代碼,這時候各自完成自己的代碼工作,A用戶先完成了,所以就把代碼推送到遠程倉庫去了,這種時候B用戶再繼續推送到遠程倉庫會失敗的,因為遠程倉庫的最新提交和B用戶試圖推送的提交有沖突
可以有以下解決方法:
- 首先,可以試圖用
git push origin <branch-name>
推送自己的修改; - 如果推送失敗,則因為遠程分支比你的本地更新,需要先用
git pull
試圖合并; - 如果合并有沖突,則解決沖突,解決的方法和分支管理中的解決沖突完全一樣,并在本地提交;
- 沒有沖突或者解決掉沖突后,再用
git push origin <branch-name>
推送就能成功!
? 產生的問題
有時候git pull
時會出現這種情況:
$ git pull
There is no tracking information for the current branch.
Please specify which branch you want to merge with.
See git-pull(1) for details.git pull <remote> <branch>If you wish to set tracking information for this branch you can do so with:git branch --set-upstream-to=origin/<branch> dev
git pull
也失敗了,原因是沒有指定本地dev
分支與遠程origin/dev
分支的鏈接,根據提示,設置dev
和origin/dev
的鏈接:
$ git branch --set-upstream-to=origin/dev dev
Branch 'dev' set up to track remote branch 'dev' from 'origin'.