Git 學習筆記

參考內容:
1.廖雪峯Git教程
2.阿里雲學習Git

關於安裝配置這裏就不囉嗦了,直奔主題。

1. Git 簡介

1.1 爲什麼要使用Git?

Git是什麼?Git是目前世界上最先進的分佈式版本控制系統(沒有之一)。

Git有什麼特點?簡單來說就是:高端大氣上檔次!

什麼是版本控制系統?

如果你用Word寫過長篇大論,那你一定有這樣的經歷:想刪除一個段落,又怕將來想恢復找不回來怎麼辦?先把當前文件“另存爲……”一個新的Word文件,再接着改,改到一定程度,再“另存爲……”一個新文件,這樣一直改下去,這樣就派生出了很多文件,其中有很多是多餘的。

當過一段時間,你想找回被刪除的文字,但是已經記不清刪除前保存在哪個文件裏了,只好一個一個文件去找,真麻煩。看着一堆亂七八糟的文件,想保留最新的一個,然後把其他的刪掉,又怕哪天會用上,還不敢刪,真鬱悶。

更要命的是,有些部分需要你的同事幫助填寫,於是你把文件Copy給她,然後,你繼續修改Word文件。一天後,同事再把Word文件傳給你,此時,你必須想想,發給她之後到你收到她的文件期間,你作了哪些改動,得把你的改動和她的部分合並,真困難。

於是你想,如果有一個軟件,不但能自動幫我記錄每次文件的改動,還可以讓同事協作編輯,這樣就不用自己管理一堆類似的文件了,也不需要把文件傳來傳去。如果想查看某次改動,只需要在軟件裏瞄一眼就可以,豈不是很方便?

在2002年以前,世界各地的志願者把源代碼文件通過diff的方式發給Linus,然後由Linus本人通過手工方式合併代碼!

到了2002年,Linux系統已經發展了十年了,代碼庫之大讓Linus很難繼續通過手工方式管理了,於是一個商業的版本控制系統BitKeeper授權Linux社區免費使用這個版本控制系統。2005年Linux社區牛人開發Samba的Andrew試圖破解BitKeeper的協議,被BitMover公司發現,於是BitMover公司收回Linux社區的免費使用權。

Linus花了兩週時間自己用C寫了一個分佈式版本控制系統,這就是Git!一個月之內,Linux系統的源碼已經由Git管理了!

1.2 Git 工作流程

一般工作流程如下:

  • 克隆 Git 資源作爲工作目錄。
  • 在克隆的資源上添加或修改文件。
  • 如果其他人修改了,你可以更新資源。
  • 在提交前查看修改。
  • 提交修改。
  • 在修改完成後,如果發現錯誤,可以撤回提交併再次修改並提交。
    在這裏插入圖片描述

1.3 Git 工作區、暫存區和版本庫

  • 工作區:就是你在電腦裏能看到的目錄。
  • 暫存區:英文叫stage, 或index。一般存放在 “.git目錄下” 下的index文件(.git/index)中,所以我們把暫存區有時也叫作索引(index)。
  • 版本庫:版本庫又名倉庫,英文名repository,你可以簡單理解成一個目錄,這個目錄裏面的所有文件都可以被Git管理起來,每個文件的修改、刪除,Git都能跟蹤,以便任何時刻都可以追蹤歷史,或者在將來某個時刻可以“還原”。
    在這裏插入圖片描述
  • 圖中左側爲工作區,右側爲版本庫。在版本庫中標記爲 “index” 的區域是暫存區(stage, index),標記爲 “master” 的是 master 分支所代表的目錄樹。
  • 圖中我們可以看出此時 “HEAD” 實際是指向 master 分支的一個"遊標"。所以圖示的命令中出現 HEAD 的地方可以用 master 來替換。
  • 圖中的 objects 標識的區域爲 Git 的對象庫,實際位於 “.git/objects” 目錄下,裏面包含了創建的各種對象及內容。
  1. 當對工作區修改(或新增)的文件執行 “git add” 命令時,暫存區的目錄樹被更新,同時工作區修改(或新增)的文件內容被寫入到對象庫中的一個新的對象中,而該對象的ID被記錄在暫存區的文件索引中。

  2. 當執行提交操作(git commit)時,暫存區的目錄樹寫到版本庫(對象庫)中,master 分支會做相應的更新。即 master 指向的目錄樹就是提交時暫存區的目錄樹。

  3. 當執行 “git reset HEAD” 命令時,暫存區的目錄樹會被重寫,被 master 分支指向的目錄樹所替換,但是工作區不受影響。

  4. 當執行 "git rm --cached " 命令時,會直接從暫存區刪除文件,工作區則不做出改變。

  5. 當執行 “git checkout .” 或者 "git checkout – " 命令時,會用暫存區全部或指定的文件替換工作區的文件。這個操作很危險,會清除工作區中未添加到暫存區的改動。

  6. 當執行 “git checkout HEAD .” 或者 "git checkout HEAD " 命令時,會用 HEAD 指向的 master 分支中的全部或者部分文件替換暫存區和以及工作區中的文件。這個命令也是極具危險性的,因爲不但會清除工作區中未提交的改動,也會清除暫存區中未提交的改動。

2 Git 本地倉庫管理

2.1 創建本地倉庫

1.選擇合適的路徑,創建一個空目錄:

$ mkdir learngit
$ cd learngit
$ pwd
/d/learngit

pwd 命令用於顯示當前目錄,我創建的倉庫位於/d/learngit

2.要使用當前目錄作爲Git倉庫,只需使它初始化:
(當然也可以使用指定目錄作爲Git倉庫,只需要在init 後加倉庫名即可,例如git init newrepo

$ git init

3.把文件添加到版本庫
編寫一個readme.txt文件,內容如下:

Git is a version control system.
Git is free software.

一定要放到learngit目錄下(子目錄也行)
第一步,用命令git add告訴Git,把文件添加到倉庫:

$ git add readme.txt

第二步,用命令git commit告訴Git,把文件提交到倉庫:

$ git commit -m "wrote a readme file"

git commit命令,-m後面輸入的是本次提交的說明,可以輸入任意內容,當然最好是有意義的,這樣你就能從歷史記錄裏方便地找到改動記錄。

爲什麼Git添加文件需要addcommit一共兩步呢?因爲commit可以一次提交很多文件,所以你可以多次add不同的文件。

4.git status查看倉庫當前的動態
繼續修改readme.txt文件,改成如下內容:

Git is a distributed version control system.
Git is free software.

git status查看倉庫當前的動態

$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   readme.txt

no changes added to commit (use "git add" and/or "git commit -a")

git status命令可以讓我們時刻掌握倉庫當前的狀態,上面的命令輸出告訴我們,readme.txt被修改過了,但還沒有準備提交的修改。

5.git diff查看具體修改了什麼內容

$ git diff readme.txt
diff --git a/readme.txt b/readme.txt
index d8036c1..013b5bc 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,2 +1,2 @@
-Git is a version control system.
+Git is a distributed version control system.
 Git is free software.
\ No newline at end of file

git diff顧名思義就是查看difference,顯示的格式正是Unix通用的diff格式,可以從上面的命令輸出看到,我們在第一行添加了一個distributed單詞。

6.git add提交修改到倉庫,並查看倉庫狀態

$ git add readme.txt

$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   readme.txt

git status告訴我們,將要被提交的修改包括readme.txt,下一步,就可以放心地提交了。

7.git commit -m "add distributed"提交修改,並查看倉庫狀態

$ git commit -m "add distributed"
[master 20781ac] add distributed
 1 file changed, 1 insertion(+), 1 deletion(-)

$ git status
On branch master
nothing to commit, working tree clean

Git告訴我們當前沒有需要提交的修改,而且,工作目錄是乾淨(working tree clean)的。

2.1 版本退回

再練習一次,修改readme.txt文件如下:

Git is a distributed version control system.
Git is free software distributed under the GPL.

然後嘗試提交:

$ git add readme.txt
$ git commit -m "append GPL"
[master feded6f] append GPL
 1 file changed, 1 insertion(+), 1 deletion(-)

每當你覺得文件修改到一定程度的時候,就可以“保存一個快照”,這個快照在Git中被稱爲commit。一旦你把文件改亂了,或者誤刪了文件,還可以從最近的一個commit恢復,然後繼續工作,而不是把幾個月的工作成果全部丟失。

1.git log查看修改的歷史記錄

$ git log
commit feded6f6bb3a61ed1c2cb47ac77c7a82308ea005 (HEAD -> master)
Author: chopin <3412391@qq.com>
Date:   Wed Feb 26 11:22:30 2020 +0800

    append GPL

commit 20781ac05a3f10584f11bc31600392d778f87fda
Author: chopin <3412391@qq.com>
Date:   Wed Feb 26 11:15:17 2020 +0800

    add distributed

commit a0f6beb6640121a7ac24d64142245f4a94e1b662
Author: chopin <3412391@qq.com>
Date:   Tue Feb 25 17:21:25 2020 +0800

    wrote as readme file

git log命令顯示從最近到最遠的提交日誌,我們可以看到3次提交,最近的一次是append GPL,上一次是add distributed,最早的一次是wrote a readme file

如果嫌輸出信息太多,看得眼花繚亂的,可以試試加上--pretty=oneline參數:

$ git log --pretty=oneline
feded6f6bb3a61ed1c2cb47ac77c7a82308ea005 (HEAD -> master) append GPL
20781ac05a3f10584f11bc31600392d778f87fda add distributed
a0f6beb6640121a7ac24d64142245f4a94e1b662 wrote as readme file

你看到的一大串類似1094adb…的是commit id(版本號),和SVN不一樣,Git的commit id不是1,2,3……遞增的數字,而是一個SHA1計算出來的一個非常大的數字,用十六進制表示,而且你看到的commit id和我的肯定不一樣,以你自己的爲準。爲什麼commit id需要用這麼一大串數字表示呢?因爲Git是分佈式的版本控制系統,後面我們還要研究多人在同一個版本庫裏工作,如果大家都用1,2,3……作爲版本號,那肯定就衝突了。

每提交一個新版本,實際上Git就會把它們自動串成一條時間線。如果使用可視化工具查看Git歷史,就可以更清楚地看到提交歷史的時間線。

2.準備把readme.txt回退到上一個版本,也就是add distributed的那個版本,怎麼做呢?
首先,Git必須知道當前版本是哪個版本,在Git中,用HEAD表示當前版本,也就是最新的提交feded6f6bb3a61ed1c2cb47ac77c7a82308ea005(每個人的ID都不一樣) 上一個版本就是HEAD^,上上一個版本就是HEAD^^,當然往上100個版本寫100個^比較容易數不過來,所以寫成HEAD~100

3.git reset命令退回指定版本

$ git reset --hard HEAD^
HEAD is now at 20781ac add distributed

看看readme.txt的內容是不是版本add distributed

$ cat readme.txt
Git is a distributed version control system.
Git is free software.

Git的版本回退速度非常快,因爲Git在內部有個指向當前版本的HEAD指針,當你回退版本的時候,Git僅僅是把HEAD從指向append GPL

在這裏插入圖片描述
4.git reflog查看 記錄的每一次命令,可用於查找退回版本的ID

$ git reflog
20781ac (HEAD -> master) HEAD@{0}: reset: moving to HEAD^
feded6f HEAD@{1}: commit: append GPL
20781ac (HEAD -> master) HEAD@{2}: commit: add distributed
a0f6beb HEAD@{3}: commit (initial): wrote as readme file

2.3 工作區和暫存區

  • 工作區(Working Directory)就是在電腦裏能看到的目錄,比如上部分創建的learngit文件夾就是一個工作區。
  • 版本庫(Repository)工作區有一個隱藏目錄.git,這個不算工作區,而是Git的版本庫。Git的版本庫裏存了很多東西,其中最重要的就是稱爲stage(或者叫index)的暫存區,還有Git爲我們自動創建的第一個分支master,以及指向master的一個指針叫HEAD。

把文件往Git版本庫裏添加的時候,是分兩步執行的:

第一步是用git add把文件添加進去,實際上就是把文件修改添加到暫存區;

第二步是用git commit提交更改,實際上就是把暫存區的所有內容提交到當前分支。

因爲我們創建Git版本庫時,Git自動爲我們創建了唯一一個master分支,所以,現在,git commit就是往master分支上提交更改。你可以簡單理解爲,需要提交的文件修改通通放到暫存區,然後,一次性提交暫存區的所有修改。

2.4 管理修改

爲什麼Git比其他版本控制系統設計得優秀,因爲Git跟蹤並管理的是修改,而非文件。

提交修改後,可以用git diff HEAD -- readme.txt命令可以查看工作區和版本庫裏面最新版本的區別。

2.5 撤銷修改

1.僅僅修改了readme.txt的文件,還沒有執行git add命令。
git checkout -- file可以丟棄工作區的修改。命令git checkout -- readme.txt意思就是,把readme.txt文件在工作區的修改全部撤銷,這裏有兩種情況:

一種是readme.txt自修改後還沒有被放到暫存區,現在,撤銷修改就回到和版本庫一模一樣的狀態;

一種是readme.txt已經添加到暫存區後,又作了修改,現在,撤銷修改就回到添加到暫存區後的狀態。

總之,就是讓這個文件回到最近一次git commitgit add時的狀態。

2.已經執行完git add命令將修改提交到暫存區,還未執行git commit命令。

用命令git reset HEAD <file>可以把暫存區的修改撤銷掉(unstage),重新放回工作區。

2.6 刪除文件

添加一個新文件test.txt到Git並且提交:

$ git commit -m "add test.txt"
[master 3161c40] add test.txt
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 test.txt

一般情況下,你通常直接在文件管理器中把沒用的文件刪了,或者用rm命令刪了:
這個時候,Git知道你刪除了文件,因此,工作區和版本庫就不一致了,git status命令會立刻告訴你哪些文件被刪除了:

$ git status
On branch master
Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        deleted:    test.txt

no changes added to commit (use "git add" and/or "git commit -a")

現在你有兩個選擇,一是確實要從版本庫中刪除該文件,那就用命令git rm刪掉,並且git commit

$ git rm test.txt
rm 'test.txt'

$ git commit -m "remove test.txt"
[master e496d67] remove test.txt
 1 file changed, 0 insertions(+), 0 deletions(-)
 delete mode 100644 test.txt

現在,文件就從版本庫中被刪除了。

另一種情況是刪錯了,因爲版本庫裏還有呢,所以可以很輕鬆地把誤刪的文件恢復到最新版本:

$ git checkout -- test.txt

git checkout其實是用版本庫裏的版本替換工作區的版本,無論工作區是修改還是刪除,都可以“一鍵還原”。但是從來沒有被添加到版本庫就被刪除的文件,是無法恢復的!

3 遠程倉庫

1.註冊github賬號
2.由於本地Git倉庫和GitHub倉庫之間的傳輸是通過SSH加密的,所以需要一點設置:

  1. 第1步:創建SSH Key。在用戶主目錄(我的在C:\Users\34123.ssh)下,看看有沒有.ssh目錄,如果有,再看看這個目錄下有沒有id_rsa和id_rsa.pub這兩個文件,如果已經有了,可直接跳到下一步。如果沒有,打開Shell(Windows下打開Git Bash),創建SSH Key:$ ssh-keygen -t rsa -C "[email protected]"
    你需要把郵件地址換成你自己的郵件地址,然後一路回車,使用默認值即可,由於這個Key也不是用於軍事目的,所以也無需設置密碼。如果一切順利的話,可以在用戶主目錄裏找到.ssh目錄,裏面有id_rsaid_rsa.pub兩個文件,這兩個就是SSH Key的祕鑰對,id_rsa是私鑰,不能泄露出去,id_rsa.pub是公鑰,可以放心地告訴任何人。
  2. 第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推送了。

3.1 把本地庫的所有內容推送到遠程庫上:

1.首先在Github創建learngit倉庫:
在這裏插入圖片描述
2.在本地的learngit倉庫下運行命令:

$ git remote add origin git@github.com:datamonday/learngit.git

3.把本地庫的所有內容推送到遠程庫上:

git push -u origin master

當你第一次使用Git的clone或者push命令連接GitHub時,會得到一個警告:

The authenticity of host 'github.com (xx.xx.xx.xx)' can't be established.
RSA key fingerprint is xx.xx.xx.xx.xx.
Are you sure you want to continue connecting (yes/no)?

這是因爲Git使用SSH連接,而SSH連接在第一次驗證GitHub服務器的Key時,需要你確認GitHub的Key的指紋信息是否真的來自GitHub的服務器,輸入yes回車即可。Git會輸出一個警告,告訴你已經把GitHub的Key添加到本機的一個信任列表裏了:

Warning: Permanently added 'github.com' (RSA) to the list of known hosts.

這個警告只會出現一次,後面的操作就不會有任何警告了。把本地庫的內容推送到遠程,用git push命令,實際上是把當前分支master推送到遠程。

注意 此處,原作者的教程並沒有創建readme.md。此處我創建之後po出了錯誤:

$ git push -u origin master
To github.com:datamonday/learngit.git
 ! [rejected]        master -> master (fetch first)
error: failed to push some refs to '[email protected]:datamonday/learngit.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.

經查找解決方案解決了該錯誤:
“出現錯誤的主要原因是github中的README.md文件不在本地代碼目錄中
可以通過如下命令進行代碼合併【注:pull=fetch+merge】 git pull --rebase origin master
執行上面代碼後可以看到本地代碼庫中多了README.md文件。此時再執行語句 git push -u origin master即可完成代碼上傳到github” 執行該命令之後,再次執行git push命令,成功推送到github。

$ git push -u origin master
Enumerating objects: 11, done.
Counting objects: 100% (11/11), done.
Delta compression using up to 8 threads
Compressing objects: 100% (9/9), done.
Writing objects: 100% (10/10), 927 bytes | 309.00 KiB/s, done.
Total 10 (delta 2), reused 0 (delta 0)
remote: Resolving deltas: 100% (2/2), done.
To github.com:datamonday/learngit.git
   c91a8ae..26e25a7  master -> master
Branch 'master' set up to track remote branch 'master' from 'origin'.

3.2 從遠程倉庫克隆

1.執行克隆項目命令:
git clone [url]

4 分支管理

分支在實際中有什麼用呢?假設你準備開發一個新功能,但是需要兩週才能完成,第一週你寫了50%的代碼,如果立刻提交,由於代碼還沒寫完,不完整的代碼庫會導致別人不能幹活了。如果等代碼全部寫完再一次提交,又存在丟失每天進度的巨大風險。

現在有了分支,就不用怕了。你創建了一個屬於你自己的分支,別人看不到,還繼續在原來的分支上正常工作,而你在自己的分支上幹活,想提交就提交,直到開發完畢後,再一次性合併到原來的分支上,這樣,既安全,又不影響別人工作。

在這裏插入圖片描述

4.1 創建與合併分支

1.首先,我們創建dev分支,然後切換到dev分支:

$ git checkout -b dev
Switched to a new branch 'dev'

git checkout命令加上-b參數表示創建並切換,相當於以下兩條命令:

$ git branch dev
$ git checkout dev
Switched to branch 'dev'

2.然後,用git branch命令查看當前分支:

$ git branch
* dev
  master

git branch命令會列出所有分支,當前分支前面會標一個*號。

3.然後,我們就可以在dev分支上正常提交,比如對readme.txt做個修改,加上一行:

Creating a new branch is quick.

然後提交:

$ git commit -m "branch test"
[dev 3c83ef1] branch test
 1 file changed, 2 insertions(+), 1 deletion(-)

4.現在,dev分支的工作完成,我們就可以切換回master分支:

$ git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.

切換回master分支後,再查看一個readme.txt文件,剛纔添加的內容不見了!因爲那個提交是在dev分支上,而master分支此刻的提交點並沒有變:
在這裏插入圖片描述
5.現在,我們把dev分支的工作成果合併到master分支上:

$ git merge dev
Updating 26e25a7..3c83ef1
Fast-forward
 readme.txt | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

git merge命令用於合併指定分支到當前分支。合併後,再查看readme.txt的內容,就可以看到,和dev分支的最新提交是完全一樣的。

注意到上面的Fast-forward信息,Git告訴我們,這次合併是“快進模式”,也就是直接把master指向dev的當前提交,所以合併速度非常快。當然,也不是每次合併都能Fast-forward,我們後面會講其他方式的合併。合併完成後,就可以放心地刪除dev分支了:

$ git branch -d dev
Deleted branch dev (was 3c83ef1).

刪除後,查看branch,就只剩下master分支了:

$ git branch
* master

因爲創建、合併和刪除分支非常快,所以Git鼓勵你使用分支完成某個任務,合併後再刪掉分支,這和直接在master分支上工作效果是一樣的,但過程更安全。

switch

我們注意到切換分支使用git checkout <branch>,而前面講過的撤銷修改則是git checkout -- <file>,同一個命令,有兩種作用,確實有點令人迷惑。

實際上,切換分支這個動作,用switch更科學。因此,最新版本的Git提供了新的git switch命令來切換分支:

創建並切換到新的dev分支,可以使用:

$ git switch -c dev

直接切換到已有的master分支,可以使用:

$ git switch master

使用新的git switch命令,比git checkout要更容易理解。

4.2 解決衝突

準備新的feature1分支,繼續我們的新分支開發:

$ git switch -c feature1
Switched to a new branch 'feature1'

修改readme.txt最後一行,改爲:

Creating a new branch is quick AND simple.

在feature1分支上提交:

$ git add readme.txt
$ git commit -m "AND simple"
[feature1 8d815b8] AND simple
 1 file changed, 2 insertions(+), 1 deletion(-)

切換到master分支:

$ git switch master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)

Git還會自動提示我們當前master分支比遠程的master分支要超前1個提交。
在master分支上把readme.txt文件的最後一行改爲:

Creating a new branch is quick & simple.

提交:

$ git add readme.txt 
$ git commit -m "&simple"
[master 0e1f71d] &simple
 1 file changed, 2 insertions(+), 1 deletion(-)

現在,master分支和feature1分支各自都分別有新的提交,變成了這樣:
在這裏插入圖片描述
這種情況下,Git無法執行“快速合併”,只能試圖把各自的修改合併起來,但這種合併就可能會有衝突,我們試試看:

$ 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也可以告訴我們衝突的文件:

$ git status
On branch master
Your branch is ahead of 'origin/master' by 2 commits.
  (use "git push" to publish your local commits)

You have unmerged paths.
  (fix conflicts and run "git commit")
  (use "git merge --abort" to abort the merge)

Unmerged paths:
  (use "git add <file>..." to mark resolution)
        both modified:   readme.txt

no changes added to commit (use "git add" and/or "git commit -a")

我們可以直接查看readme.txt的內容:

Git is a distributed version control system.
Git is free software.
<<<<<<< HEAD
Creating a new branch is quick & simple.
=======
Creating a new branch is quick AND simple.
>>>>>>> feature1

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

Creating a new branch is quick and simple.

再提交:

$ git add readme.txt 
$ git commit -m "conflict fixed"
[master 521e03c] conflict fixed

現在,master分支和feature1分支變成了下圖所示:
在這裏插入圖片描述
用帶參數的git log也可以看到分支的合併情況:

$ git log --graph --pretty=oneline --abbrev-commit
*   521e03c (HEAD -> master) conflict fixed
|\
| * 8d815b8 (feature1) AND simple
* | 0e1f71d &simple
|/
* 3c83ef1 branch test
* 26e25a7 (origin/master) remove test.txt
* fa4710c add test.txt
* 5c0719d add distributed
* 4c7bb86 wrote as readme file
* c91a8ae Initial commit

最後,刪除feature1分支:

$ git branch -d feature1
Deleted branch feature1 (was 8d815b8).

工作完成。

小結

當Git無法自動合併分支時,就必須首先解決衝突。解決衝突後,再提交,合併完成。
解決衝突就是把Git合併失敗的文件手動編輯爲我們希望的內容,再提交。
git log --graph命令可以看到分支合併圖。

4.3 分支管理策略

通常,合併分支時,如果可能,Git會用Fast forward模式,但這種模式下,刪除分支後,會丟掉分支信息。如果要強制禁用Fast forward模式,Git就會在merge時生成一個新的commit,這樣,從分支歷史上就可以看出分支信息。

下面我們實戰一下--no-ff方式的git merge

首先,仍然創建並切換dev分支:

$ git switch -c dev
Switched to a new branch 'dev'

修改readme.txt文件,並提交一個新的commit:

$ git add readme.txt 
$ git commit -m "add merge"
[dev c3d9527] add merge
 1 file changed, 1 insertion(+), 1 deletion(-)

現在,我們切換回master:

$ git switch master
Switched to branch 'master'

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

$ git merge --no-ff -m "merge with no-off" dev
Merge made by the 'recursive' strategy.
 readme.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

因爲本次合併要創建一個新的commit,所以加上-m參數,把commit描述寫進去。合併後,我們用git log看看分支歷史:

$ git log --graph --pretty=oneline --abbrev-commit
*   35752ac (HEAD -> master) merge with no-off
|\
| * c3d9527 (dev) add merge
|/
*   521e03c conflict fixed
|\
| * 8d815b8 AND simple
* | 0e1f71d &simple
|/
* 3c83ef1 branch test
* 26e25a7 (origin/master) remove test.txt
* fa4710c add test.txt
* 5c0719d add distributed
* 4c7bb86 wrote as readme file
* c91a8ae Initial commit

可以看到,不使用Fast forward模式,merge後就像這樣:
在這裏插入圖片描述

分支策略

在實際開發中,我們應該按照幾個基本原則進行分支管理:

首先,master分支應該是非常穩定的,也就是僅用來發布新版本,平時不能在上面幹活;

那在哪幹活呢?幹活都在dev分支上,也就是說,dev分支是不穩定的,到某個時候,比如1.0版本發佈時,再把dev分支合併到master上,在master分支發佈1.0版本;你和你的小夥伴們每個人都在dev分支上幹活,每個人都有自己的分支,時不時地往dev分支上合併就可以了。

所以,團隊合作的分支看起來就像這樣:
在這裏插入圖片描述

4.4 Bug分支

軟件開發中,bug就像家常便飯一樣。有了bug就需要修復,在Git中,由於分支是如此的強大,所以,每個bug都可以通過一個新的臨時分支來修復,修復後,合併分支,然後將臨時分支刪除。

當你接到一個修復一個代號101的bug的任務時,很自然地,你想創建一個分支issue-101來修復它,但是,等等,當前正在dev上進行的工作還沒有提交:

$ 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功能,可以把當前工作現場“儲藏”起來,等以後恢復現場後繼續工作:

$ git stash
Saved working directory and index state WIP on dev: f52c633 add merge

現在,用git status查看工作區,就是乾淨的(除非有沒有被Git管理的文件),因此可以放心地創建分支來修復bug。

首先確定要在哪個分支上修復bug,假定需要在master分支上修復,就從master創建臨時分支:

$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 6 commits.
  (use "git push" to publish your local commits)

$ git checkout -b issue-101
Switched to a new branch 'issue-101'

現在修復bug,需要把“Git is free software …”改爲“Git is a free software …”,然後提交:

$ git add readme.txt 
$ git commit -m "fix bug 101"
[issue-101 4c805e2] fix bug 101
 1 file changed, 1 insertion(+), 1 deletion(-)

修復完成後,切換到master分支,並完成合並,最後刪除issue-101分支:

$ git switch master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 6 commits.
  (use "git push" to publish your local 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(-)

到dev分支:

$ git switch dev
Switched to branch 'dev'

$ git status
On branch dev
nothing to commit, working tree clean

工作區是乾淨的,剛纔的工作現場存到哪去了?用git stash list命令看看:

$ git stash list
stash@{0}: WIP on dev: f52c633 add merge

工作現場還在,Git把stash內容存在某個地方了,但是需要恢復一下,有兩個辦法:

一是用git stash apply恢復,但是恢復後,stash內容並不刪除,你需要用git stash drop來刪除;

另一種方式是用git stash pop,恢復的同時把stash內容也刪了:

$ 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} (5d677e2ee266f39ea296182fb2354265b91b3b2a)
再用git stash list查看,就看不到任何stash內容了:

$ git stash list

你可以多次stash,恢復的時候,先用git stash list查看,然後恢復指定的stash,用命令:

$ git stash apply stash@{0}

同樣的bug,要在dev上修復,我們只需要把4c805e2 fix bug 101這個提交所做的修改“複製”到dev分支。注意:我們只想複製4c805e2 fix bug 101這個提交所做的修改,並不是把整個master分支merge過來。

爲了方便操作,Git專門提供了一個cherry-pick命令,讓我們能複製一個特定的提交到當前分支:

$ git branch
* dev
  master
$ git cherry-pick 4c805e2
[master 1d4b803] fix bug 101
 1 file changed, 1 insertion(+), 1 deletion(-)

Git自動給dev分支做了一次提交,注意這次提交的commit是1d4b803,它並不同於master的4c805e2,因爲這兩個commit只是改動相同,但確實是兩個不同的commit。用git cherry-pick,我們就不需要在dev分支上手動再把修bug的過程重複一遍。

4.5 Feature分支

1.創建新的分支準備開發

$ git switch -c feature-vulcan
Switched to a new branch 'feature-vulcan'

2.開發完畢:

$ git add vulcan.py

$ git status
On branch feature-vulcan
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	new file:   vulcan.py

$ git commit -m "add feature vulcan"
[feature-vulcan 287773e] add feature vulcan
 1 file changed, 2 insertions(+)
 create mode 100644 vulcan.py

3.準備合併:

$ git switch dev

4.此時需要就地銷燬feature分支,執行刪除命令,但是銷燬失敗。使用強制刪除命令參數-D解決。

$ git branch -d feature-vulcan
error: The branch 'feature-vulcan' is not fully merged.
If you are sure you want to delete it, run 'git branch -D feature-vulcan'.

銷燬失敗。Git友情提醒,feature-vulcan分支還沒有被合併,如果刪除,將丟失掉修改,如果要強行刪除,需要使用大寫的-D參數。現在我們強行刪除:

$ git branch -D feature-vulcan
Deleted branch feature-vulcan (was 287773e).

4.6 多人協作

4.6.1 多人協作

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

$ git remote
origin

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

$ git remote -v
origin  git@github.com:datamonday/learngit.git (fetch)
origin  git@github.com:datamonday/learngit.git (push)

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

4.6.2 推送分支

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

$ git push origin master

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

$ git push origin dev

但是,並不是一定要把本地分支往遠程推送,那麼,哪些分支需要推送,哪些不需要呢?

  • master分支是主分支,因此要時刻與遠程同步;
  • dev分支是開發分支,團隊所有成員都需要在上面工作,所以也需要與遠程同步;
  • bug分支只用於在本地修復bug,就沒必要推到遠程了,除非老闆要看看你每週到底修復了幾個bug;
  • feature分支是否推到遠程,取決於你是否和你的小夥伴合作在上面開發。

4.6.3 抓取分支

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

現在,模擬一個你的小夥伴,可以在另一臺電腦(注意要把SSH Key添加到GitHub)或者同一臺電腦的另一個目錄下克隆:

$ git clone git@github.com:xx/xx.git

當你的小夥伴從遠程庫clone時,默認情況下,你的小夥伴只能看到本地的master分支。不信可以用git branch命令看看:

$ git branch
* master

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

$ git checkout -b dev origin/dev

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

$ git add env.txt
$ git commit -m "add env"
$ git push origin dev

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

$ cat env.txt
env
$ git add env.txt
$ git commit -m "add new env"

$ git push origin dev
To github.com:michaelliao/learngit.git
 ! [rejected]        dev -> dev (non-fast-forward)
error: failed to push some refs to '[email protected]:michaelliao/learngit.git'

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

$ git pull
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的鏈接:

$ git branch --set-upstream-to=origin/dev dev
Branch 'dev' set up to track remote branch 'dev' from 'origin'.

再pull:

$ git pull
Auto-merging env.txt
CONFLICT (add/add): Merge conflict in env.txt
Automatic merge failed; fix conflicts and then commit the result.

這回git pull成功,但是合併有衝突,需要手動解決,解決的方法和分支管理中的解決衝突完全一樣。解決後,提交,再push:

$ git commit -m "fix env conflict"
[dev 57c53ab] fix env conflict
$ git push origin dev

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

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

如果git pull提示no tracking information,則說明本地分支和遠程分支的鏈接關係沒有創建,用命令git branch --set-upstream-to <branch-name> origin/<branch-name>

小結

  • 查看遠程庫信息,使用git remote -v;
  • 本地新建的分支如果不推送到遠程,對其他人就是不可見的;
  • 從本地推送分支,使用git push origin branch-name,如果推送失敗,先用git pull抓取遠程的新提交;
  • 在本地創建和遠程分支對應的分支,使用git checkout -b branch-name origin/branch-name,本地和遠程分支的名稱最好一致;
  • 建立本地分支和遠程分支的關聯,使用git branch --set-upstream branch-name origin/branch-name;
  • 從遠程抓取分支,使用git pull,如果有衝突,要先處理衝突。

4.6 rebase

rebase操作的特點:把分叉的提交歷史“整理”成一條直線,看上去更直觀。缺點是本地的分叉提交已經被修改過了。細節請參考廖雪峯Git教程

5標籤管理

5.1 創建標籤

在Git中打標籤非常簡單,首先,切換到需要打標籤的分支上:

$ git branch
* dev
  master
$ git checkout master
Switched to branch 'master'

然後,敲命令git tag 就可以打一個新標籤:

$ git tag v1.0

可以用命令git tag查看所有標籤:

$ git tag
v1.0

默認標籤是打在最新提交的commit上的。有時候,如果忘了打標籤,比如,現在已經是週五了,但應該在週一打的標籤沒有打,怎麼辦?方法是找到歷史提交的commit id,然後打上就可以了:

$ git log --pretty=oneline --abbrev-commit

比方說要對add merge這次提交打標籤,它對應的commit id是f52c633,敲入命令:

$ git tag v0.9 f52c633

再用命令git tag查看標籤:

$ git tag
v0.9
v1.0

注意,標籤不是按時間順序列出,而是按字母排序的。可以用git show 查看標籤信息:

$ git show v0.9

可以看到,v0.9確實打在add merge這次提交上。

還可以創建帶有說明的標籤,用-a指定標籤名,-m指定說明文字:

$ git tag -a v0.1 -m "version 0.1 released" 1094adb

用命令git show <tagname>可以看到說明文字:

5.2 操作標籤

如果標籤打錯了,也可以刪除:

$ git tag -d v0.1
Deleted tag 'v0.1' (was f15b0dd)

因爲創建的標籤都只存儲在本地,不會自動推送到遠程。所以,打錯的標籤可以在本地安全刪除。
如果要推送某個標籤到遠程,使用命令git push origin :

$ git push origin v1.0

或者,一次性推送全部尚未推送到遠程的本地標籤:

$ git push origin --tags

如果標籤已經推送到遠程,要刪除遠程標籤就麻煩一點,先從本地刪除:

$ git tag -d v0.9

然後,從遠程刪除。刪除命令也是push,但是格式如下:

$ git push origin :refs/tags/v0.9
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章