我們的團隊使用Git作爲版本控制工具已經有半年多的時間了,大家對一些常規的操作都已經比較熟練,然而在使用過程中難免遇到這樣那樣的問題,今天來總結一下遇到過的一些問題和解決方案。
一、git pull與git pull - - rebase
使用pull從遠程主機更新代碼時,會獲取到遠程指定分支的更新併合併到本地指定的分支,一般情況下不需要指定,默認把本地當前分支對應的遠程分支的更新獲取到並跟本地的當前分支合併。在多人協作的場景中,不推薦使用git pull,舉個栗子:
當前的遠程分支是這樣的:
origin dev:A—B—C—D
此時我把dev拉到本地,進行開發,開發完成commit後我本地是這樣的:
A—B—C—D—E—F
而此時dev分支可能還有別的同事在開發,並且已經提交了他的修改,現在origin dev分支是這樣的:
A—B—C—D—G—H
執行git pull後,倉庫裏的分支曲線是這樣的:
時間久了這條曲線就會非常混亂。如果此時用git pull - - rebase,結果是這樣的:
A—B—C—D—G—H—E’—F’,依然保持一條直線,看上去很整潔。
但是使用git pull和git pull - - rebase都有可能產生conflicts,這個是不可避免的,乖乖的解決衝突就好了。
如果要把 rebase當做 git pull 的默認值,可以在 ~/.gitconfig 配置讓所都自動套用這個設定:
[branch]
autosetuprebase = always
二、撤銷修改
如果要讓工作目錄回到上次提交時的狀態,執行
reset這個命令有三個參數可選:soft mixed hard,這三個參數分別是什麼意思呢?
首先我們要理解三個概念:
- HEAD,是一個指向當前分支最後一次提交的一個指針
- Index,也就是staging area(暫存區),是存放即將被提交的文件的地方
- Working Copy,當前工作目錄下的文件
當我們第一次checkout一個遠程分支時,HEAD指向該分支上最後一個commit,他和Index和Working Copy是一樣的。
當我修改了一個文件,Working Copy和Index、HEAD就不一致了,這時Git會提示我們文件有修改;
當我add這個文件後,Working Copy 和Index一樣了,而他倆又和HEAD不一致了,這時Git會提示我們有文件可以提交了
當我commit這個文件後,HEAD指向了剛創建的這次commit,Working Copy和Index和HEAD又一樣了。
那麼,這三個參數的區別就是:
- soft:告訴Git只將HEAD重置到某個commit,Working Copy和Index都不變動;
- mixed:HEAD和Index都改變,Working Copy中的文件不變動,此時會提示文件有修改,但是沒有緩存到暫存區;
- hard:三者都改變到你要reset的那個commit上,這可能會導致你本地的修改丟失。
三、關於stash
當我在一個feature分支上開發時,突然有同事反饋了一個bug,這時我需要切換到dev分支來修改這個bug,但此時在feature分支上的修改還沒有做完,不想提交,此時可以使用git stash,將本地的修改先隱藏起來,不讓Git發現,這時就可以checkout到dev分支,此時查看status你會發現Working Copy是clean的,可以任意發揮了。當改完bug後再切換回feature分支,執行git stash pop 來恢復之前隱藏的那些修改,這時我又可以繼續feature上的開發了。
四、合併提交
有時候開發一個feature或者改一個bug可能分了多次commit,如果我想把這幾次commit合併成一次以後再push呢?這時可以使用git rebase -i.
如下圖,這個倉庫中有四次提交,如果要把“second commit”和“third commit”合併爲一次提交,可以這樣做:
git rebase -i 750000a08f63e4f3e7f2a511acc522e9afac0ea2
-i後面的參數是second commit的前一次提交的SHA1值
執行完成後,會出現一個編輯器讓你選擇合併哪次提交
因爲我們要把“third commit” 和“second commit”合併,所以把“third commit”前面的“pick”改成“squash”
然後退出保存,git會執行rebase操作,當遇到第二次提交時,會再次彈出編輯器說明即將合併,需要你提供commit message
輸入你的commit message,然後保存
git會提示rebase成功,此時再看git log
第二次和第三次commit已經被合併成“second and third commit”了。
當合併到只剩下兩個commit時,git rebase -i就不起作用了,這時執行:
- $ git reset –soft HEAD^1
- $ git commit –amend
就可以把最後兩個commit合併爲一個。
五、cherry-pick
這個命令可以幫助我們把另一個分支的某個commit提交到當前的分支,而不必把整個分支都合併一次。想象一個場景:我們正在進行新版本的迭代開發,此時準備發佈的版本突然被發現一個bug,而這個bug已經在新版本開發中修復了,但是又不能直接把新版本的分支合併到待發布的分支上,此時可以使用git cherry-pick comitid,將新版本分支中的那次commit提交到待發布的分支上,這樣既修復了bug,又沒有污染待發布的分支。但是這個命令是有風險的,可能會造成衝突,應該謹慎使用。
六、我合併兩個分支時,如何保證某些文件不被合併?
曾經遇到過幾次這樣的場景,我在某個feature分支上做完開發,要合併回主dev分支,但此時dev分支上有同事修改了一個重要的文件,並且改動很大,而我在feature分支上也改了這個文件,但是還是要以dev分支上的修改爲主,如果直接merge這兩個分支,這個文件必然會衝突,而解決衝突的代價實在太多大了,因此在merge時,我不希望這個文件被合併,該怎麼做呢?
實際上git在merge分支時,有一個默認的merge driver,這個驅動會去檢查每個文件的每一行,如果按照一定規則發現兩個分支的同一個文件有不同,那麼認爲兩個分支都對這個文件做了修改,會merge兩個文件,此時有可能產生衝突;那麼如果我們自定義一個merge driver,在裏面定義一些不會被檢查的文件,那git就會直接跳過這些文件,因此就不會merge了。
步驟如下:
- 創建我們的自定義merge driver:git config –global merge.ours.driver true
- 在要被merge的分支上創建.gitattributes 文件,並且在文件中置頂不merge的文件名:
echo ‘email.json merge=ours’ >> .gitattributes
git add .gitattributes
git commit -m ‘chore: Preserve email.json during merges’ - 回到要合併到的分支,執行merge:
(newbranch)gitcheckoutmaster(master) git merge newbranch
Auto-merging …
Merge made by the ‘recursive’ strategy.
demo-shared | 1 +
1 file changed, 1 insertion(+)
經過以上步驟,我們指定的email.json就不會被合併了。
杏樹林研發 馮康