git乾貨系列:(五)多人協同工作之分支管理 | 嘟嘟獨立博客

文章目錄

  1. 1. 前言
  2. 2. 正文
  3. 3. 分支簡介
  4. 4. 分支創建
  5. 5. 分支切換
  6. 6. 合併分支(快速合併)
  7. 7. 刪除分支
  8. 8. 分支合併衝突
  9. 9. 合併分支(普通合併)
  10. 10. 分支管理策略
  11. 11. 團隊多人開發協作
    1. 11.1. 推送分支
    2. 11.2. 抓取分支
  • 總結
  • 前言

    分支就是科幻電影裡面的平行宇宙,當你正在電腦前努力學習Git的時候,另一個你正在另一個平行宇宙里努力學習SVN。如果兩個平行宇宙互不干擾,那對現在的你也沒啥影響。不過,在某個時間點,兩個平行宇宙合併了,結果,你既學會了Git又學會了SVN

    正文分支簡介

    為了真正理解 Git 處理分支的方式,我們需要回顧一下Git是如何保存數據的。Git 保存的不是文件的變化或者差異,而是一系列不同時刻的文件快照。在進行提交操作時,Git會保存一個提交對象(commit object)。知道了Git保存數據的方式,我們可以很自然的想到——該提交對象會包含一個指向暫存內容快照的指針。 但不僅僅是這樣,該提交對象還包含了作者的姓名和郵箱、提交時輸入的信息以及指向它的父對象的指針。首次提交產生的提交對象沒有父對象,普通提交操作產生的提交對象有一個父對象,而由多個分支合併產生的提交對象有多個父對象。

    Git的分支,其實本質上僅僅是指向提交對象的可變指針。 Git的默認分支名字是 master。 在多次提交操作之後,你其實已經有一個指向最後那個提交對象的 master 分支。 它會在每次的提交操作中自動向前移動。

    Git 的 「master」 分支並不是一個特殊分支。它就跟其它分支完全沒有區別。 之所以幾乎每一個倉庫> 都有 master 分支,是因為 git init 命令默認創建它,並且大多數人都懶得去改動它。

    分支在實際中有什麼用呢?假設你準備開發一個新功能,但是需要兩周才能完成,第一周你寫了50%的代碼,如果立刻提交,由於代碼還沒寫完,不完整的代碼庫會導致別人不能幹活了。如果等代碼全部寫完再一次提交,又存在丟失每天進度的巨大風險。現在有了分支,就不用怕了。你創建了一個屬於你自己的分支,別人看不到,還繼續在原來的分支上正常工作,而你在自己的分支上幹活,想提交就提交,直到開發完畢後,再一次性合併到原來的分支上,這樣,既安全,又不影響別人工作。其他版本控制系統如SVN等都有分支管理,但是用過之後你會發現,這些版本控制系統創建和切換分支比蝸牛還慢,簡直讓人無法忍受,結果分支功能成了擺設,大家都不去用。但Git的分支是與眾不同的,無論創建、切換和刪除分支,Git在1秒鐘之內就能完成!無論你的版本庫是1個文件還是1萬個文件。

    分支創建

    Git是怎麼創建新分支的呢? 很簡單,它只是為你創建了一個可以移動的新的指針。 比如,創建一個 testing分支, 你需要使用 git branch 命令:

    1

    $ git branch testing

    這會在當前所在的提交對象上創建一個指針。

    兩個指向相同提交歷史的分支。

    那麼,Git又是怎麼知道當前在哪一個分支上呢? 也很簡單,它有一個名為 HEAD 的特殊指針。 請注意它和許多其它版本控制系統(如 Subversion 或 CVS)里的 HEAD 概念完全不同。 在 Git中,它是一個指針,指向當前所在的本地分支(譯註:將 HEAD 想像為當前分支的別名)。 在本例中,你仍然在master 分支上。 因為 git branch 命令僅僅 創建 一個新分支,並不會自動切換到新分支中去。

    HEAD 指向當前所在的分支.

    你可以簡單地使用 git log 命令查看各個分支當前所指的對象。 提供這一功能的參數是 --decorate

    1234

    $ git log --oneline --decoratef30ab (HEAD, master, testing) add feature #32 - ability to add new34ac2 fixed bug #1328 - stack overflow under certain conditions98ca9 initial commit of my project

    正如你所見,當前 「master」 和 「testing」 分支均指向校驗和以 f30ab 開頭的提交對象。

    分支切換

    要切換到一個已存在的分支,你需要使用git checkout命令。 我們現在切換到新創建的 testing 分支去:

    1

    $ git checkout testing

    這樣 HEAD 就指向 testing 分支了。

    HEAD 指向當前所在的分支.

    上面的創建分支和切換分支命令可以合起來用下面這個命令來替代。

    1

    $ git checkout -b testing

    那麼,這樣的實現方式會給我們帶來什麼好處呢? 現在不妨再提交一次:

    12

    $ vim test.rb$ git commit -a -m "made a change"

    HEAD 分支隨著提交操作自動向前移動.如圖所示,你的 testing 分支向前移動了,但是 master 分支卻沒有,它仍然指向運行 git checkout 時所指的對象。 這就有意思了,現在我們切換回 master 分支看看:

    1

    $ git checkout master

    檢出時 HEAD 隨之移動.這條命令做了兩件事。 一是使 HEAD 指回 master 分支,二是將工作目錄恢復成 master 分支所指向的快照內容。 也就是說,你現在做修改的話,項目將始於一個較舊的版本。 本質上來講,這就是忽略testing 分支所做的修改,以便於向另一個方向進行開發。可以使用 git branch命令查看當前分支,注意前面帶*的表示當前分支


    Note分支切換會改變你工作目錄中的文件在切換分支時,一定要注意你工作目錄里的文件會被改變。 如果是切換到一個較舊的分支,你的工作目> 錄會恢復到該分支最後一次提交時的樣子。 如果Git不能幹凈利落地完成這個任務,它將禁止切換分支。

    合併分支(快速合併)

    假如我們在testing上的工作完成了,就可以把testing合併到master上。Git怎麼合併呢?最簡單的方法,就是直接把master指向testing的當前提交,就完成了合併,這裡你需要使用git merge命令

    123456

    $ git merge testingUpdating 64ba18a..760118bFast-forward hello.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 hello.txt

    git merge命令用於合併指定分支到當前分支。合併後,再查看內容,就可以看到,和testing分支的最新提交是完全一樣的。注意到上面的Fast-forward信息,Git告訴我們,這次合併是「快進模式」,也就是直接把master指向testing的當前提交,所以合併速度非常快。當然,也不是每次合併都能Fast-forward,我們後面會講其他方式的合併。

    刪除分支

    合併完分支後,甚至可以刪除dev分支。刪除dev分支就是把dev指針給刪掉,刪掉後,我們就剩下了一條master分支,這裡需要使用git branch -d命令來刪除分支

    12

    $ git branch -d testingDeleted branch testing (was 760118b).

    分支合併衝突

    人生不如意之事十之八九,合併分支往往也不是一帆風順的。準備新的dev分支,繼續我們的新分支開發:

    12

    $ git checkout -b devSwitched to a new branch "dev"

    修改README.md內容,添加一樣內容」day day up~」,在dev分支上提交:

    123

    $ git commit -am "one commit"[dev 6a6a08e] one commit 1 file changed, 1 insertion(+)

    切換到master分支:

    123

    $ git checkout masterSwitched to branch "master"Your branch is up-to-date with "origin/master".

    Git還會自動提示我們當前master分支比遠程的master分支要超前1個提交。在master分支上把README.md文件的最後改為 good good study,然後提價

    123

    $ git commit -am "two commit"[master 75d6f25] two commit 1 file changed, 1 insertion(+)

    現在,master分支和dev分支各自都分別有新的提交,變成了這樣:

    這種情況下,Git無法執行「快速合併」,只能試圖把各自的修改合併起來,但這種合併就可能會有衝突,我們試試看:

    1234

    $ git merge devAuto-merging README.mdCONFLICT (content): Merge conflict in README.mdAutomatic merge failed; fix conflicts and then commit the result.

    果然衝突了!Git告訴我們, README.md文件存在衝突,必須手動解決衝突後再提交。git status也可以告訴我們衝突的文件:

    1234567891011121314151617181920212223

    $ git statusOn branch masterYour branch is ahead of "origin/master" by 1 commit. (use "git push" to publish your local commits)You have unmerged paths. (fix conflicts and run "git commit")Unmerged paths: (use "git add <file>..." to mark resolution) both modified: README.mdno changes added to commit (use "git add" and/or "git commit -a")``` 我們可以直接查看`README.md`的內容: ``` bash$ cat README.md#gitLearn<<<<<<< HEADgood good study=======day day up>>>>>>> dev

    Git用<<<<<<<=======>>>>>>>標記出不同分支的內容,我們修改如下後保存:

    1

    #gitLearngood good studyday day up

    再提交:

    12

    $ git commit -am "merge commit"[master 9a4d00b] merge commit

    現在,master分支和dev分支變成了下圖所示:

    用帶參數的git log也可以看到分支的合併情況:

    123456789101112131415161718192021222324

    $ git log --graph --pretty=oneline --abbrev-commit* 9a4d00b merge commit|| * 6a6a08e one commit* | 75d6f25 two commit|/* ae06dcf 123* 760118b test* 64ba18a test|| * 4392848 Accept Merge Request #1 test : (dev -> master)| || | * a430c4b update README.md| |/| * 88ec6d7 Initial commit* 32d11c8 update README.md* 8d5acc1 new file README* e02f115 Initial commit``` 最後,刪除`feature1`分支: ``` bash$ git branch -d devDeleted branch dev (was 6a6a08e).

    合併分支(普通合併)

    通常,合併分支時,如果可能,Git會用Fast forward模式,但這種模式下,刪除分支後,會丟掉分支信息。如果要強制禁用Fast forward模式,Git就會在merge時生成一個新的commit,這樣,從分支歷史上就可以看出分支信息。下面我們實戰一下--no-ff方式的git merge:首先,仍然創建並切換dev分支:

    12

    $ git checkout -b devSwitched to a new branch "dev"

    修改README.md文件,並提交一個新的commit:

    123

    $ git commit -am "submit"[dev fee6025] submit 1 file changed, 1 insertion(+)

    現在,我們切換回master

    12

    $ git checkout masterSwitched to branch "master"

    目前來說流程圖是這樣:

    準備合併dev分支,請注意--no-ff參數,表示禁用Fast forward

    1234

    $ git merge --no-ff -m "merge with no-ff" devMerge made by the "recursive" strategy. README.md | 1 + 1 file changed, 1 insertion(+)

    因為本次合併要創建一個新的commit,所以加上-m參數,把commit描述寫進去。

    合併後,我們用git log看看分支歷史:

    1234567

    $ git log --graph --pretty=oneline --abbrev-commit* b98f802 merge with no-ff|| * fee6025 submit|/* 9a4d00b merge commit...

    可以看到,不使用Fast forward模式,merge後就像這樣:

    分支管理策略

    實際公司開發的時候一般3個分支就可以了:

    1. mster 主分支用來發布
    2. dev 日常開發用的分支
    3. bug 修改bug用的分支

    首先,master分支應該是非常穩定的,也就是僅用來發布新版本,平時不能在上面幹活;幹活都在dev分支上,也就是說,dev分支是不穩定的,到某個時候,比如1.0版本發布時,再把dev分支合併到master上,在master分支發布1.0版本,你和你的小夥伴們每個人都在dev分支上幹活,每個人都有自己的分支,時不時地往dev分支上合併就可以了;bug分支用來處理日常bug,搞定後合到dev分支即可;

    假設遠程公共倉庫,有一個master和一個dev分支,進行多人協作開發時候(每個人的公鑰必須加入到遠程賬號下,否則無法push), 每個人都應該clone一份到本地。 但是clone的只是master,如果遠程的masterdev一樣,沒關係;如果不一致,則需要clonedev分支 git checkout -b dev origin/dev 之後每個人在本地的dev分支上獨自開發(最好不要在mast上開發), 開發完成之後push到遠程dev, git push origin dev。 之後審核人再確定是否合併devmaster

    團隊多人開發協作

    當你從遠程倉庫克隆時,實際上Git自動把本地的master分支和遠程的master分支對應起來了,並且,遠程倉庫的默認名稱是origin。要查看遠程庫的信息,用git remote

    12

    $ git remoteorigin

    或者,用git remote -v顯示更詳細的信息:

    123

    $ git remote -vorigin git@git.coding.net:tengj/gitLearn.git (fetch)origin git@git.coding.net:tengj/gitLearn.git (push)

    上面顯示了可以抓取和推送的origin的地址。如果沒有推送許可權,就看不到push的地址。

    推送分支

    推送分支,就是把該分支上的所有本地提交推送到遠程庫。推送時,要指定本地分支,這樣,Git就會把該分支推送到遠程庫對應的遠程分支上:

    1

    $ git push origin master

    如果要推送其他分支,比如dev,就改成:

    1

    $ git push origin dev

    抓取分支

    多人協作時,大家都會往masterdev分支上推送各自的修改。現在,模擬一個你的小夥伴,可以在另一台電腦(注意要把SSH Key添加到GitHub)或者同一台電腦的另一個目錄下克隆:

    1234567891011

    $ git clone git@git.coding.net:tengj/gitStudy.gitCloning into "gitStudy"...remote: Counting objects: 3, done.remote: Total 3 (delta 0), reused 0 (delta 0)Receiving objects: 100% (3/3), done.Checking connectivity... done.``` 當你的小夥伴從遠程庫clone時,默認情況下,你的小夥伴只能看到本地的`master`分支。不信可以用`git branch`命令看看:``` bash$ git branch* master

    現在,你的小夥伴要在dev分支上開發,就必須創建遠程origindev分支到本地,於是他用這個命令創建本地dev分支(程分支dev要先創建)。

    12

    $ git checkout -b devgit

    創建dev分之後,先同步遠程伺服器上的數據到本地

    123

    $ git fetch originFrom git.coding.net:tengj/gitStudy * [new branch] dev -> origin/dev

    現在,他就可以在dev上繼續修改,然後,時不時地把dev分支push到遠程:

    1

    $ git commit -am "test"[dev c120ad6] test 1 file changed, 1 insertion(+)$ git push origin devCounting objects: 3, done.Delta compression using up to 4 threads.Compressing objects: 100% (2/2), done.Writing objects: 100% (3/3), 262 bytes | 0 bytes/s, done.Total 3 (delta 0), reused 0 (delta 0)To git@git.coding.net:tengj/gitStudy.git 65c05aa..c120ad6 dev -> dev

    你的小夥伴已經向origin/dev分支推送了他的提交,而碰巧你也對同樣的文件作了修改,並試圖推送:

    123456789

    $ git push origin devTo git@git.coding.net:tengj/gitStudy.git ! [rejected] dev -> dev (fetch first)error: failed to push some refs to "git@git.coding.net:tengj/gitStudy.git"hint: Updates were rejected because the remote contains work that you dohint: not have locally. This is usually caused by another repository pushinghint: to the same ref. You may want to first integrate the remote changeshint: (e.g., "git pull ...") before pushing again.hint: See the "Note about fast-forwards" in "git push --help" for details.

    推送失敗,因為你的小夥伴的最新提交和你試圖推送的提交有衝突,解決辦法也很簡單,Git已經提示我們,先用git pull把最新的提交從origin/dev抓下來,然後,在本地合併,解決衝突,再推送:

    1234567891011

    $ git pull origin devremote: Counting objects: 3, done.remote: Compressing objects: 100% (2/2), done.remote: Total 3 (delta 0), reused 0 (delta 0)Unpacking objects: 100% (3/3), done.From git.coding.net:tengj/gitStudy * branch dev -> FETCH_HEAD b7b87f4..f636337 dev -> origin/devAuto-merging a.txtCONFLICT (content): Merge conflict in a.txtAutomatic merge failed; fix conflicts and then commit the result.

    因此,多人協作的工作模式通常是這樣:

    1. 首先,可以試圖用git push origin branch-name推送自己的修改;
    2. 如果推送失敗,則因為遠程分支比你的本地更新,需要先用git pull試圖合併;
    3. 如果合併有衝突,則解決衝突,並在本地提交;
    4. 沒有衝突或者解決掉衝突後,再用git push origin branch-name推送就能成功!

    如果git pull提示「no tracking information」,則說明本地分支和遠程分支的鏈接關係沒有創建,用命令git branch --set-upstream-to branch-name origin/branch-name。這就是多人協作的工作模式,一旦熟悉了,就非常簡單。

    總結

    到此,Git分支管理就學完了,整理一下所學的命令,大體如下:

    1234567891011

    git branch 查看當前分支git branch -v 查看每一個分支的最後一次提交git branch -a 查看本地和遠程分支的情況git branch --merged 查看已經與當前分支合併的分支git branch --no-merged 查看已經與當前分支未合併的分支git branch -r 查看遠程分支git branch dev 創建分支 devgit checkout dev 切換到分支devgit checkout -b dev 創建並切換分支devgit merge dev 名稱為dev的分支與當前分支合併git branch -d dev 刪除分支dev


    最近擼了個java的公眾號,學習資源超級多,視頻,電子書,最新開發工具一個都不能少,已全部分享到百度雲盤,求資源共享,打造一個學習方便,工作方便的java公眾號,開源開源,有需求的可以關注~撒花


    推薦閱讀:

    禮包|極市分享資源大整理
    有了Ta,你的圖片也可以下起雨來
    視頻轉場技巧大分享【PR學習乾貨】
    藝術乾貨:畫個可怕、不懷好意的吻,是種什麼體驗?|張小玉
    【乾貨碼頭】| 「易」神探:家中養魚的12條禁忌!

    TAG:工作 | 管理 | 獨立 | 博客 | 乾貨 | 獨立博客 |