前些時(shí)間,公司產(chǎn)品經(jīng)理針對(duì)司機(jī)端提出一個(gè)微信及支付寶二維碼掃碼支付需求,需求不急,但也是剛性任務(wù),于是我在會(huì)后便火急火燎開(kāi)干,期間碰到一些問(wèn)題獲取了一些經(jīng)驗(yàn),想分享給各位。
第一個(gè)問(wèn)題,期間,或許因?yàn)楣痉磸?fù)修網(wǎng)造成GitLab地址變動(dòng),管理GitLab的后臺(tái)并沒(méi)有多余的時(shí)間來(lái)維護(hù),所以短時(shí)間內(nèi),并不能將本地的修改 push 到遠(yuǎn)程。這種情況下,我將每次的修改 commit 到本地,等GitLab好了再 push,對(duì)此我深刻感受到Git的好處。
第二個(gè)問(wèn)題,在公司我負(fù)責(zé)iOS移動(dòng)端的獨(dú)立開(kāi)發(fā)與維護(hù)。由于是獨(dú)立開(kāi)發(fā),使用Git時(shí),我一般在 dev 上 fix bug 或者改需求。此次,我需求完成一半,測(cè)試那邊給出反饋,我放下手頭需求,緊急 fix bug,commit 到本地后,在無(wú)法 push 到遠(yuǎn)程的情況下,我需要緊急提交一個(gè)新版本。問(wèn)題來(lái)了,由于GitLab未修復(fù),我無(wú)法clone上個(gè)版本到本地來(lái)fix bug,又因?yàn)槲襠ev分支上同時(shí)fix bug和迭代需求,本地也不是上一個(gè)版本。
創(chuàng)建與合并分支
在Git里,每次提交,Git把提交串成一個(gè)分支,這個(gè)分支是主分支,即 master 分支。HEAD 不是指向提交,而是指向 master,master 才指向提交,所以,HEAD指向的是當(dāng)前分支。
Git創(chuàng)建一個(gè) dev 分支時(shí)很快,因?yàn)槌诵枰黾右粋€(gè) dev 指針,改變 HEAD 的指向,工作區(qū)的文件沒(méi)有變。
Git合并 dev 分支到 master 分支也很快,在 dev 上完成工作后,直接把 master 指向 dev 當(dāng)前的提交,就完成了合并。合并過(guò)程中,指針指向改變,工作區(qū)內(nèi)容同樣沒(méi)有變。
- 創(chuàng)建
dev分支,然后切換到dev分支
git checkout -b dev
- 創(chuàng)建分支
git branch <name>
- 切換分支
git checkout <name>
- 用
git branch查看當(dāng)前分支
$ git branch
* dev
master
-
git merge命令用于合并指定分支到當(dāng)前分支
git merge <name>
- 刪除分支
git branch -d <name>
解決沖突
- 創(chuàng)建并切換
feature分支
$ git checkout -b feature
Switched to a new branch feature
- 修改工作區(qū)內(nèi)容后,在
feature分支上提交修改并且將暫存區(qū)的修改commit到當(dāng)前分支
$ git add readme.txt
$ git commit -m "and simple"
[feature 75a857c] and simple
1 file changed, 1 insertion(+), 1 deletion(-)
- 切換到
master分支,系統(tǒng)將會(huì)提示當(dāng)前master分支彼遠(yuǎn)程的master分支要超前一個(gè)提交
$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 1 commit
- 在
master分支上把工作區(qū)內(nèi)容進(jìn)行修改,并且提交
$ git add readme.txt
$ git commit -m "& simple"
[master 400b400] & simple
1 file changed, 1 insertion(+), 1 deletion(-)
- 此刻在master分支和feature分支都要提交的情況下,Git無(wú)法進(jìn)行快速合并,如果試圖把各自的修改合并,將會(huì)造成沖突。
$ git merge feature
Auto-merging readme.txt
CONFLICT (content): Merge conflict in readme.txt
Automatic merge failed; fix conflicts and then commit the result.
- Git將會(huì)提示,readme.txt文件存在沖突,必須手動(dòng)解決沖突后再提交,我們一般使用
git status了解我們沖突的文件。
$ git status
# On branch master
# Your branch is ahead of 'origin/master' by 2 commits.
#
# Unmerged paths:
# (use "git add/rm <file>..." as appropriate to mark resolution)
#
# both modified: readme.txt
#
no changes added to commit (use "git add" and/or "git commit -a")
直接查看readme.txt文件夾下內(nèi)容,Git用
<<<<<<<,=======,>>>>>>>標(biāo)記出不同分支的內(nèi)容,我們修改文件夾下內(nèi)容使feature分支和master分支保持一致后,再提交。使用帶參數(shù)的
git log也可以看到分支的合并情況
$ git log --graph --pretty=oneline --abbrev-commit
* 59bc1cb conflict fixed
|\
| * 75a857c AND simple
* | 400b400 & simple
|/
* fec145a branch test
...
- 最后刪除分支
$ git branch -d feature
Deleted branch feature (was 75a857c).
- 所以說(shuō),當(dāng)Git無(wú)法自動(dòng)合并分支時(shí),就必須首先解決沖突,解決沖突后,再提交,合并完成。使用
git log --graph命令可以查看分支合并圖
分支管理策略
在解決沖突時(shí),我們提到了快速合并,通常,我們?cè)诤喜⒎种r(shí),Git會(huì)使用 Fast forward 模式,在 Fast forward 模式下,刪除分支后,會(huì)丟失分支信息。
所以,我們會(huì)強(qiáng)制禁用 Fast forward 模式,Git會(huì)在 merge 時(shí)生成一個(gè)新的 commit,這樣我們可以在分支歷史上查看分支信息。
--no-ff方式的git merge
- 首先,創(chuàng)建并切換
dev分支
$ git checkout -b dev
Switched to a new branch dev
- 修改工作區(qū)readme.txt文件,并提交一個(gè)新的
commit
$ git add readme.txt
$ git commit -m "add merge"
[dev 6224937] add merge
1 file changed, 1 insertion(+)
- 切換回
master
$ git checkout master
Switched to branch 'master'
- 合并
dev分支時(shí),注意使用--no-ff參數(shù),表示禁用Fast forward
$ git merge --no-ff -m "merge with --no-ff" dev
Merge made by the 'recursive' strategy.
readme.txt | 1 +
1 file changed, 1 insertion(+)
- 合并后我們使用
git log查看分支歷史
$ git log --graph --pretty=oneline --abbrev-commit
* 7825a50 merge with no-ff
|\
| * 6224937 add merge
|/
* 59bc1cb conflict fixed
...
分支策略
其實(shí),我們?cè)趯?shí)際開(kāi)發(fā)中,應(yīng)該按照幾個(gè)基本原則進(jìn)行分支管理:
首先,
master分支應(yīng)該是最穩(wěn)定的,也就是說(shuō),我們平時(shí)不能在master上干活,master分支僅用來(lái)發(fā)布版本使用。如果要干活,我們應(yīng)該創(chuàng)建并切換一個(gè)分支,這個(gè)分支就是
dev分支。也就是說(shuō),dev分支是不穩(wěn)定的,平時(shí)我們各自在dev分支上干活,每個(gè)人有自己的分支,不斷往dev分支上合并,等到版本發(fā)布時(shí),再把dev分支合并到master上。
Bug分支
當(dāng)前,正在 dev 上的工作還沒(méi)提交,我們需要經(jīng)緊急修復(fù)一個(gè)bug,很自然地,需要?jiǎng)?chuàng)建一個(gè) bug 分支來(lái)修復(fù)這個(gè)bug。有人會(huì)有疑問(wèn),為什么不把 dev 上工作區(qū)的修改先進(jìn)行提交,其實(shí)我們并不是不想提交,而是工作進(jìn)行到一半無(wú)法提交。那么,Git為我們提供一個(gè) stash 功能,先把當(dāng)前工作現(xiàn)場(chǎng)“儲(chǔ)藏”起來(lái),等恢復(fù)現(xiàn)場(chǎng)后繼續(xù)工作。
$ git stash
Saved working directory and index state WIP on dev: 6224937 add merge
HEAD is now at 6224937 add merge
git stash后,我們用git status查看工作區(qū),工作區(qū)是干凈的,我們可以放心創(chuàng)建分支修復(fù)bug。確定在那個(gè)分支上修復(fù)bug,就在那個(gè)分支上創(chuàng)建bug分支。此次我們?cè)?
master上修復(fù),就從master上創(chuàng)建臨時(shí)分支。由于創(chuàng)建的bug分支是臨時(shí)分支,修復(fù)bug后,切換到master分支,完成合并,最后刪除bug分支。接著我們回到
dev干活,使用git status查看工作區(qū)狀態(tài),發(fā)現(xiàn)工作區(qū)是干凈的,這是我們需要使用git stash list查看存的工作現(xiàn)場(chǎng)。
$ git stash list
stash@{0}: WIP on dev: 6224937 add merge
使用
git stash apply恢復(fù),但是恢復(fù)后,stash內(nèi)容不會(huì)刪除,需要使用git stash drop刪除stash內(nèi)容。或者,使用
git stash pop,恢復(fù)的同時(shí)把stash內(nèi)容也刪除了。
$ git stash pop
# On branch dev
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# new file: hello.py
#
# Changes 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.txt
#
Dropped refs/stash@{0} (f624f8e5f082f2df2bed8a4e09c12fd2943bdd40)
- 使用
git stash pop后,使用git stash list查看,就看不到stash內(nèi)容了。
Feature分支
一般情況下,添加一個(gè)新功能,需要?jiǎng)?chuàng)建一個(gè) feature 分支,feature 分支作為臨時(shí)開(kāi)發(fā)新功能的分支最終要合并到 dev 分支,也就是說(shuō),我們添加一個(gè)新功能,并不是在 dev 上進(jìn)行開(kāi)發(fā),而是要?jiǎng)?chuàng)建一個(gè) feature 分支,而這個(gè) feature 分支用于臨時(shí)開(kāi)發(fā)一個(gè)新功能,是一個(gè)臨時(shí)分支。
其實(shí),
feature分支和bug分支類似,創(chuàng)建切換,合并,然后刪除。取消新功能時(shí),使用
git branch -d <name>刪除分支,但是會(huì)提示銷毀失敗,這時(shí)需要強(qiáng)行刪除分支,使用命令git branch -D <name>。
$ git branch -D feature-vulcan
Deleted branch feature-vulcan (was 756d4af).
- 所以,如果要丟棄一個(gè)沒(méi)有被合并的分支,可以通過(guò)
git branch -D <name>強(qiáng)行刪除這個(gè)分支。
多人協(xié)作
真正意義上,Git的價(jià)值還是體現(xiàn)在團(tuán)隊(duì)協(xié)作作用上,而那些命令其實(shí)僅僅起到輔助協(xié)作的作用,如果是獨(dú)立開(kāi)發(fā)并不能深刻體會(huì)到這種協(xié)作的作用,但是如果進(jìn)入純技術(shù)公司團(tuán)隊(duì),可能Git的這種協(xié)作價(jià)值才能真正的體現(xiàn)出來(lái)。
從遠(yuǎn)程倉(cāng)庫(kù)
clone時(shí),實(shí)際上Git自動(dòng)把本地master分支和遠(yuǎn)程master分支對(duì)應(yīng)起來(lái)了,并且,遠(yuǎn)程倉(cāng)庫(kù)默認(rèn)的名稱是origin。所以說(shuō),我們使用
git remote查看遠(yuǎn)程倉(cāng)庫(kù)的信息
$ git remote
origin
- 或者,用
git remote -v查看遠(yuǎn)程庫(kù)詳細(xì)信息
$ git remote -v
origin git@github.com:michaelliao/learngit.git (fetch)
origin git@github.com:michaelliao/learngit.git (push)
推送分支
master分支在本地作為主分支,要時(shí)刻與遠(yuǎn)程倉(cāng)庫(kù)同步。dev分支是開(kāi)發(fā)分支,團(tuán)隊(duì)所有成員都需要在dev分支上工作,所以dev分支同樣需要與遠(yuǎn)程同步。bug分支作為臨時(shí)分支,只在本地修復(fù)bug,沒(méi)有必要推送到遠(yuǎn)程,可能一周推送一次。feature分支同樣是臨時(shí)分支,但是,feature分支是否推送到分支,取決于是否團(tuán)隊(duì)協(xié)作,如果是獨(dú)立開(kāi)發(fā),feature分支沒(méi)有必要推送到遠(yuǎn)程,如果協(xié)作開(kāi)發(fā),需要推送到遠(yuǎn)程,與遠(yuǎn)程保持同步。
抓取分支
- 從遠(yuǎn)程庫(kù)
clone時(shí)默認(rèn)只能看到本地的master分支,我們可以使用git stauts查看。
$ git branch
* master
- 要在
dev分支上開(kāi)發(fā),必須創(chuàng)建遠(yuǎn)程origin的dev分支到本地,可以使用git checkout -b dev origin/dev命令創(chuàng)建本地dev分支。
$ git checkout -b dev origin/dev
- 可以在本地創(chuàng)建的這個(gè)
dev分支上修改,并且把修改commit到dev分支,然后把dev分支push到遠(yuǎn)程。
$ git commit -m "add /usr/bin/env"
[dev 291bea8] add /usr/bin/env
1 file changed, 1 insertion(+)
$ git push origin dev
Counting objects: 5, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 349 bytes, done.
Total 3 (delta 0), reused 0 (delta 0)
To git@github.com:michaelliao/learngit.git
fc38031..291bea8 dev -> dev
- 由于你已經(jīng)向
origin/dev分支推送了提交,而devB碰巧對(duì)同樣的文件做了修改,并且試圖推送,由于我的最新提交和devB試圖推送的提交有沖突,所以,推送失敗。
$ git add hello.py
$ git commit -m "add coding: utf-8"
[dev bd6ae48] add coding: utf-8
1 file changed, 1 insertion(+)
$ git push origin dev
To git@github.com:michaelliao/learngit.git
! [rejected] dev -> dev (non-fast-forward)
error: failed to push some refs to 'git@github.com:michaelliao/learngit.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Merge the remote changes (e.g. 'git pull')
hint: before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
- 這時(shí),Git提醒,先用
git pull把最新的提交從origin/dev抓取下來(lái),然后,在本地合并,先解決沖突,然后再推送到遠(yuǎn)程。
$ git pull
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 3 (delta 0)
Unpacking objects: 100% (3/3), done.
From github.com:michaelliao/learngit
fc38031..291bea8 dev -> origin/dev
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 dev origin/<branch>
-
git pull失敗,因?yàn)槲粗付ū镜?dev分支和遠(yuǎn)程origin/dev分支鏈接,所以,設(shè)置dev和遠(yuǎn)程origin/dev的鏈接。
$ git branch --set-upstream dev origin/dev
Branch dev set up to track remote branch dev from origin.
-
git pull成功后,git merge合并存在沖突,先手動(dòng)解決沖突,然后提交,最后push到遠(yuǎn)程。
$ git commit -m "merge & fix hello.py"
[dev adca45d] merge & fix hello.py
$ git push origin dev
Counting objects: 10, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (5/5), done.
Writing objects: 100% (6/6), 747 bytes, done.
Total 6 (delta 0), reused 0 (delta 0)
To git@github.com:michaelliao/learngit.git
291bea8..adca45d dev -> dev
- 請(qǐng)記住,如果
git pull提示“no tracking information“,則說(shuō)明本地分支和遠(yuǎn)程分支的鏈接關(guān)系沒(méi)有創(chuàng)建,使用命令git branch --set-upstream branch-name origin/branch-name,以上就是多人協(xié)作的工作模式。