Git系列二之數據管理


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
4.簡要顯示數據修改行數
#--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(-)
 
5.根據不同的格式展示提交的歷史信息
 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_rsaid_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_rsaid_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嚴格來說不是指向提交,而是指向mastermaster才是指向提交的,所以,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
  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 +
  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
  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 +-
  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、抓取分支

多人協作時,大家都會往masterdev分支上推送各自的修改。

現在,模擬一個你的小伙伴,可以在另一台電腦(注意要把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分支上開發,就必須創建遠程origindev分支到本地,於是他用這個命令創建本地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分支的鏈接,根據提示,設置devorigin/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

 

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

  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 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

忽略文件的原則是:

  1. 忽略操作系統自動生成的文件,比如縮略圖等;
  2. 忽略編譯生成的中間文件、可執行文件等,也就是如果一個文件是通過另一個文件自動生成的,那自動生成的文件就沒必要放進版本庫,比如Java編譯產生的.class文件;
  3. 忽略你自己的帶有敏感信息的配置文件,比如存放口令的配置文件。

舉個例子:

假設你在Windows下進行Python開發,Windows會自動在有圖片的目錄下生成隱藏的縮略圖文件,如果有自定義目錄,目錄下就會有Desktop.ini文件,因此你需要忽略Windows自動生成的垃圾文件:

1
2
3
4
# Windows:
Thumbs.db
ehthumbs.db
Desktop.ini

然后,繼續忽略Python編譯產生的.pyc.pyodist等文件或目錄:

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的基本操作有一定的了解,下面進行一下簡單的梳理總結:

    1. 命令 git config --global user.name "name" #配置git使用用戶
    2. # git config --global user.email "mail" #配置git使用郵箱
    3. # git config --global color.ui true #配置顏色
    4. # git config --list #查看當前配置
    5. # git init #初始為git工作目錄
    6. # git status #查看git狀態
    7. # git reflog #查看未來歷史更新點
    8. # git reset --hard 4bf5b29 #找到歷史還原點的SHA-1值,就可以還原(值不寫全,系統會自動匹配)
    9. # git checkout -- file #恢復暫存區至上一版本
    10. # git add [file1] [file2] ... #添加指定文件至暫存區
    11. # git add [dir] #添加指定目錄至暫存區,包括子目錄(遞歸添加)
    12. # git add . #添加當前目錄所有文件至暫存區
    13. # git rm [file1] [file2] ... #刪除工作區文件,並將這次刪除放入暫存區
    14. # git rm –cached [file] #停止追蹤指定文件,但該文件會保留在工作區
    15. # git mv [file-old] [file-new] #重命名文件,修改后放入暫存區


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM