
這篇博文是自己在學習git過程中的思考總結。本文僅僅代表個人的看法,如有不妥地方還請本文文末留言。 ??
GIT是什么
GIT是一個免費并且開源的分布式版本控制系統(tǒng),能夠高速有效的處理或小或大的項目。(以上的話是自己翻譯github官網(wǎng))
至今,自己用過了window系統(tǒng)的TortoiseSVN, mac系統(tǒng)的CornerStone,最近的大半年也在用GIT(主要管理自己的github項目)。比較下來,還是GIT優(yōu)勢比較明顯。
GIT跨平臺
GIT可以在不同的操作系統(tǒng)中使用。也許你注意到了,我在window上和mac系統(tǒng)上工作的時候是使用兩個不同的svn。如果我在linux上工作會不會又是一個呢。
GIT是分布式版本控制系統(tǒng),而svn是集中式版本控制系統(tǒng)
集中式版本控制系統(tǒng)是集中放在中央服務器上面的,而團隊的人需要從中央服務器上面拉取最新的代碼,然后進行開發(fā),最后推送到中央服務器上面,就像串聯(lián)的電路。而分布式版本控制系統(tǒng)沒有中央服務器,團隊的每個人的電腦就是一個完整的版本庫,就好像并聯(lián)的電路(自我理解)。
集中式版本控制系統(tǒng)必須聯(lián)網(wǎng)才能工作,如果是在局域網(wǎng)內(nèi)還好,帶寬足夠大,速度足夠快,但是遇到網(wǎng)速慢的話,那心里就一萬個羊駝??在蹦騰了。
集中式版本控制系統(tǒng)安全性比較低,如果中央系統(tǒng)崩潰了,那就有點悲催了。當然你不嫌麻煩,可以定期備份的啦。而分布式中央系統(tǒng)就比較安全,團隊的每個成員的電腦就是一個完整的版本庫。如果其中一個壞掉了,你可以從團隊另外一個的人員電腦那里拷貝一份就行了。對了,GIT也會有一臺中央的機子,主要是為了方便團隊的交流,它是可以不存在的。
GIT安裝
GIT支持不同的系統(tǒng),看者可以在鏈接https://git-scm.com/downloads中,找到和自己電腦系統(tǒng)匹配的GIT版本,下載安裝包后根據(jù)提示進行安裝。當然,GIT還提供圖形界面管理工具,看者也可以在鏈接中下載GUI Clients,如下圖所示--

根據(jù)提示安裝完成后,要驗證是否安裝成功??凑呖纱蜷_命令行工具,輸入
git --version命令,如果安裝成功,控制臺輸出安裝的版本號(當然,安裝前就應該輸入git --version查看是否安裝了git),我這里安裝的GIT版本是2.10.0。
GIT配置
GIT在使用前,需要進行相關的配置。每臺計算機上面只需要配置一次,程序升級的時候會保留配置信息。當然,看者可以在任何時候再次通過運行命令行來修改它們。
用戶信息
設置GIT的用戶名稱和郵件地址,這個很重要,因為每個GIT的提交都會使用這些信息,并且它會寫入到每一次的提交中。你可以在自己的倉庫中使用git log,控制臺上面顯示的每次的提交都有Author字段,它的值就是用戶名稱 <郵件地址>。方便查看某次的提交的負責人是誰。
$ git config --global user.name "你的用戶名"
$ git config --global user.email 你的郵箱地址
?? GIT一般和github配合使用,看者應該設置用戶名稱為你的github用戶名。當然,還有和gitlab等配合使用...
?? 如果配置中使用了--global選項,那么該命令只需要運行一次,因為之后無論你在該系統(tǒng)上做任何事情,GIT都會使用這些信息。但是,當你想針對特定項目使用不同的用戶名稱與郵件地址的時候,可以在那個倉庫目錄下運行不使用global選項的命令來配置。
檢查配置信息
通過git config --list命令可以列出所有GIT能找到的配置。如下:(我的git版本為2.10.0)
...
user.name=reng99
user.email=1837895991@qq.com
color.ui=true
core.repositoryformatversion=0
core.filemode=true
core.bare=false
...
當然,你可以通過git config <key>來檢查GIT的某一項配置。比如$ git config user.name。
幫助中心
在使用GIT的時候,遇到問題尋求幫助的時候,可以運行git help或git --help或git命令來查看。在控制臺上會展示相關的幫助啦。
usage:
...
start a working area (see also: git help tutorial)
...
work on the current change (see also: git help everyday)
...
examine the history and state (see alse: git help revisions)
...
grow,mark and tweak your common history
...
collaborate (see also: git help workflows)
...
更加詳細的內(nèi)容,請點擊傳送門
創(chuàng)建版本庫
版本庫又名倉庫(repository),可以理解成一個目錄,這個目錄里面所有文件都可以被GIT管理起來,每個文件的修改、刪除,GIT都能跟蹤,以便任何時刻都能可以追蹤歷史,或者在將來某個時刻可以還原。
創(chuàng)建一個版本庫,首先得選擇一個存放目錄的地方,我這里選擇了桌面,并且創(chuàng)建一個空的目錄。
$ cd desktop
$ mkdir -p learngit
$ cd learngit
$ pwd
/Users/reng/desktop/learngit
mkdir -p dirnanme是創(chuàng)建一個子目錄,這里的-p確保目錄的名稱存在,如果目錄不存在的就新建一個,如果你確定目錄不存在,直接使用mkdir dirname就可以了。pwd(Print Working Directory)是顯示當前目錄的整個路徑名。
然后,通過命令行git init,將創(chuàng)建的目錄變成GIT可以管理的倉庫:
$ git init
Initialized empty Git repository in /Users/reng/Desktop/learngit/.git/
初始化好倉庫后就可以愉快的玩耍了,但是,得先來了解下GIT整個工作流程先。
GIT工作流程
為了更好的學習,自己用Axure RP 8粗略的畫了下流程圖,如下--

本地倉庫(repo)包含工作區(qū)和版本庫,那么什么是工作區(qū)和版本庫呢?基本的流程又是什么呢?
工作區(qū)和版本庫
我們新建一個倉庫,就像我們新建的learngit倉庫,現(xiàn)在在里面添加一個文件README.md,用sublime打開learngit目錄。此時會出現(xiàn)如下圖的情況(當然你設置了其他東西例外)--

如上圖,出現(xiàn)的內(nèi)容就是工作區(qū)( 電腦上能看到的此目錄下的內(nèi)容),這里工作區(qū)只有
README.md一個文件。工作區(qū)有一個隱藏的目錄.git,這個不算工作區(qū),而是GIT的版本庫。版本庫又包括暫存區(qū)和GIT倉庫。暫存區(qū)是一個文件,保存了下次將提交的文件列表信息,而GIT倉庫目錄是GIT用來保存項目的元數(shù)據(jù)和對象數(shù)據(jù)庫的地方。這是GIT中最重要的部分,從其他計算機克隆倉庫的時候,拷貝的就是這里的數(shù)據(jù)。當執(zhí)行git add .或者git add path/to/filename的時候,文件從工作區(qū)轉(zhuǎn)到暫存區(qū);執(zhí)行git commit -m"here is the message described the file you add"的時候,文件從緩存區(qū)添加到GIT倉庫。
基本的工作流
基本的GIT工作流可以簡單總結如下--
- 在工作區(qū)目錄中修改文件
- 暫存區(qū)中暫存文件,將文件的快照放入暫存區(qū)域
- 提交更新,找到暫存區(qū)域的文件,將快照永久性存儲到GIT倉庫目錄
時光機穿梭
到目前為止,在自己創(chuàng)建的本地倉庫--learngit中已經(jīng)初具形態(tài)了。進入learngit,執(zhí)行ls,可看到目前倉庫中已有的文件README.md。
$ cd desktop/learngit
$ ls
README.md
$ cat README.md
## content
上面展示了本地learngit內(nèi)的相關的內(nèi)容。運行下git status查看現(xiàn)在的狀態(tài)。
$ git status
On branch master
nothing to commit, working tree clean
這時候會提示沒有內(nèi)容可以提交,工作區(qū)是干凈的。因為我之前已經(jīng)提交(git commit)過了。上面還提示了目前是位于主分支上面,GIT在初始化(git init)的時候會自動創(chuàng)建一個HEAD指針指向默認master分支,也只有一個分支,看者可以通過git branch查看。
現(xiàn)在,在README.md上添加一些內(nèi)容。
## content
### first change
此刻再通過git status查看當前狀態(tài)。
$ git status
On branch master
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.md
no changes added to commit (use "git add" and/or "git commit -a")
這時候顯示出一堆的東西,告訴我們現(xiàn)在是位于主分支上面,然后告訴我們修改的文件啊,可以使用的命令進行下一步的操縱。那么我們來進行下一步的操作了,git add . 或者 git add README.md將修改的文件添加到暫存區(qū)域。
$ git add .
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: README.md
對了,有時候需要在添加的之前(執(zhí)行git add . 或者 git add path/to/filename)的時候,需要看下修改了哪些內(nèi)容可以執(zhí)行下git diff。那么,現(xiàn)在先回退到修改的前一個版本。
$ git reset HEAD README.md
Unstaged changes after reset:
M README.md
$ git checkout -- README.md
$ ls
README.md
$ cat README.md
## content
回退正確,現(xiàn)在像上次那樣添加內(nèi)容### first change,然后執(zhí)行命令git diff來查看更改的內(nèi)容。
$ git diff
diff --git a/README.md b/README.md
index 75759ec..0bc52b9 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,3 @@
-## content
\ No newline at end of file
+## content
+
+### first change
\ No newline at end of file
現(xiàn)在就顯示了修改前的內(nèi)容---前為修改前的內(nèi)容,和修改后的內(nèi)容--+前修改后的內(nèi)容。查看完之后,覺得沒有問題了,就可以進行添加(git add),提交(git commit)。當然,一般不常用git diff的,因為自己修改的東西自己心里總有點數(shù)吧,可能合作中團隊的其他人需要查看文件前后的不同點就需要用到git diff啦。
版本回退
為了方便講解下版本回退,我先將上面添加的### first change提交以下--git add . && git commit -m "add first change"。下面通過git log就可以查看自己提交的記錄了。
$ git log
commit 5c2639ee54bd7c8ef2cbf186f5dc4798e72a4a67
Author: reng99 <1837895991@qq.com>
Date: Sun Dec 17 17:11:53 2017 +0800
init README.md
$ git add . && git commit -m "add first change"
[master 0ac49ba] add first change
1 file changed, 3 insertions(+), 1 deletion(-)
$ git log
commit 0ac49bae6ab55df9c05d0770de347665a2568f31
Author: reng99 <1837895991@qq.com>
Date: Mon Dec 18 15:26:06 2017 +0800
add first change
commit 5c2639ee54bd7c8ef2cbf186f5dc4798e72a4a67
Author: reng99 <1837895991@qq.com>
Date: Sun Dec 17 17:11:53 2017 +0800
init README.md
在上面中,自己先執(zhí)行了git log來顯示提交的日志,顯示只有一條,然后執(zhí)行了add和commit的命令,打印的內(nèi)容是現(xiàn)實主分支、commit的id、commit的信息、多少個文件的更改、多少個插入以及多少個刪除。之后再次執(zhí)行git log打印日志,顯示了兩次提交。?? 注意:當提交(commit)的次數(shù)較多之后,控制臺會顯示不下(最多現(xiàn)實4條)那么多的條數(shù),可以通過按鍵盤的向上或向下鍵查看日志的內(nèi)容,需要退出查看日志命令的話,在英文輸入法的狀態(tài)按下q,意思就是quit(退出)。
版本的回退就是改變HEAD指針的指向。通過git reset --hard HEAD^返回上一個版本,通過git reset --hard HEAD^^返回上上個版本...由此推論,往上100個版本的話就是100個^,當然,這樣你數(shù)到明天也未必數(shù)得正確,所以寫成git reset --hard HEAD~100。另外一種是,你知道提交的id,例如commit 5c2639ee54bd7c8ef2cbf186f5dc4798e72a4a67的前7位就是commit的id(5c2639e),執(zhí)行git reset --hard 5c2639e就回到此版本啦。
$ reng$ git reset --hard HEAD^
HEAD is now at 5c2639e init README.md
$ git log
commit 5c2639ee54bd7c8ef2cbf186f5dc4798e72a4a67
Author: reng99 <1837895991@qq.com>
Date: Sun Dec 17 17:11:53 2017 +0800
init README.md
$ ls
README.md
$ cat README.md
## content
現(xiàn)在你已經(jīng)回到了最初的版本,這里演示的是通過HEAD,你也可以通過commit id來實現(xiàn)的。執(zhí)行上面的代碼后,README.md文件里面只有一### content文字內(nèi)容,但是過了段時間后,你想恢復到原先的版本,通過git log命令行,控制臺顯示的以前的信息,通過它找不到回退前的commit id,怎么辦?GIT提供一個git reflog顯示提交的歷史記錄,在那里可以查看提交的id、HEAD的指針歷史和操作的信息記錄。下面演示回退到最新的版本(也就是commit -m "add first change")--
$ git log
commit 5c2639ee54bd7c8ef2cbf186f5dc4798e72a4a67
Author: reng99 <1837895991@qq.com>
Date: Sun Dec 17 17:11:53 2017 +0800
init README.md
$ git reflog
5c2639e HEAD@{0}: reset: moving to HEAD^
0ac49ba HEAD@{1}: commit: add first change
5c2639e HEAD@{2}: commit (initial): init README.md
$ git reset --hard 0ac49ba
HEAD is now at 0ac49ba add first change
$ ls
README.md
$ cat README.md
## content
### first
現(xiàn)在又回到了最新的版本,又能夠愉快的玩耍了。??
管理修改
GIT比其他版本控制系統(tǒng)設計優(yōu)秀,其中一點是--GIT跟蹤并管理的是修改,而非文件。
下面在README.md內(nèi)添加信息### second change。之后看下變化后的文件的狀態(tài)和差異等。
$ ls
README.md
$ cat README.md
## content
### first change
#### second change
$ git status
On branch master
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.md
no changes added to commit (use "git add" and/or "git commit -a")
$ git add README.md
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: README.md
此時,對README.md進行第三次的修改,添加內(nèi)容### third change。
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: README.md
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.md
$ cat README.md
## content
### first change
#### second change
### third change
$ git commit -m "test file modify"
[master 18f86ba] test file modify
1 file changed, 3 insertions(+), 1 deletion(-)
$ git status
On branch master
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.md
no changes added to commit (use "git add" and/or "git commit -a")
上面的演示流程是這樣的第一次修改(#### second change) -> git add -> 第二次修改(### third change) -> git commit。但是最后查看狀態(tài)的時候(git status),第二次的修改并沒有被提交上去。因為GIT管理的是修改,當使用git add命令的時候,在工作區(qū)的第一次修改被放入暫存區(qū),準備提交,但是在工作區(qū)的第二次修改并沒有放入到暫存區(qū),而git commit是將暫存區(qū)的修改提交到GIT倉庫,所以第二次修改的內(nèi)容是不會被提交的。這也是說明為什么可以多次添加(git add),一次提交(git commit)的原因了。
撤銷修改
文件的撤銷修改分成三種情況,一種是修改在工作區(qū)的內(nèi)容,一種是修改在暫存區(qū)的內(nèi)容,另一種是修改在GIT倉庫的內(nèi)容。也許會有看者說,不能修改在遠程庫中的內(nèi)容嗎?有啊,就是git add->git commit->git push將遠程倉庫的內(nèi)容覆蓋被,不過團隊人在克隆遠程庫下來的時候,還是可以查看到你提交的錯誤內(nèi)容的。我們現(xiàn)在只針對本地倉庫的三種情況談下自己的看法--
情況一:撤銷工作區(qū)的內(nèi)容
在管理修改中,自己的工作區(qū)還是沒有提交,此時想放棄當前工作區(qū)的編輯內(nèi)容執(zhí)行git checkout -- file。接著上面的內(nèi)容,我這里的工作區(qū)內(nèi)有的內(nèi)容是### third change,現(xiàn)在我要放棄第三次修改,只要執(zhí)行git checkout -- README.md就可以了。
$ git status
On branch master
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.md
no changes added to commit (use "git add" and/or "git commit -a")
$ ls
README.md
$ cat README.md
## content
### first change
#### second change
### third change
$ git checkout -- README.md
$ cat README.md
## content
### first change
#### second change
$ git status
On branch master
nothing to commit, working tree clean
情況二:撤銷暫存區(qū)的內(nèi)容
當你不但改亂了工作區(qū)的某個文件的內(nèi)容,還添加(git add)到了暫存區(qū)時,想丟棄修改,那么得分兩步來撤銷文件。先是通過git reset HEAD file,將暫存區(qū)的文件退回到工作區(qū),然后通過git checkout -- file放棄修改改文件的內(nèi)容。為了方便演示,我這里的暫存區(qū)沒什么內(nèi)容,所以添加內(nèi)容### tentative content并將它添加到緩存區(qū)。之后,演示將緩存區(qū)的內(nèi)容撤回--
$ cat README.md
## content
### first change
#### second change
### tentative content
$ git add .
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: README.md
$ git reset HEAD README.md
Unstaged changes after reset:
M README.md
$ git checkout -- README.md
$ cat README.md
## content
### first change
#### second change
$ git status
On branch master
nothing to commit, working tree clean
情況三:撤銷GIT倉庫的內(nèi)容
如果你不僅添加(git add)了內(nèi)容到暫存區(qū)并且提交(git commit)了內(nèi)容到GIT倉庫中了。你需要撤銷上一次的內(nèi)容,也就是要回退到上一個版本,執(zhí)行git reset --hard HEAD^就可以啦,詳細的內(nèi)容查看版本回退。如下--
$ git status
On branch master
nothing to commit, working tree clean
$ cat README.md
## content
### first change
#### second change
$ git reset --hard HEAD^
HEAD is now at 0ac49ba add first change
$ cat READMEmd
## content
### first change
遠程倉庫
遠程倉庫的使用能夠提高你和團隊的工作效率,無論何時何地,團隊的人員都可以在聯(lián)網(wǎng)的情況下將代碼進行拉取,修改和更新。因為我是使用github來管理項目的,所以我的遠程倉庫是放在github里面。這里默認看者已經(jīng)安裝了github,當然也可以用碼云、gitlab等。
本地庫添加到遠程庫
這點很容易,登錄自己注冊的github,如果打不開,請開下VPN。進入自己的首頁(https://github.com/username),點擊+號創(chuàng)建(new repository)一個名為learngit的倉庫(注意哦?? 名稱是本地倉庫已經(jīng)初始化過的,我這里本地有個同名初始化的learngit倉庫),其他的字段自選來填寫。點擊Create repository創(chuàng)建此遠程倉庫。緊接著就是進行本地倉庫和遠程倉庫的關聯(lián)啦,github很友好的提示了你怎么進行一個遠程倉庫的關聯(lián)。



現(xiàn)在按照上圖來關聯(lián)下遠程倉庫。
$ git remote add origin https://github.com/reng99/learngit.git
$ git push -u origin master
Counting objects: 6, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (6/6), 456 bytes | 0 bytes/s, done.
Total 6 (delta 0), reused 0 (delta 0)
To https://github.com/reng99/learngit.git
* [new branch] master -> master
Branch master set up to track remote branch master from origin.
注意?? 第一次向遠程倉庫(關聯(lián))push的時候是$ git push -u origin master,不能忽略-u,以后的push不用帶-u。至此,打開你的github的相關的倉庫就可以看到添加了README.md文件,我這里地址是https://github.com/reng99/learngit,因為我是使用markdown語法寫的,控制臺顯示的內(nèi)容和倉庫的顯示內(nèi)容有所區(qū)別啦。<del>(?? 后期我將learngit倉庫刪除啦,所以你訪問鏈接是找不到這個倉庫的,畢竟不想放一個沒什么內(nèi)容的倉庫在我的github上)</del>。
遠程庫克隆到本地
從遠程倉庫克隆東西到本地同樣很簡單,只需要進入你想克隆的倉庫,將倉庫的url復制下來(當然你也可以復制window.location.href的內(nèi)容),運行git clone address。現(xiàn)在我將本地桌面的learngit的倉庫刪除,然后從遠程將learngit克隆到本地。

$ cd desktop
$ rm -rf learngit
$ find learngit
find: learngit: No such file or directory
$ git clone https://github.com/reng99/learngit
Cloning into 'learngit'...
remote: Counting objects: 6, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 6 (delta 0), reused 6 (delta 0), pack-reused 0
Unpacking objects: 100% (6/6), done.
成功將gitlearn從遠程克隆下來,接下來又可以愉快的玩耍啦。
分支管理
分支管理允許創(chuàng)建另一條線/方向上開發(fā),能夠讓你在不影響他人工作的情況下,正常的工作。當在自己創(chuàng)建的分支中完成自己的功過后,合并到主分支就行了(git init初始化的時候已經(jīng)默認創(chuàng)建了master主分支)。一般團隊的合作是不在主分支上進行的,個人項目除外(個人理解)。
創(chuàng)建分支
當前learngit倉庫上只有一個分支,那就是master分支,看者可以通過git branch命令來查看當前的分支,git branch branchName命令來創(chuàng)建一個新的分支,我這里創(chuàng)建的是dev分支。
$ cd desktop/learngit
$ git branch
* master
$ git branch dev
$ git branch
dev
* master
現(xiàn)在已經(jīng)創(chuàng)建了dev分支,有兩個分支了,分支前面帶有一個星號的分支說明是當前的正在工作的分區(qū)。執(zhí)行上面的分支后,可以簡單的畫下現(xiàn)在的情況了,有個HEAD指針指向主分支的最新點,剛才新創(chuàng)建的dev分支我這里默認是一個dev的指針指向了dev分支的最新點。
.
. HEAD指針
. │
├────────*master
└────────dev
│
dev指針
切換分支
我們一般是很少在主分支進行工作的,所以在創(chuàng)建出新的分支之后,我們就切換到新的分支進行相關的工作??梢酝ㄟ^git checkout branchName切換到已經(jīng)存在的分支工作,通過分支前面的*可查看目前位于哪個分支內(nèi)?,F(xiàn)在我切換到創(chuàng)建的dev分支。
$ git branch
dev
* master
$ git checkout dev
Switched to branch 'dev'
$ git branch
* dev
master
合并分支
在創(chuàng)建好分支后,我們在新的分支上工作完成后,就需要往主分支上進行合并啦。我修改了分支dev上的README.md的內(nèi)容,就是添加文字### new branch content。合并分支可以分成兩個合并的方式,一種是本地合并到materz主分支之后,推送(push)到遠程庫,一種是直接將分支推送到遠程庫,在遠程庫進行合并。
本地合并推送
在合并分支前,需要切換到要合并到哪個分支(一般是master主分支),通過git merge branchName將需要的合并的分支合并到當前分支,我是將dev分支合并到master分支。
$ git branch
* dev
master
$ git checkout master
M README.md
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.
$ git merge dev
$ Already up-to-date.
$ git add .
$ git commit -m "merge dev branch"
[master d705e73] merge dev branch
1 file changed, 3 insertions(+), 1 deletion(-)
$ git push origin master
Counting objects: 3, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 282 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To https://github.com/reng99/learngit
0ac49ba..d705e73 master -> master
合并之后,此時,HEAD指針就指向了dev指針,也就是兩者同時指向了master主分支的最新處。具體的內(nèi)容參考傳送門
.
.
.
├────────*master
└────────dev
│
dev指針 ── HEAD指針
遠程庫推送合并
遠程庫內(nèi)合并的話,要先將dev的分支推送到遠程庫,然后在遠程庫進行合并。我這里在dev分支上添加了### add new branch content into again然后demo演示推送(git push origin dev)以及合并。
$ git branch
dev
* master
$ git checkout dev
Switched to branch 'dev'
$ git add .
$ git commit -m "add dev branch commit again"
[dev dc817c4] add dev branch commit again
1 file changed, 3 insertions(+), 1 deletion(-)
$ git push origin dev
Counting objects: 3, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 300 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To https://github.com/reng99/learngit
* [new branch] dev -> dev
接下來就是進入我的遠程learngit倉庫進行合并,你會看到下面圖示的提示。點擊Compare && pull request,然后寫點相關的comment(選填),點擊Create pull request。之后在綠色勾的提示下Merge pull request,緊接著點擊Confirm merge按鈕確定合并此分支,這時候返回主分支就可以看到dev內(nèi)合并的內(nèi)容了(后期我改動了dev的內(nèi)容)??凑呷绻吹貌幻靼祝约荷鲜謬L試一下唄!

完成后,你會看到learngit倉庫的Pull requests量為1,branches量為2。你可以點擊進入分支,在ALL branches里面查看分支的具體內(nèi)容。
刪除分支
在創(chuàng)建了分支,然后將分支的內(nèi)容合并到主分支后,分支的使命就完成了,你就可以將分支刪除了,這里的刪除個人認為可以是兩種,一種是本地倉庫的分支刪除,一種是遠程倉庫的分支的刪除。當然啦,留著分支也沒啥,可以留著唄<del>,自己認為有點礙眼</del>。
本地分支的刪除
在本地的learngit的目錄下,執(zhí)行命令行git branch -D branchName就可以刪除了。我這里刪除的是dev分支。注意?? ,刪除的分支不應該是當前工作的分支,需要切換到其他分支,我這里切換的是master分支,畢竟我只有兩個分支呢。
$ git branch
* dev
master
$ git branch -D dev
error: Cannot delete branch 'dev' checked out at '/Users/reng/desktop/learngit'
$ git checkout master
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.
$ git branch
dev
* master
$ git branch -D dev
Deleted branch dev (was dc817c4).
$ git branch
* master
遠程庫分支的刪除
刪除遠程庫的分支,只要執(zhí)行git push origin :branchName命令就行了?,F(xiàn)在我要刪除我遠程庫中的dev分支,執(zhí)行git push origin :dev。
$ git push origin :dev
To https://github.com/reng99/learngit
- [deleted] dev
此時,打開我的遠程庫learngit,發(fā)現(xiàn)之前的Pull requests量為0,branch量為1。
重命名分支
通過git branch -m oldBranchName newBranchName來重命名分支。我這里沒有分支了,現(xiàn)在創(chuàng)建一個reng分支,然后將它重命名為dev分支。
$ git branch
* master
$ git branch reng
$ git branch
* master
reng
$ git branch -m reng dev
$ git branch
dev
* master
解決沖突
在我們開發(fā)的時候,不知道分支和分支之間的進度情況是什么,難免會產(chǎn)生沖突。當產(chǎn)生沖突的時候,就得將沖突的內(nèi)容更正,然后提交。為了方便演示,我將本地的learngit刪除,重新拉取遠程的gitlearn倉庫(因為我不知道我之前在本地倉庫做的修改是啥,對了,我將遠程的分支刪除了,只剩下master主分支)??寺∠聛砗螅绻€存在本地分支,也將它刪除,之后我將在master和dev分支中重新填充里面的README.md的內(nèi)容。
$ cd desktop
$ git clone https://github.com/reng99/learngit.git
Cloning into 'learngit'...
remote: Counting objects: 43, done.
remote: Compressing objects: 100% (17/17), done.
remote: Total 43 (delta 4), reused 38 (delta 1), pack-reused 0
Unpacking objects: 100% (43/43), done.
$ cd learngit
$ git branch
* master
$ ls
README.md
$ cat README.md
## master branch content
$ git add .
$ git commit -m "add master branch content"
[master 1cfa0aa] add master branch content
1 file changed, 1 insertion(+), 1 deletion(-)
$ git push origin master
Counting objects: 3, done.
Writing objects: 100% (3/3), 271 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To https://github.com/reng99/learngit.git
d2f936f..1cfa0aa master -> master
$ git branch dev
$ git checkout dev
Switched to branch 'dev'
$ cat README.md
## master branch content
### dev branch content
$ git add .
$ git commit -m "add dev branch content"
[dev 80faf6d] add dev branch content
1 file changed, 2 insertions(+)
$ git checkout master
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.
$ cat README.md
## master content
### new master branch content
$ git add .
$ git commit -m "change master content"
[master ec18715] change master content
1 file changed, 3 insertions(+), 1 deletion(-)
$ git merge dev
Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
Automatic merge failed; fix conflicts and then commit the result.
README.md文件中沖突內(nèi)容--
<<<<<<< HEAD (當前更改)
## master content
### new master branch content
=======
## master branch content
### dev branch content
>>>>>>> dev (傳入更改)
手動修改了README.md文件中沖突的內(nèi)容--
## master branch content
### new master branch content
### dev branch content
然后命令行執(zhí)行--
$ git add .
$ git commit -m "fix confict content"
[master dd848b4] fix confict content
$ git log --graph
* commit 980788b7690d8bcf14610072fc072460bee7e9f1
|\ Merge: c49d09e 2929dca
| | Author: reng99 <1837895991@qq.com>
| | Date: Thu Dec 21 11:14:10 2017 +0800
| |
| | fix confict content
| |
| * commit 2929dca91ef8f493adba7744cdad19656538334f
| | Author: reng99 <1837895991@qq.com>
| | Date: Thu Dec 21 11:11:49 2017 +0800
| |
| | add dev branch content
| |
* | commit c49d09e33e7098d67b59c845d18e9c6f8a8f4fea
|/ Author: reng99 <1837895991@qq.com>
| Date: Thu Dec 21 11:12:50 2017 +0800
|
| change master content
|
* commit b07f0be8280e4e437cccf2a3f8fac6beef03ff41
| Author: reng99 <1837895991@qq.com>
| Date: Thu Dec 21 11:10:51 2017 +0800
|
:
上面操作過程是,我先從遠程庫中克隆learngit倉庫到本地,目前的本地learngit的分支只有master分支,然后我在master分支的README.md中添加相關的文字(見代碼),接著把它推送到遠程庫。然后創(chuàng)建并切換dev分支,在README.md文件中添加新內(nèi)容(見代碼),接著將它提交到GIT倉庫。又切換到master分支,修改README.md到內(nèi)容(見代碼),提交到GIT倉庫后開始執(zhí)行merge命令合并dev分支的內(nèi)容。此時,產(chǎn)生了沖突,這就需要手動將沖突的內(nèi)容解決,重新commit到GIT倉庫,最后你就可以提交到遠程庫了(這步我沒有演示,也就是git push origin master一行命令行的事情)。最后我還使用git log ----graph打印出整個分支合并圖(從下往上看),方便查看。?? 此時退出git log --graph是書寫英文狀態(tài)按鍵盤的q鍵。
說這么多,目的只有一個 --> 產(chǎn)生沖突后,需要手動調(diào)整??
分支管理策略
先放上一張分支管理策略圖,然后再慢慢講解相關的內(nèi)容...

在分支管理中,我們不斷的新建分支,開發(fā),合并分支,刪除分支的操作。這里需要注意合并分子的操作,之前我們進行分支的時候是直接將dev開發(fā)的分支使用git merge dev進行合并,這樣有個缺點:我們看不出分支信息。因為在默認情況下,合并分支的時候,GIT是使用了Fast Foward的模式,在這種模式下,刪除分支后,會丟掉分支的信息。下面我重新克隆下我遠程learngit倉庫,然后創(chuàng)建并更改dev分支的信息,使用默認的模式進行合并。
$ git branch
* master
$ git branch dev
$ git checkout dev
Switched to branch 'dev'
$ cat README.md
## master branch content
### new master branch content
### dev branch content
### new dev branch content
$ git add .
$ git commit -m "add new dev contentt"
[dev 750e1f1] add new dev content
1 file changed, 1 insertion(+)
$ git checkout master
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.
$ git merge dev
Updating 980788b..750e1f1
Fast-forward
README.md | 1 +
1 file changed, 1 insertion(+)
$ git log --graph
* commit 750e1f17854872eed4d6cff8315e404079ecb18f
| Author: reng99 <1837895991@qq.com>
| Date: Fri Dec 22 10:05:36 2017 +0800
|
| add new dev content
|
* commit 980788b7690d8bcf14610072fc072460bee7e9f1
...
上面的合并就是將master分支上面的HEAD指向dev指針,如下:
# 記錄是從上往下
- before merge
master
* (begin)
|
|
*
\
\
*
|
|
* (end)
dev
- after merge
master
* (begin)
|
|
*
|
|
*
|
|
* (end)
為了保留分支的情況,保證版本演進的清晰,我們就得使用普通模式合并,也就是在Fast Foward的模式基礎上加上--no-ff參數(shù),即git merge --no-ff branchName,不過我們一般加上你合并的相關信息,即git merge --no-ff -m "your msg here" banchName。現(xiàn)在更改dev分支的內(nèi)容,再進行合并。
$ git checkout dev
Switched to branch 'dev'
$ cat README.md
## master branch content
### new master branch content
### dev branch content
### new dev branch content
### merge with no-ff
$ git add .
$ git commit -m "add no-ff mode content"
[dev 80b628c] add no-ff mode content
1 file changed, 2 insertions(+), 1 deletion(-)
$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)
$ git merge dev --no-ff -m "merge with no-ff" dev
Merge made by the 'recursive' strategy.
README.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
$ git log --graph
* commit 98746d93a9b64ea02b8ff1c7f0fa5e915405c0e6
|\ Merge: 750e1f1 80b628c
| | Author: reng99 <1837895991@qq.com>
| | Date: Fri Dec 22 14:39:32 2017 +0800
| |
| | merge with no-ff
| |
| * commit 80b628c334618711b77da81fa805ffc246a2cf7d
|/ Author: reng99 <1837895991@qq.com>
| Date: Fri Dec 22 14:38:17 2017 +0800
|
| add no-ff mode content
|
* commit 750e1f17854872eed4d6cff8315e404079ecb18f
...
使用--no-ff參數(shù)的普通模式合并,會執(zhí)行正常合并,在master主分支上面會生成一個新的節(jié)點,如下(我上面的分支管理策略圖里面的合并就是使用了普通的模式):
# 記錄是從上往下
- --no-ff合并
master
* (before)
|
|
*
|\
| \
| *dev
| |
| |
| *
| /
|/
* (after)
我們在開發(fā)中,分支管理可以分成master主分支、dev開發(fā)分支、feature功能分支、release預發(fā)布分支、hotfixes修補bug分支。其中功能分支、預發(fā)布分支和修補bug分支可以歸為臨時分支。臨時分支在進行分支的合并之后就可以被刪除了。下面就一一講解自己眼中的各種分支。
主分支master
主分支是在你初始化倉庫的時候(git init),自動生成的一個master分支,刪除不了的哦(演示待會給)。主分支是有且僅有一個,也是發(fā)布上線的分支,團隊合作的最終代碼都會在master主分支上面體現(xiàn)出來。也許你也注意到了分支管理策略圖里面的主分支會被打上TAG的標簽,這是為了方便到某個時間段對版本的查找,標簽tag的學習總結后面給出。
# 記錄是從上往下
master
|
|
*(tag 1.0)
|
|
*(tag 1.1)
|
|
*(tag 1.2)
下面代碼演示下不能放刪除master的情況:
$ cd learngit
$ git branch
dev
* master
$ git branch -D master
error: Cannot delete branch 'master' checked out at '/Users/reng/desktop/learngit'
開發(fā)分支develop
在開發(fā)的過程中,項目合作者應該保持自己本地有一個開發(fā)環(huán)境的分支,在進行分支開發(fā)之前,需要進行git pull拉取master主分支的最新內(nèi)容,或者通過其他的方法。在獲取到最新的內(nèi)容之后才可以進行本地的新功能的開發(fā)。在開發(fā)完成后將內(nèi)容merge到主分支之后,不用將dev分支刪除,因為你開發(fā)的就是在這里進行,何必刪除后再新建一個開發(fā)環(huán)境的分支呢。
接著上面的情況,我目前已經(jīng)擁有了dev開發(fā)分支:
$ cd learngit
$ git branch
dev
* master
功能(特性)分支feature
一個軟件就是一個個功能疊加起來的,在軟件的開發(fā)中,我們總不能在主分支開發(fā),將主分支搞亂吧。當然,你可以在dev分支中開發(fā),一般新建功能分支來開發(fā),然后功能開發(fā)完再合并到dev分支,之后刪除功能分支。需要的時候就可以將dev開發(fā)分支合并到master主分支,這樣就隨時保證dev分支功能的完整性。
下面演示功能分支user開發(fā)(隨便寫點內(nèi)容)的合并(這里也演示了合并到master主分支,跳過了release分支的測試),刪除。
$ git checkout dev
Switched to branch 'dev'
$ git branch user
$ git branch
* dev
master
user
$ git checkout user
Switched to branch 'user'
$ cat README.md
## master branch content
### new master branch content
### dev branch content
### new dev branch content
### merge with no-ff
### function user
$ git add .
$ git commit -m "function user was acheive"
[user 26beda3] function user was acheive
1 file changed, 2 insertions(+), 1 deletion(-)
$ git checkout dev
Switched to branch 'dev'
$ git merge --no-ff -m "merge user feature" user
Merge made by the 'recursive' strategy.
README.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 3 commits.
(use "git push" to publish your local commits)
$ git merge --no-ff -m "merge dev branch" dev
Merge made by the 'recursive' strategy.
README.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
$ git log --graph
* commit f15a1e9012635fc21e944ab76c4cd4bbd539f82f
|\ Merge: 98746d9 0ca83c6
| | Author: reng99 <1837895991@qq.com>
| | Date: Fri Dec 22 16:35:43 2017 +0800
| |
| | merge dev branch
| |
| * commit 0ca83c654df64724743a966f5f0989477e504cbc
| |\ Merge: 80b628c 26beda3
| | | Author: reng99 <1837895991@qq.com>
| | | Date: Fri Dec 22 16:33:27 2017 +0800
| | |
| | | merge user feature
| | |
| | * commit 26beda3b8246e047f10ac0461ca11d1a6f132819
| |/ Author: reng99 <1837895991@qq.com>
| | Date: Fri Dec 22 16:31:41 2017 +0800
| |
| | function user was acheive
| |
* | commit 98746d93a9b64ea02b8ff1c7f0fa5e915405c0e6
|\ \ Merge: 750e1f1 80b628c
| |/ Author: reng99 <1837895991@qq.com>
:
$ git branch -D user
Deleted branch user (was 26beda3).
$ git branch
dev
* master
預發(fā)布分支release
在進行一系列的功能的開發(fā)和合并后,在滿足迭代目標的時候,就可以打包送測了。這里就需要一個預發(fā)布分支release。預發(fā)布分支是指在發(fā)布正式版本之前( 即合并到master分支之前,可查看上面分支管理策略圖),需要一個有預發(fā)布的版本(可以理解為灰度環(huán)境)進行測試。
預發(fā)布環(huán)境是從dev分支上面分出來的,預發(fā)布結束之后,必須合并到dev和master分支上面。這里我就不演示了,跟功能分支差不多,就是合并的時候要合并到dev和master上,這時候dev分支和master的同步的代碼,就不需要將dev分支合并到master了。最后將預發(fā)布分支刪除掉。
修復bug分支 bug/hotfixes
在寫代碼的過程中,由于種種原因 -> 比如功能考慮不周全,版本上線時間有限,產(chǎn)品突然改需求等,我們寫的代碼就出現(xiàn)一些或大或小的bug或者需要緊急修復。那么我們就可以使用bug分支(其實就是新建一個分支處理bug而已啦,命名隨意起的),然后在這個分支上處理編碼出現(xiàn)的問題。我在分支管理策略圖上面已經(jīng)展示了一種出現(xiàn)bug的情況 -> 就是在測試發(fā)布版本看似沒問題的情況下,將release版本整合到master和dev中,這時候火眼精金發(fā)現(xiàn)了遺留的一個bug,然后新建一個bug分支處理,再合并到master和dev中,之后將bug分支移除啦。
在開發(fā)的過程中,無論咋樣都是這樣 : 新建bug分支 -> 把分支合并 -> 刪除分支,這里的demo就不演示了,可以參考上面的功能(特性)分支feature。
這里需要注意??的一點,當在開發(fā)的過程中,開發(fā)到一定的程度,需要停下來需改緊急的bug,那么需要停下手頭的工作需改bug啦。這時候需要將工作現(xiàn)場儲藏(stash功能)起來,等以后回復現(xiàn)場了后接著工作?,F(xiàn)在我在原先的gitlearn倉庫中README.md文件文末添加### modify content內(nèi)容來進行演示。
$ cd desktop
$ cd learngit
$ cat README.md
## master branch content
### new master branch content
### dev branch content
### new dev branch content
### merge with no-ff
$ git status
On branch dev
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.md
no changes added to commit (use "git add" and/or "git commit -a")
$ git stash
Saved working directory and index state WIP on dev: 80b628c add no-ff mode content
HEAD is now at 80b628c add no-ff mode content
$ git status
On branch dev
nothing to commit, working tree clean
然后過段時間(這里省略修改的演示),代碼已經(jīng)修改好合并后,需要回到最新的內(nèi)容區(qū)域進行工作,這就需要還原最新的內(nèi)容了,demo如下:
$ cd learngit
$ git stash list
stash@{0}: WIP on dev: 80b628c add no-ff mode content
$ git stash pop
On branch dev
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.md
no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (9e85bcc8435ae38c17db59ddc3cd8401af404827)
$ git stash list
?? git stash不僅可以隱藏工作區(qū)的內(nèi)容,也可以隱藏暫存區(qū)的內(nèi)容。git stash list是查看隱藏的列表。git stash pop是將隱藏的內(nèi)容恢復并刪除,git stash pop相當于git stash apply && git stash drop,這里的git stash apply是恢復隱藏內(nèi)容,git stash drop是刪除隱藏內(nèi)容。
多人協(xié)作
簡單談下自己git協(xié)作的過程吧。在負責人將搭建好的倉庫上傳到遠程的倉庫后(一般是包含了master默認的分支和dev分支),自己將遠程倉庫克隆到本地,然后在本地的倉庫上新建一個dev分支,將遠程的dev分支重新拉取下git pull origin dev,開發(fā)完成后就可以提交自己的代碼到遠程的dev分支了,如果提交之前或者之后需要修改bug或者添加新的需求的話,需要新建一個相關的分支并完成開發(fā),將他們合并到本地dev分支后上傳到遠程dev分支。如果新建的遠程倉庫中只有master分支,我是這樣處理的:依然要在本地新建一個dev分支,然后在完成特定版本的開發(fā)后,將分支合并到本地master分支然后再推送到遠程master分支,本地的dev分支保留哦。我自己比較偏向于第一種情況。
標簽管理
發(fā)布一個版本前,為了唯一確定時刻的版本,我們通常在版本庫中打一個標簽(tag),方便在發(fā)布版本以后,可以在某個時刻將某個歷史的版本提取出來(因為標簽tag也是版本庫的一個快照)。
創(chuàng)建標簽
創(chuàng)建標簽是默認在你切換的分支最新提交處創(chuàng)建的。我這里在本地桌面的learngit倉庫的master分支上打一個v1.0標簽。
$ cd desktop/learngit
$ git branch
* dev
master
$ git checkout master
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.
$ git tag
$ git tag v1.0
$ git tag
v1.0
當然,你可以在非新commit的地方進行標簽。這就需要你查找到需要打標簽處的commit的id,然后執(zhí)行git tag tagName commitId。這里我隨意找master分支中的commit id進行標簽v0.9的標簽創(chuàng)建。
$ git log --pretty=oneline --abbrev-commit
f15a1e9 merge dev branch
0ca83c6 merge user feature
26beda3 function user was acheive
98746d9 merge with no-ff
...
現(xiàn)在在commit id為 98746d9處打標簽。
$ git tag v0.9 98746d9
$ git tag
v0.9
v1.0
操作標簽
在上面創(chuàng)建標簽,我們已經(jīng)有了標簽v0.9 v1.0。有時候我們標簽打錯了,需要進行刪除,那么就得更改啦,運用git tag -d tagName
$ git tag -d v0.9
Deleted tag 'v0.9' (was 98746d9)
$ git tag
v1.0
$ git tag v0.8 80b628c -m "version 0.8"
$ git tag
v0.8
v1.0
$ git show v0.8
$ git show v0.8
tag v0.8
Tagger: reng99 <1837895991@qq.com>
tag v0.8
Tagger: reng99 <1837895991@qq.com>
Date: Wed Dec 27 16:07:46 2017 +0800
version 0.8
在上面的演示中,我刪除了v0.9,然后在創(chuàng)建v0.8的時候追加了打標簽的信息,之后使用git show tagName查看簽名信息。
我們還可以進行分支切換標簽,類似于分支的切換,我這里打的兩個標簽的內(nèi)容是不同的,我可以通過觀察內(nèi)容的改表來得知時候成功切換標簽了。
$ git tag
v0.8
v1.0
$ git checkout v1.0
HEAD is now at f15a1e9... merge dev branch
$ cat README.md
## master branch content
### new master branch content
### dev branch content
### new dev branch content
### merge with no-ff
### function user
$ git checkout v0.8
Previous HEAD position was f15a1e9... merge dev branch
HEAD is now at 80b628c... add no-ff mode content
$ cat README.md
## master branch content
### new master branch content
### dev branch content
### new dev branch content
### merge with no-ff
在確認好標簽后,就可以像遠程推送標簽了,我這里推送v1.0。
$ git push origin v1.0
Total 0 (delta 0), reused 0 (delta 0)
To https://github.com/reng99/learngit.git
* [new tag] v1.0 -> v1.0
上面是使用git push origin tagName推送特定的tag到遠程庫,但是我們能不能推送全部的tag呢?答案是肯定的,看者可以通過git push origin --tags進行推送。有時候,我們推送了tag標簽到遠程庫中了,現(xiàn)在想刪除掉怎么辦?這個就略微麻煩點,我們不能像上面提到的刪除本地庫的標簽那樣,通過git tag -d tagName那樣,而是通過git push origin :refs/tags/tagName,這里不演示,如果看者感興趣可以自己來把弄一下哦。