1.Git基本管理
git常用的基本操作
1.1提交數據
我們可以簡單的把工作目錄理解成是一個被Git服務程序管理的目錄,Git會時刻的追蹤目錄內文件的改動,另外在安裝好了Git服務程序后,默認就會創建好了一個叫做master的分支,我們直接可以提交數據到了
1.創建本地工作目錄demo,並初始化為git工作目錄
[root@git-node1 ~]# mkdir demo [root@git-node1 ~]# cd demo/ [root@git-node1 git-demo]# git init Initialized empty Git repository in /root/demo/.git/
2.創建readme.txt文件
[root@rabbitmq demo]# vi index.html
welcome nulige
3.查看git狀態
[root@rabbitmq demo]# git status # On branch master # Changed but not updated: # (use "git add <file>..." to update what will be committed) # (use "git checkout -- <file>..." to discard changes in working directory) # # modified: index.html # no changes added to commit (use "git add" and/or "git commit -a")
4.提示使用git add添加文件至暫存區
[root@rabbitmq demo]# git add index.html #添加到暫存區 [root@rabbitmq demo]# git status #查看變更狀態 # On branch master # Changes to be committed: # (use "git reset HEAD <file>..." to unstage) # # modified: index.html #
5.使用git cmmit提交暫存區文件至git版本倉庫 -m:提交描述信息(提交至遠程需添加遠程倉庫)
[root@git-node1 demo]# git commit -m "the first commit index.html" [master(根提交) 85bd268] the first commit index.html 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 index.html
1.2移除數據
有些時候會將已經添加到暫存區的文件移除,但仍然希望文件在工作目錄中不丟失,換句話說,就是把文件從追蹤清單中刪除。
1.建立新文件
[root@git-node1 demo]# vi index.php
Hello nuloge
2.添加新文件至暫存區
[root@git-node1 demo]# git add index.php #添加到暫存區 [root@git-node1 demo]# git status # On branch master # Changes to be committed: # (use "git reset HEAD <file>..." to unstage) # # new file: index.php #
3.將新文件從暫存區撤出(並不會刪除工作目錄內的數據)
[root@git-node1 demo]# git add index.php #文件添加到暫存區 [root@git-node1 demo]# git status #查看文件狀態 # On branch master # Changes to be committed: # (use "git reset HEAD <file>..." to unstage) # # new file: index.php [root@git-node1 demo]# git reset HEAD index.php #從暫存區撤消文件 [root@git-node1 demo]# git status //index.php #文件狀態為未跟蹤 fatal: '//index.php' is outside repository [root@git-node1 demo]# git status #查看文件狀態 # On branch master # Untracked files: # (use "git add <file>..." to include in what will be committed) # # index.php nothing added to commit but untracked files present (use "git add" to track) #提交為空,但是存在尚未跟蹤的文件(使用 "git add" 建立跟蹤) #如果想將文件數據從git暫存區和工作目錄一起刪除,可以做如下操作。
[root@git-node1 demo]# git add index.php #添加文件到暫存區 [root@git-node1 demo]# git rm index.php #文件已經放入暫存區域,防止誤刪 error: 'index.php' has changes staged in the index #有變更已暫存至索引中 (use --cached to keep the file, or -f to force removal) #(使用 --cached 保存文件,或用 -f 強制刪除) [root@git-node1 demo]# git rm -f index.php #使用追加強制刪除-f參數或者-cache保存 rm 'index.php' [root@git-node1 demo]# git status #查看當前狀態 # On branch master #位於分支 master nothing to commit (working directory clean) #無文件要提交,干凈的工作區 [root@git-node1 demo]# ls #文件已經不存在 index.html
1.3移動數據
有些時候會將已經添加到暫存區的文件重新修改名稱,可使用如下操作完成。
1.git重新命名文件名稱,使用git mv操作。
[root@git-node1 demo]# ls
index.html
[root@git-node1 demo]# git mv index.html index.php
2.使用git status查看更改狀態
[root@git-node1 demo]# git status # On branch master # Changes to be committed: # (use "git reset HEAD <file>..." to unstage) # # renamed: index.html -> index.php #
3.使用git commit 提交git倉庫,並描述提交信息
[root@git-node1 demo]# git commit -m "chaned name index.html->index.php" [master ca24753] chaned name index.html->index.php 1 files changed, 0 insertions(+), 0 deletions(-) rename index.html => index.php (100%)
1.4歷史數據
在提交了若干更新之后,想回顧下提交歷史,可以使用 git log 命令查看提交歷史記錄。
1.查看提交歷史記錄
[root@git-node1 demo]# git log commit ca24753faac356ae926c89f6d8c2da2ee69d67ec Author: nulige <nulige@nulige.com> Date: Mon May 8 20:48:46 2017 +0800 chaned name index.html->index.php commit 90596d4d1fdcc37e175d18778e2f07a98c42f86b Author: root <root@git-node1.(none)> Date: Mon May 8 20:26:08 2017 +0800 the first commit index.html
2.查看歷史1次提交記錄
# -n (n是一個正整數),查看最近n次的提交信息
[root@git-node1 demo]# git log -1 commit ca24753faac356ae926c89f6d8c2da2ee69d67ec Author: nulige <nulige@nulige.com> Date: Mon May 8 20:48:46 2017 +0800 chaned name index.html->index.php commit 90596d4d1fdcc37e175d18778e2f07a98c42f86b Author: root <root@git-node1.(none)> Date: Mon May 8 20:26:08 2017 +0800 the first commit index.html
3.顯示提交的內容差異,例如僅查看最近2次差異
[root@git-node1 demo]# git log -p -2 #顯示最近二次更新 commit ca24753faac356ae926c89f6d8c2da2ee69d67ec Author: nulige <nulige@nulige.com> Date: Mon May 8 20:48:46 2017 +0800 chaned name index.html->index.php diff --git a/index.html b/index.html deleted file mode 100644 index 6e599f4..0000000 --- a/index.html +++ /dev/null @@ -1 +0,0 @@ -welcome nulige diff --git a/index.php b/index.php new file mode 100644 index 0000000..6e599f4 --- /dev/null +++ b/index.php @@ -0,0 +1 @@ +welcome nulige : commit ca24753faac356ae926c89f6d8c2da2ee69d67ec Author: nulige <nulige@nulige.com> Date: Mon May 8 20:48:46 2017 +0800 chaned name index.html->index.php diff --git a/index.html b/index.html deleted file mode 100644 index 6e599f4..0000000 --- a/index.html +++ /dev/null @@ -1 +0,0 @@ -welcome nulige diff --git a/index.php b/index.php new file mode 100644 index 0000000..6e599f4 --- /dev/null +++ b/index.php @@ -0,0 +1 @@ +welcome nulige commit 90596d4d1fdcc37e175d18778e2f07a98c42f86b Author: root <root@git-node1.(none)> Date: Mon May 8 20:26:08 2017 +0800 : 退出按 q
#--stat: 列出文件的修改行數
[root@git-node1 demo]# git log --stat commit ca24753faac356ae926c89f6d8c2da2ee69d67ec Author: nulige <nulige@nulige.com> Date: Mon May 8 20:48:46 2017 +0800 chaned name index.html->index.php index.html | 1 - index.php | 1 + 2 files changed, 1 insertions(+), 1 deletions(-) commit 90596d4d1fdcc37e175d18778e2f07a98c42f86b Author: root <root@git-node1.(none)> Date: Mon May 8 20:26:08 2017 +0800 the first commit index.html index.html | 1 + 1 files changed, 1 insertions(+), 0 deletions(-)
1 [root@git-node1 demo]# git log --pretty=oneline 2 95734131860ef7c9078b8a208ff6437d0952380b chnged name index.html->index.php 3 85bd2680bd4b70aeded9dbd230c07ab086712ff9 the first commit index.htm 4 可以使用format參數來指定具體的輸出格式,這樣非常便於后期編程的提取分析哦,常用的格式有: 5 %s 提交說明。 6 %cd 提交日期。 7 %an 作者的名字。 8 %cn 提交者的姓名。 9 %ce 提交者的電子郵件。 10 %H 提交對象的完整SHA-1哈希字串。 11 %h 提交對象的簡短SHA-1哈希字串。 12 %T 樹對象的完整SHA-1哈希字串。 13 %t 樹對象的簡短SHA-1哈希字串。 14 %P 父對象的完整SHA-1哈希字串。 15 %p 父對象的簡短SHA-1哈希字串。 16 %ad 作者的修訂時間。 17 定制詳細的日志輸出
18 [root@git-node1 demo]# git log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr)%Creset %cn"' --abbrev-commit --date=relative 19 * 5f3f588 - (HEAD, origin/master, –r, tt, master) merge branch linux readme.txt (68 分鍾之前) xulia 20 |\ 21 | * 4580c3b - (linux) touch readme.txt (80 分鍾之前) xuliangwei" 22 * | 4c7a145 - touch readme.txt (72 分鍾之前) xuliangwei" 23 |/ 24 * 9573413 - (tag: v1.0.0) chnged name index.html->index.php (5 小時之前) xuliangwei" 25 * 85bd268 - the first commit index.html (5 小時之前) xuliangwei" 26 配置.git/config文件,增加如下2行,以后即可使用 git hlog代替如上復雜命令 27 [root@git-node1 demo]# tail -2 .git/config 28 [alias] 29 hlog = log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr)%Creset %cn' --abbrev-commit --date=relative 30 //以后使用git hlog即可實現復雜的命令 31
1.5還原數據(稱為:回滾)
將文件hello boos寫成了hello nulige,但是已經提交至遠程倉庫,現在想要撤回重新提交。
(本小結涉及遠程倉庫,可先學習后面章節,在回頭學習本章節)
1.查看index.html文件
[root@git-node1 demo]# git checkout -b dev //內容存在dev分支 [root@git-node1 demo]# cat index.html hello boss [root@git-node1 demo]# echo "hello nulige" >> index.html
2.追加內容至index.html,並提交至遠程倉庫
[root@git-node1 demo]# git add index.html
[root@git-node1 demo]# git commit -m "hello nulige" [master 1941990] boss dou 1 file changed, 1 insertion(+)
[root@git-node1 demo]# git push origin dev Counting objects: 5, done. Compressing objects: 100% (2/2), done. Writing objects: 100% (3/3), 298 bytes | 0 bytes/s, done. Total 3 (delta 0), reused 0 (delta 0) To git@git-node1:root/git_demo.git 507bf99..1941990 dev -> dev
3.此時覺得寫的不妥,想還原上一次提交的文件快照,找出歷史信息
[root@git-node1 demo]# git log -2 * 1941990 - (HEAD, origin/dev, dev) boss dou (2 分鍾之前) nulige * 507bf99 - hello (30 分鍾之前) nulige
4.回滾:找到歷史還原點的值后,就可以還原(值不寫全,系統會自動匹配)
[root@git-node1 demo]# git reset --hard 507bf99 #從暫存區那個點,回滾到工作區 HEAD 現在位於 507bf99 hello [root@git-node1 nulige]# git push origin dev -f //強制推送至dev分支 [root@git-node1 demo]# cat index.html hello boss
5、git reflog 用來記錄你的每一次命令
[root@git-node1 Bastion-of-machine]# git reflog 08f962f HEAD@{0}: commit (initial): first commit
1.6 刪除操作
在Git中,刪除也是一個修改操作,我們實戰一下,先添加一個新文件test.txt到Git並且提交:
[root@git-node1 demo]# vi test.txt
I am nulige
添加到暫存區
[root@git-node1 demo]# git add . [root@git-node1 demo]# git commit -m "add test.txt" [master ac0cb7b] add test.txt 2 files changed, 2 insertions(+), 0 deletions(-) create mode 160000 Bastion-of-machine create mode 100644 test.txt
一般情況下,你通常直接在文件管理器中把沒用的文件刪了,或者用rm命令刪了
[root@git-node1 demo]# rm test.txt rm:是否刪除普通文件 "test.txt"? #回車
#這個時候,Git知道你刪除了文件,因此,工作區和版本庫就不一致了,git status命令會立刻告訴你哪些文件被刪除了: [root@git-node1 demo]# git status # On branch master # Changed but not updated: # (use "git add/rm <file>..." to update what will be committed) # (use "git checkout -- <file>..." to discard changes in working directory) # # deleted: index.php # deleted: test.txt # no changes added to commit (use "git add" and/or "git commit -a")
現在你有兩個選擇,一是確實要從版本庫中刪除該文件,那就用命令git rm刪掉,並且git commit:
[root@git-node1 demo]# ll 總用量 8 drwxr-xr-x 3 root root 4096 5月 8 22:12 Bastion-of-machine -rw-r--r-- 1 root root 12 5月 8 23:50 test.txt [root@git-node1 demo]# rm test.txt rm:是否刪除普通文件 "test.txt"? [root@git-node1 demo]# git rm test.txt rm 'test.txt' [root@git-node1 demo]# git commit -m "remove test" [master 7b5310a] remove test 1 files changed, 0 insertions(+), 1 deletions(-) delete mode 100644 test.txt
現在,文件就從版本庫中被刪除了。
另一種情況是刪錯了,因為版本庫里還有呢,所以可以很輕松地把誤刪的文件恢復到最新版本:
[root@git-node1 demo]# git checkout -- test.txt
git checkout 其實是用版本庫里的版本替換工作區的版本,無論工作區是修改還是刪除,都可以“一鍵還原”。
1.7、理解工作區和暫存區
Git和其他版本控制系統如SVN的一個不同之處就是有暫存區的概念。
先來看名詞解釋。
工作區(Working Directory)
就是你在電腦里能看到的目錄,比如我的git_trainning文件夾就是一個工作區:
|
1
2
|
$
ls
git_trainning/
first_git_file.txt
|
版本庫(Repository)
工作區有一個隱藏目錄.git,這個不算工作區,而是Git的版本庫。
Git的版本庫里存了很多東西,其中最重要的就是稱為stage(或者叫index)的暫存區,還有Git為我們自動創建的第一個分支master,以及指向master的一個指針叫HEAD。
分支和HEAD的概念我們以后再講。
前面講了我們把文件往Git版本庫里添加的時候,是分兩步執行的:
第一步是用git add把文件添加進去,實際上就是把文件修改添加到暫存區;
第二步是用git commit提交更改,實際上就是把暫存區的所有內容提交到當前分支。
因為我們創建Git版本庫時,Git自動為我們創建了唯一一個master分支,所以,現在,git commit就是往master分支上提交更改。
你可以簡單理解為,需要提交的文件修改通通放到暫存區,然后,一次性提交暫存區的所有修改。
俗話說,實踐出真知。現在,我們再練習一遍,先對first_git_file.txt做個修改,比如加上一行內容:
|
1
2
3
4
5
6
|
First
time
using git, excited! update ...
insert line here..改之前的.
第一次用git哈哈
insert line again haha...
加點新內容
update v5
|
然后,在工作區新增一個readme.md文本文件(內容隨便寫)。
先用git status查看一下狀態:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
$ 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: first_git_file.txt
Untracked files:
(use
"git add <file>..."
to include
in
what will be committed)
readme.md
no changes added to commit (use
"git add"
and
/or
"git commit -a"
)
|
Git非常清楚地告訴我們,first_git_file.txt被修改了,而readme.md還從來沒有被添加過,所以它的狀態是Untracked。
現在,使用命令git add . ,再用git status再查看一下:
|
1
2
3
4
5
6
7
8
9
|
$ git add .
$ git status
On branch master
Changes to be committed:
(use
"git reset HEAD <file>..."
to unstage)
modified: first_git_file.txt
new
file
: readme.md
<
/file
>
|
現在,暫存區的狀態就變成這樣了:

(盜圖關系, 這里readme.txt = first_git_file.txt , LICENSE = readme.md)
所以,git add命令實際上就是把要提交的所有修改放到暫存區(Stage),然后,執行git commit就可以一次性把暫存區的所有修改提交到分支。
|
1
2
3
4
|
$ git commit -m
"知道暫存區stage的意思了"
[master 9d65cb2] 知道暫存區stage的意思了
2 files changed, 2 insertions(+)
create mode 100644 readme.md
|
一旦提交后,如果你又沒有對工作區做任何修改,那么工作區就是“干凈”的:
|
1
2
3
|
$ git status
On branch master
nothing to commit, working directory clean
|
現在版本庫變成了這樣,暫存區就沒有任何內容了:

(盜圖關系, 這里readme.txt = first_git_file.txt , LICENSE = readme.md)
暫存區是Git非常重要的概念,弄明白了暫存區,就弄明白了Git的很多操作到底干了什么。
1.8、遠程倉庫
注冊一個GitHub賬號,就可以免費獲得Git遠程倉庫。
由於你的本地Git倉庫和GitHub倉庫之間的傳輸是通過SSH加密的,所以,需要一點設置:
第1步:創建SSH Key。在用戶主目錄下,看看有沒有.ssh目錄,如果有,再看看這個目錄下有沒有id_rsa和id_rsa.pub這兩個文件,如果已經有了,可直接跳到下一步。如果沒有,打開Shell(Windows下打開Git Bash),創建SSH Key:
[root@git-node1 demo]# ssh-keygen Generating public/private rsa key pair. Enter file in which to save the key (/root/.ssh/id_rsa): Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /root/.ssh/id_rsa. Your public key has been saved in /root/.ssh/id_rsa.pub. The key fingerprint is: 16:02:9f:cd:58:e5:c1:7c:b2:e9:6b:51:87:e3:f8:2a root@git-node1 The key's randomart image is: +--[ RSA 2048]----+ | . .+o | | o * .+.. | | = + .= . | | . .o + . | | S. + o | | . + . | | + | | E o . | | o.. | +-----------------+ [root@git-node1 demo]# ls ~/.ssh/ id_rsa id_rsa.pub [root@git-node1 demo]# cat ~/.ssh/id_rsa.pub ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA0twNMooyMj25MeCKQoBcarKeXpPSy3mGDnmFeE0qrZ+jqQ8O8DSqaQYXVUPov3axtDS6Sdq+J63fXmZksuMYAWIRzHXzqwyKgU/exu1HAf+
/UwN/nrS1wAVEPEZM5+yutFSnUpZG0Nr2lTyTHnNBugBXr/xyxDtJErOkSbKXwzJevVIYElcLOZMfsWvJBF/zqfgvdAr6bh7nmnyEguiXL6l5CmcC7k5A/WGqFUJRzhFtNK5hTGp6YBsMBv
j3fyr9+nPexMf7TuJO3HvGps0rW0EsXJD092EO4k8cUKwVtlNoonpRChKapyRDQW1kA9dewUGdU/uRsVcCamEyp98b8w== root@git-node1
可以在用戶主目錄里找到.ssh目錄,里面有id_rsa和id_rsa.pub兩個文件,這兩個就是SSH Key的秘鑰對,id_rsa是私鑰,不能泄露出去,id_rsa.pub是公鑰,可以放心地告訴任何人。
第2步:登陸GitHub,打開“Account settings”,“SSH Keys”頁面:
然后,點“Add SSH Key”,填上任意Title,在Key文本框里粘貼id_rsa.pub文件的內容:

點“Add Key”,你就應該看到已經添加的Key
為什么GitHub需要SSH Key呢?因為GitHub需要識別出你推送的提交確實是你推送的,而不是別人冒充的,而Git支持SSH協議,所以,GitHub只要知道了你的公鑰,就可以確認只有你自己才能推送。
當然,GitHub允許你添加多個Key。假定你有若干電腦,你一會兒在公司提交,一會兒在家里提交,只要把每台電腦的Key都添加到GitHub,就可以在每台電腦上往GitHub推送了。
最后友情提示,在GitHub上免費托管的Git倉庫,任何人都可以看到喔(但只有你自己才能改)。所以,不要把敏感信息放進去。
如果你不想讓別人看到Git庫,有兩個辦法,一個是交點保護費,讓GitHub把公開的倉庫變成私有的,這樣別人就看不見了(不可讀更不可寫)。另一個辦法是自己動手,搭一個Git服務器,因為是你自己的Git服務器,所以別人也是看不見的。這個方法我們后面會講到的,相當簡單,公司內部開發必備。
確保你擁有一個GitHub賬號后,我們就即將開始遠程倉庫的學習。
1.9、分支管理
分支就是科幻電影里面的平行宇宙,當你正在電腦前努力學習Git的時候,另一個你正在另一個平行宇宙里努力學習SVN。
如果兩個平行宇宙互不干擾,那對現在的你也沒啥影響。不過,在某個時間點,兩個平行宇宙合並了,結果,你既學會了Git又學會了SVN!

分支在實際中有什么用呢?假設你准備開發一個新功能,但是需要兩周才能完成,第一周你寫了50%的代碼,如果立刻提交,由於代碼還沒寫完,不完整的代碼庫會導致別人不能干活了。如果等代碼全部寫完再一次提交,又存在丟失每天進度的巨大風險。
現在有了分支,就不用怕了。你創建了一個屬於你自己的分支,別人看不到,還繼續在原來的分支上正常工作,而你在自己的分支上干活,想提交就提交,直到開發完畢后,再一次性合並到原來的分支上,這樣,既安全,又不影響別人工作。
其他版本控制系統如SVN等都有分支管理,但是用過之后你會發現,這些版本控制系統創建和切換分支比蝸牛還慢,簡直讓人無法忍受,結果分支功能成了擺設,大家都不去用。
但Git的分支是與眾不同的,無論創建、切換和刪除分支,Git在1秒鍾之內就能完成!無論你的版本庫是1個文件還是1萬個文件。
2.0、創建與合並分支
在學習版本回退部分時,你已經知道,每次提交,Git都把它們串成一條時間線,這條時間線就是一個分支。截止到目前,只有一條時間線,在Git里,這個分支叫主分支,即master分支。HEAD嚴格來說不是指向提交,而是指向master,master才是指向提交的,所以,HEAD指向的就是當前分支。

下面開始實戰。
首先,我們創建dev分支,然后切換到dev分支:
|
1
2
|
$ git checkout -b dev
Switched to a new branch
'dev'
|
git checkout命令加上-b參數表示創建並切換,相當於以下兩條命令:
|
1
2
3
|
$ git branch dev
$ git checkout dev
Switched to branch
'dev'
|
然后,用git branch命令查看當前分支:
|
1
2
3
|
$ git branch
* dev
master
|
git branch命令會列出所有分支,當前分支前面會標一個*號。
然后,我們就可以在dev分支上正常提交,比如對readme.txt做個修改,加上一行:
|
1
|
Creating a new branch is quick.
|
然后提交:
|
1
2
3
4
|
$ git add readme.txt
$ git commit -m
"branch test"
[dev fec145a] branch
test
1
file
changed, 1 insertion(+)
|
現在,dev分支的工作完成,我們就可以切換回master分支:
|
1
2
|
$ git checkout master
Switched to branch
'master'
|
切換回master分支后,再查看一個readme.txt文件,剛才添加的內容不見了!因為那個提交是在dev分支上,而master分支此刻的提交點並沒有變:

現在,我們把dev分支的工作成果合並到master分支上:
|
1
2
3
4
5
|
$ git merge dev
Updating d17efd8..fec145a
Fast-forward
readme.txt | 1 +
1
file
changed, 1 insertion(+)
|
git merge命令用於合並指定分支到當前分支。合並后,再查看readme.txt的內容,就可以看到,和dev分支的最新提交是完全一樣的。
注意到上面的Fast-forward信息,Git告訴我們,這次合並是“快進模式”,也就是直接把master指向dev的當前提交,所以合並速度非常快。
當然,也不是每次合並都能Fast-forward,我們后面會講其他方式的合並。
合並完成后,就可以放心地刪除dev分支了:
|
1
2
|
$ git branch -d dev
Deleted branch dev (was fec145a).
|
刪除后,查看branch,就只剩下master分支了:
|
1
2
|
$ git branch
* master
|
因為創建、合並和刪除分支非常快,所以Git鼓勵你使用分支完成某個任務,合並后再刪掉分支,這和直接在master分支上工作效果是一樣的,但過程更安全。
解決沖突
人生不如意之事十之八九,合並分支往往也不是一帆風順的。
准備新的feature1分支,繼續我們的新分支開發:
|
1
2
|
$ git checkout -b feature1
Switched to a new branch
'feature1'
|
修改readme.txt最后一行,改為:
|
1
|
added this line from branch feature 1
|
在feature1分支上提交:
$ git add readme.txt
$ git commit -m "add feature" [feature1 75a857c] AND simple 1 file changed, 1 insertion(+), 1 deletion(-)
切換到master分支:
$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 1 commit.
Git還會自動提示我們當前master分支比遠程的master分支要超前1個提交。
在master分支上把readme.txt文件的最后一行改為:
|
1
|
added this line from master
|
提交:
$ git add readme.txt $ git commit -m "master update" [master 400b400] & simple 1 file changed, 1 insertion(+), 1 deletion(-)
現在,master分支和feature1分支各自都分別有新的提交,變成了這樣:

這種情況下,Git無法執行“快速合並”,只能試圖把各自的修改合並起來,但這種合並就可能會有沖突,我們試試看:
|
1
2
3
4
|
$ git merge feature1
Auto-merging readme.txt
CONFLICT (content): Merge conflict
in
readme.txt
Automatic merge failed; fix conflicts and
then
commit the result.
|
果然沖突了!Git告訴我們,readme.txt文件存在沖突,必須手動解決沖突后再提交。git status也可以告訴我們沖突的文件:
|
1
2
3
4
5
6
7
8
9
10
|
$ 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的內容:
|
1
2
3
4
5
6
7
|
#git study repo
Creating a new branch is quick.
<<<<<<< HEAD
added this line from master
=======
added this line from branch feature 1
>>>>>>> feature1
|
Git用<<<<<<<,=======,>>>>>>>標記出不同分支的內容,我們修改如下后保存:
|
1
2
3
4
|
#git study repo
Creating a new branch is quick.
added this line from master
added this line from branch feature 1
|
再提交
|
1
2
3
|
$ git add readme.txt
$ git commit -m
"conflict fixed"
[master 59bc1cb] conflict fixed
|
現在,master分支和feature1分支變成了下圖所示:

用帶參數的git log也可以看到分支的合並情況:
|
1
2
3
4
5
6
7
8
|
$ git log --graph --pretty=oneline
* feedd786cad3e18323a41846fcc1b0d52fc0c98e fix conflict
|\
| * 01f8f8d168e113fac9fbe24c4cfa6d4c351a9821 update from branch
* | 743ccee30f3d74f1993f17e7312032b7399b1306 from master
|/
* edfbc29982927236596539e0f1971b0575f803c0 branch
test
* 8675486bfeeb340914369e80d2cfcf3e854e88a3 add home page
|
2.1、分支策略
在實際開發中,我們應該按照幾個基本原則進行分支管理:
首先,master分支應該是非常穩定的,也就是僅用來發布新版本,平時不能在上面干活;
那在哪干活呢?干活都在dev分支上,也就是說,dev分支是不穩定的,到某個時候,比如1.0版本發布時,再把dev分支合並到master上,在master分支發布1.0版本;
你和你的小伙伴們每個人都在dev分支上干活,每個人都有自己的分支,時不時地往dev分支上合並就可以了。
所以,團隊合作的分支看起來就像這樣:

2.2、bug分支
軟件開發中,bug就像家常便飯一樣。有了bug就需要修復,在Git中,由於分支是如此的強大,所以,每個bug都可以通過一個新的臨時分支來修復,修復后,合並分支,然后將臨時分支刪除。
當你接到一個修復一個代號101的bug的任務時,很自然地,你想創建一個分支issue-101來修復它,但是,等等,當前正在dev上進行的工作還沒有提交:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
$ git status
# 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
#
|
並不是你不想提交,而是工作只進行到一半,還沒法提交,預計完成還需1天時間。但是,必須在兩個小時內修復該bug,怎么辦?
幸好,Git還提供了一個stash功能,可以把當前工作現場“儲藏”起來,等以后恢復現場后繼續工作:
|
1
2
3
|
$ git stash
Saved working directory and index state WIP on dev: 6224937 add merge
HEAD is now at 6224937 add merge
|
現在,用git status查看工作區,就是干凈的(除非有沒有被Git管理的文件),因此可以放心地創建分支來修復bug。
首先確定要在哪個分支上修復bug,假定需要在master分支上修復,就從master創建臨時分支:
|
1
2
3
4
5
|
$ git checkout master
Switched to branch
'master'
Your branch is ahead of
'origin/master'
by 6 commits.
$ git checkout -b issue-101
Switched to a new branch
'issue-101'
|
現在修復bug,需要把“Git is free software ...”改為“Git is a free software ...”,然后提交:
|
1
2
3
4
|
$ git add readme.txt
$ git commit -m
"fix bug 101"
[issue-101 cc17032] fix bug 101
1
file
changed, 1 insertion(+), 1 deletion(-)
|
修復完成后,切換到master分支,並完成合並,最后刪除issue-101分支:
|
1
2
3
4
5
6
7
8
9
|
$ git checkout master
Switched to branch
'master'
Your branch is ahead of
'origin/master'
by 2 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(-)
$ git branch -d issue-101
Deleted branch issue-101 (was cc17032).
|
太棒了,原計划兩個小時的bug修復只花了5分鍾!現在,是時候接着回到dev分支干活了!
|
1
2
3
4
5
|
$ git checkout dev
Switched to branch
'dev'
$ git status
# On branch dev
nothing to commit (working directory clean)
|
工作區是干凈的,剛才的工作現場存到哪去了?用git stash list命令看看:
|
1
2
|
$ git stash list
stash@{0}: WIP on dev: 6224937 add merge
|
工作現場還在,Git把stash內容存在某個地方了,但是需要恢復一下,有兩個辦法:
一是用git stash apply恢復,但是恢復后,stash內容並不刪除,你需要用git stash drop來刪除;
另一種方式是用git stash pop,恢復的同時把stash內容也刪了:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
$ 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 list查看,就看不到任何stash內容了:
|
1
|
$ git stash list
|
你可以多次stash,恢復的時候,先用git stash list查看,然后恢復指定的stash,用命令:
|
1
|
$ git stash apply stash@{0}
|
2.3、分支策略
多人協作
當你從遠程倉庫克隆時,實際上Git自動把本地的master分支和遠程的master分支對應起來了,並且,遠程倉庫的默認名稱是origin。
要查看遠程庫的信息,用git remote:
|
1
2
|
$ git remote
origin
|
或者,用git remote -v顯示更詳細的信息:
|
1
2
3
|
$ git remote -
v
origin git@github.com:triaquae
/gitskills
.git (fetch)
origin git@github.com:triaquae
/gitskills
.git (push)
|
上面顯示了可以抓取和推送的origin的地址。如果沒有推送權限,就看不到push的地址。
2.4、 推送分支
推送分支,就是把該分支上的所有本地提交推送到遠程庫。推送時,要指定本地分支,這樣,Git就會把該分支推送到遠程庫對應的遠程分支上:
|
1
|
$ git push origin master
|
如果要推送其他分支,比如dev,就改成:
|
1
|
$ git push origin dev
|
但是,並不是一定要把本地分支往遠程推送,那么,哪些分支需要推送,哪些不需要呢?
-
master分支是主分支,因此要時刻與遠程同步; -
dev分支是開發分支,團隊所有成員都需要在上面工作,所以也需要與遠程同步; -
bug分支只用於在本地修復bug,就沒必要推到遠程了,除非老板要看看你每周到底修復了幾個bug;
-
feature分支是否推到遠程,取決於你是否和你的小伙伴合作在上面開發。
總之,就是在Git中,分支完全可以在本地自己藏着玩,是否推送,視你的心情而定!
2.5、抓取分支
多人協作時,大家都會往master和dev分支上推送各自的修改。
現在,模擬一個你的小伙伴,可以在另一台電腦(注意要把SSH Key添加到GitHub)或者同一台電腦的另一個目錄下克隆:
|
1
2
3
4
5
6
7
|
$ git clone git@github.com:triaquae
/gitskills
.git
Cloning into
'gitskills'
...
remote: Counting objects: 16,
done
.
remote: Compressing objects: 100% (7
/7
),
done
.
remote: Total 16 (delta 0), reused 10 (delta 0), pack-reused 0
Receiving objects: 100% (16
/16
),
done
.
Checking connectivity...
done
.
|
當你的小伙伴從遠程庫clone時,默認情況下,你的小伙伴只能看到本地的master分支。不信可以用git branch命令看看:
|
1
2
|
$ git branch
* master
|
現在,你的小伙伴要在dev分支上開發,就必須創建遠程origin的dev分支到本地,於是他用這個命令創建本地dev分支:
|
1
|
$ git checkout -b dev origin
/dev
|
現在,他就可以在dev上繼續修改,然后,時不時地把dev分支push到遠程:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
$ git add .
$ git commit -m
"small updates"
[dev f1b762e] small updates
2 files changed, 5 insertions(+), 1 deletion(-)
Alexs-MacBook-Pro:gitskills alex$ git push origin dev
Counting objects: 4,
done
.
Delta compression using up to 8 threads.
Compressing objects: 100% (3
/3
),
done
.
Writing objects: 100% (4
/4
), 438 bytes | 0 bytes
/s
,
done
.
Total 4 (delta 0), reused 0 (delta 0)
To git@github.com:triaquae
/gitskills
.git
33ec6b4..f1b762e dev -> dev
|
你的小伙伴已經向origin/dev分支推送了他的提交,而碰巧你也對同樣的文件作了修改,並試圖推送:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
$ git add .
$ git commit -m
"add Dog class"
[dev 7e7b1bf] add Dog class
2 files changed, 7 insertions(+)
$ git push origin dev
To git@github.com:triaquae
/gitskills
.git
! [rejected] dev -> dev (fetch first)
error: failed to push some refs to
'git@github.com:triaquae/gitskills.git'
hint: Updates were rejected because the remote contains work that you
do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (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抓下來,然后,在本地合並,解決沖突,再推
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
$ git pull
remote: Counting objects: 4,
done
.
remote: Compressing objects: 100% (3
/3
),
done
.
remote: Total 4 (delta 0), reused 4 (delta 0), pack-reused 0
Unpacking objects: 100% (4
/4
),
done
.
From github.com:triaquae
/gitskills
33ec6b4..f1b762e 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-to=origin/<branch> dev
|
git pull也失敗了,原因是沒有指定本地dev分支與遠程origin/dev分支的鏈接,根據提示,設置dev和origin/dev的鏈接:
|
1
2
|
$ git branch --
set
-upstream-to=origin
/dev
dev
Branch dev
set
up to track remote branch dev from origin.
|
再pull:
|
1
2
3
4
5
6
|
$ git pull
Auto-merging hello.py
CONFLICT (content): Merge conflict
in
hello.py
Auto-merging branch_test.md
CONFLICT (content): Merge conflict
in
branch_test.md
Automatic merge failed; fix conflicts and
then
commit the result.
|
這回git pull成功,但是合並有沖突,需要手動解決,解決的方法和分支管理中的解決沖突完全一樣。解決后,提交,再push:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
$ git add .
$ git commit -m
"merge & fix hello.py"
[dev 93e28e3] merge & fix hello.py
$ git push origin dev
Counting objects: 8,
done
.
Delta compression using up to 8 threads.
Compressing objects: 100% (7
/7
),
done
.
Writing objects: 100% (8
/8
), 819 bytes | 0 bytes
/s
,
done
.
Total 8 (delta 1), reused 0 (delta 0)
remote: Resolving deltas: 100% (1
/1
),
done
.
To git@github.com:triaquae
/gitskills
.git
f1b762e..93e28e3 dev -> dev
|
因此,多人協作的工作模式通常是這樣:
-
首先,可以試圖用
git push origin branch-name推送自己的修改; -
如果推送失敗,則因為遠程分支比你的本地更新,需要先用
git pull試圖合並; -
如果合並有沖突,則解決沖突,並在本地提交;
-
沒有沖突或者解決掉沖突后,再用
git push origin branch-name推送就能成功!
如果git pull提示“no tracking information”,則說明本地分支和遠程分支的鏈接關系沒有創建,用命令git branch --set-upstream branch-name origin/branch-name。
這就是多人協作的工作模式,一旦熟悉了,就非常簡單。
2.6、 github使用
我們一直用GitHub作為免費的遠程倉庫,如果是個人的開源項目,放到GitHub上是完全沒有問題的。其實GitHub還是一個開源協作社區,通過GitHub,既可以讓別人參與你的開源項目,也可以參與別人的開源項目。
在GitHub出現以前,開源項目開源容易,但讓廣大人民群眾參與進來比較困難,因為要參與,就要提交代碼,而給每個想提交代碼的群眾都開一個賬號那是不現實的,因此,群眾也僅限於報個bug,即使能改掉bug,也只能把diff文件用郵件發過去,很不方便。
但是在GitHub上,利用Git極其強大的克隆和分支功能,廣大人民群眾真正可以第一次自由參與各種開源項目了。
如何參與一個開源項目呢?比如人氣極高的bootstrap項目,這是一個非常強大的CSS框架,你可以訪問它的項目主頁https://github.com/twbs/bootstrap,點“Fork”就在自己的賬號下克隆了一個bootstrap倉庫,然后,從自己的賬號下clone:
|
1
|
git clone git@github.com:michaelliao
/bootstrap
.git
|
一定要從自己的賬號下clone倉庫,這樣你才能推送修改。如果從bootstrap的作者的倉庫地址git@github.com:twbs/bootstrap.git克隆,因為沒有權限,你將不能推送修改。
Bootstrap的官方倉庫twbs/bootstrap、你在GitHub上克隆的倉庫my/bootstrap,以及你自己克隆到本地電腦的倉庫,他們的關系就像下圖顯示的那樣:

如果你想修復bootstrap的一個bug,或者新增一個功能,立刻就可以開始干活,干完后,往自己的倉庫推送。
如果你希望bootstrap的官方庫能接受你的修改,你就可以在GitHub上發起一個pull request。當然,對方是否接受你的pull request就不一定了。
如果你沒能力修改bootstrap,但又想要試一把pull request,那就Fork一下我的倉庫:https://github.com/triaquae/gitskills ,創建一個your-github-id.txt的文本文件,寫點自己學習Git的心得,然后推送一個pull request給我,我會視心情而定是否接受。
小結
-
在GitHub上,可以任意Fork開源倉庫;
-
自己擁有Fork后的倉庫的讀寫權限;
-
可以推送pull request給官方倉庫來貢獻代碼。
2.7、 忽略特殊文件.gitignore
有些時候,你必須把某些文件放到Git工作目錄中,但又不能提交它們,比如保存了數據庫密碼的配置文件啦,等等,每次git status都會顯示Untracked files ...,有強迫症的童鞋心里肯定不爽。
好在Git考慮到了大家的感受,這個問題解決起來也很簡單,在Git工作區的根目錄下創建一個特殊的.gitignore文件,然后把要忽略的文件名填進去,Git就會自動忽略這些文件。
不需要從頭寫.gitignore文件,GitHub已經為我們准備了各種配置文件,只需要組合一下就可以使用了。所有配置文件可以直接在線瀏覽:https://github.com/github/gitignore
忽略文件的原則是:
- 忽略操作系統自動生成的文件,比如縮略圖等;
- 忽略編譯生成的中間文件、可執行文件等,也就是如果一個文件是通過另一個文件自動生成的,那自動生成的文件就沒必要放進版本庫,比如Java編譯產生的
.class文件; - 忽略你自己的帶有敏感信息的配置文件,比如存放口令的配置文件。
舉個例子:
假設你在Windows下進行Python開發,Windows會自動在有圖片的目錄下生成隱藏的縮略圖文件,如果有自定義目錄,目錄下就會有Desktop.ini文件,因此你需要忽略Windows自動生成的垃圾文件:
|
1
2
3
4
|
# Windows:
Thumbs.db
ehthumbs.db
Desktop.ini
|
然后,繼續忽略Python編譯產生的.pyc、.pyo、dist等文件或目錄:
|
1
2
3
4
5
6
7
|
# Python:
*.py[cod]
*.so
*.egg
*.egg-info
dist
build
|
加上你自己定義的文件,最終得到一個完整的.gitignore文件,內容如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
# Windows:
Thumbs.db
ehthumbs.db
Desktop.ini
# Python:
*.py[cod]
*.so
*.egg
*.egg-info
dist
build
# My configurations:
db.ini
deploy_key_rsa
|
最后一步就是把.gitignore也提交到Git,就完成了!當然檢驗.gitignore的標准是git status命令是不是說working directory clean。
使用Windows的童鞋注意了,如果你在資源管理器里新建一個.gitignore文件,它會非常弱智地提示你必須輸入文件名,但是在文本編輯器里“保存”或者“另存為”就可以把文件保存為.gitignore了。
有些時候,你想添加一個文件到Git,但發現添加不了,原因是這個文件被.gitignore忽略了:
|
1
2
3
4
|
$ git add App.class
The following paths are ignored by one of your .gitignore files:
App.class
Use -f
if
you really want to add them.
|
如果你確實想添加該文件,可以用-f強制添加到Git:
|
1
|
$ git add -f App.class
|
或者你發現,可能是.gitignore寫得有問題,需要找出來到底哪個規則寫錯了,可以用git check-ignore命令檢查:
|
1
2
|
$ git check-ignore -
v
App.class
.gitignore:3:*.class App.class
|
Git會告訴我們,.gitignore的第3行規則忽略了該文件,於是我們就可以知道應該修訂哪個規則。
小結
-
忽略某些文件時,需要編寫
.gitignore; -
.gitignore文件本身要放到版本庫里,並且可以對.gitignore做版本管理!
2.8、基本操作小結
相信大家對Git的基本操作有一定的了解,下面進行一下簡單的梳理總結:
命令 git config --global user.name "name" #配置git使用用戶# git config --global user.email "mail" #配置git使用郵箱# git config --global color.ui true #配置顏色# git config --list #查看當前配置# git init #初始為git工作目錄# git status #查看git狀態# git reflog #查看未來歷史更新點# git reset --hard 4bf5b29 #找到歷史還原點的SHA-1值,就可以還原(值不寫全,系統會自動匹配)# git checkout -- file #恢復暫存區至上一版本# git add [file1] [file2] ... #添加指定文件至暫存區# git add [dir] #添加指定目錄至暫存區,包括子目錄(遞歸添加)# git add . #添加當前目錄所有文件至暫存區# git rm [file1] [file2] ... #刪除工作區文件,並將這次刪除放入暫存區# git rm –cached [file] #停止追蹤指定文件,但該文件會保留在工作區# git mv [file-old] [file-new] #重命名文件,修改后放入暫存區
