Git 詳解(一) 本地操作

寫在前面

對於一門技術而言,20% 的知識可以解決你工作中遇到的 80% 的問題,而剩下的 80% 屬於冷門知識,你可能很少會用到它們。對於 Git 而言,亦是如此。

因此,我不可能將全部 Git 相關的知識點盡數列出,這也是不切實際的,因爲技術總是在進步,在更新,在迭代。我能做的唯有將這門技術的精華與原理儘可能的說與你聽,當然這其中必然摻雜着描述上的錯誤抑或本人理解上的不正確。所以我希望你可以在閱讀之餘,參考其他博主的文章來提高自己,亦糾正我的錯誤。

本文演示使用的 Git 版本爲 1.8,操作系統爲 CentOS 7。話不多說,下面就開始 Git 之旅吧!

 

一次完整的提交

創建 mygit 目錄,並將 mygit 目錄初始化爲 Git 倉庫

git init

該指令執行完成後,可以發現 mygit 目錄下出現隱藏目錄 .git :

在 mygit 目錄下創建文件 test.txt,查看 Git 工作目錄下的文件狀態: 

git status

此時文件 test.txt 處於未跟蹤(untracked)狀態:

跟蹤 test.txt 文件,並將該文件從工作區提交到暫存區

git add test.txt

再次執行 git status 指令查看 Git 工作目錄下的文件狀態,可以看到 test.txt 文件的狀態從未跟蹤變成了等待提交(已暫存):

將暫存區的文件提交至版本庫

git commit

由於是首次提交,Git 會提示需要先配置用戶名和郵箱:

按照提示信息,配置用戶名和郵箱:

git config --global user.name 'zhangsan'

git config --global user.email '[email protected]'

配置完成後,再次執行 git commit 指令。此時會打開操作系統默認的文件編輯器,提示輸入提交說明:

提交說明填寫完成之後,即可將暫存區的文件提交到版本庫。

查看 Git 倉庫的提交日誌:

git log

提交日誌會顯示每條提交記錄的作者、提交時間和提交說明: 

至此,一次完整的提交結束。

 

工作區、暫存區和版本庫

Git 倉庫可以分爲三個區域:

  • 工作區:查看、編輯、保存文件的地方,也是用戶能直接操作到的地方。
  • 暫存區:英文爲 stage。對應 .git/index 文件,因此有時也將暫存區叫作索引(index)。實際上就是一個包含文件索引的目錄樹,在這個虛擬工作區的目錄樹中,記錄了文件名、文件的狀態信息(時間戳、文件長度等),文件的內容並不存儲其中,而是保存在 Git 對象庫(.git/objects)中,文件索引建立了文件和對象庫中對象實體之間的對應
  • 版本庫:即 Git 倉庫下隱藏目錄 .git 。

:上一節中我用了 “Git 工作目錄” 這個詞,可以理解爲工作區和暫存區的統稱。例如,執行 git status 指令會列出工作區和暫存區的文件狀態變化,而我在上一節中描述爲:“查看 Git 工作目錄(即 mygit 目錄)下的文件狀態”。

 

status

Git 工作目錄下的每一個文件不外乎兩種狀態:未跟蹤(untrackded)已跟蹤(tracked)

Git 官網對二者的定義如下:已跟蹤的文件指納入版本庫的文件,在工作一段時間後,它們的狀態可能處於未修改已修改已暫存。工作目錄中除已跟蹤文件之外的所有其他文件都屬於未跟蹤文件。

上面定義中的“已跟蹤”,我更願意稱之爲“廣義上的已跟蹤”。在一次完整的提交小節中,對未跟蹤文件 test.txt 執行 git add <file>指令後,該文件的狀態就已變成已跟蹤狀態(已暫存)了,而此時 tes.txt 文件並未被納入版本庫。但是這種已跟蹤狀態是暫時的,如果 test.txt 文件在被提交到版本庫之前你就從將它從暫存區移除,那麼該文件的狀態會重置爲未跟蹤。

因此,廣義上的已跟蹤指的是文件被納入版本庫,狹義上的已跟蹤指的是文件存在於暫存區。這個概念你只要瞭解就行了,後文提到的已跟蹤文件指的採用狹義上的已跟蹤

簡而言之,Git 管理的文件分爲兩類:

  • 未跟蹤
  • 已跟蹤:又可細分爲未修改、已修改、已暫存

使用 Git 管理的文件生命週期如下(Ps:理想狀態下):

用下面的指令就可以查看 Git 工作目錄下的文件狀態: 

git status

使用該指令的輸出信息十分詳細,同時也過於繁瑣。對於很熟悉文件狀態的人,可以使用下面的指令得到更爲簡潔的輸出:

git status --short   // 等價於

git status -s

輸出結果示例:

新添加的未跟蹤文件前面用 ?? 標記,新添加到暫存區的文件前面用 A 標記,修改過的文件前面有 M 標記。你可能注意到了 有兩個可以出現的位置,出現在右邊的 表示該文件被修改了但是還沒放入暫存區,出現在靠左邊的 表示該文件(已納入版本庫)被修改了並放入了暫存區。至於 test2.txt 文件,它在工作區被修改並提交到暫存區後又在工作區中被修改了,所以在暫存區和工作區都有該文件被修改的記錄。

 

config

前文提到使用 git config 指令可以配置用戶名和郵箱:

git config --global user.name 'zhangsan'

git config --global user.email '[email protected]'

該指令中使用的是 --global 參數,除此之外還有 --system 和 --local 兩個參數,它們的含義如下:

  • --system:包含系統上每一個用戶及他們倉庫的通用配置,配置文件爲 ./etc/gitconfig(執行該命令之後纔會創建該文件)
  • --global:只針對當前用戶,配置文件爲 ~/.gitconfig(執行該命令之後纔會創建該文件)
  • --local:針對該倉庫,配置文件爲 .git/config(使用 git init 命令就會創建該文件)

如果省略級別參數,則默認使用的 --local 參數。若三種級別的用戶名和郵箱都做了配置,則低級別會覆蓋高級別的配置,所以 .git/config 中的配置變量會覆蓋 /etc/gitconfig 中的配置變量。

順帶一提,在 Windows 系統中三個配置文件分別位於:

  • system:C:\Program Files\Git\mingw64\etc\gitconfig(需要使用管理員權限配置)
  • global:C:\Users\username\.gitconfig
  • local:Git 工作目錄下 .git/config

雖然不使用 git config 指令,也可以進入對應配置文件進行 Git 相關信息的配置,因爲執行 git config 指令的本質就是就是操作配置文件(強烈不建議這麼做)。例如執行上面的兩條指令,其本質就是在 ~/.gitconfig 文件中寫入如下信息:

即使你誤將 user.email 寫成了 user.mail,配置文件中還是會寫入對應的信息:

如果要刪除寫錯的信息,執行下面的指令即可:

git config --global --unset user.mail 

如果你想查看配置的所有用戶名和郵箱,可以使用下面的指令列出所有 Git 能找到的配置信息(包括系統、全局和本地配置):

git config --list

 

add

在一次完整的提交小節中,執行  git add <file> 指令做了兩件事情:

  1. 跟蹤文件
  2. 將文件從工作區提交到暫存區

這是因爲該指令操作的對象是未跟蹤的新文件。如果對納入版本庫的文件執行該指令,那麼 git add <file> 所表達的含義就只有將文件從工作區提交到暫存區。

若需要提交的文件數量較多,該指令便略顯雞肋,此時可以使用下面的指令:

git add .

該指令將工作區修改(不包括刪除)新增的文件添加到暫存區。

從上圖可以看到,git add 指令默認使用 --ignore-removal 參數(忽略刪除)。若想把刪除的文件也提交到暫存區,需要使用 --all 或 -A 參數:

git add --all   //  等價於

git add -A

該指令表示將工作區所有的新增修改(包括刪除)的文件添加到暫存區

若只想將已跟蹤的文件添加到暫存區,可以使用 -u (update)參數(包括刪除的文件但不添加未追蹤的文件):

git add -u

如果想要強制添加某個文件,不管 .gitignore(見後文詳述)是否包含這個文件,可以使用 -f 參數:

git add -f <file>

 

commit

git commit 指令用於將暫存區的文件納入版本庫,直接輸入該指令會啓動系統默認的文本編輯器以輸入提交的說明(一般是 vi,我在Windows 的 cmd 中嘗試之後,發現竟然也打開了 vi)。當然你也可以使用下面的指令設定你喜歡的文本編輯器

git config --global core.editor <editor>

 每次都彈出文件編輯器未免繁瑣,如果只需要提交簡短的說明,可以使用 -m 參數指定提交說明,這樣就不會進入文件編輯模式:

git commit -m <message>

甚至我們也可以跳過 git add 步驟,直接提交工作區的文件。這裏需要使用 -a 參數:

git commit -a

需要注意,這種方式只能提交已跟蹤的文件。

如果提交之後才發現漏掉了幾個文件沒有添加,或者提交信息寫錯了,可以使用 --amend 參數重新提交:

git commit  --amend

注意,執行該指令會生成新的提交記錄,覆蓋上一次的提交記錄。

 

log

使用下面的命令可以查看所有的提交日誌:

git log

不使用任何參數,該指令會按提交時間降序列出 Git 版本庫中所有的提交記錄,最近的提交記錄排在最上面。 這個命令會列出每次提交的 SHA-1 值(commit)、作者的名字和電子郵件地址(Author)、提交時間(Date)以及提交說明。

一般情況下,該指令會搭配各種參數使用。下面是幾個常用的參數

  • --graph:圖形化顯示
  • --stat:文件變化信息
  •  --pretty=oneline:每個提交放在一行顯示
  • --abbrev-commit:僅顯示 SHA-1 的前幾個字符

除了提交日誌以外,還可以查看操作日誌:

git reflog

 

rm 與 git rm

有時候我們需要將 Git 工作目錄下的某個文件刪除,有兩種方式:

rm <file>:系統自帶的文件刪除功能,直接將文件刪除

git rm <file>:刪除文件並將此次操作提交到暫存區(未跟蹤的文件不要使用該指令,如果使用 git rm 會報錯)

對於被納入版本庫的文件(執行過 git commit 的文件),如果出現誤刪,這兩種刪除都是可以還原的:

1、使用 rm 指令刪除文件,由於文件只在工作區被刪除,執行下面的指令直接將文件從暫存區恢復到工作區,一般用於丟棄工作區對該文件的修改:

git checkout -- <file>

2、使用 git rm 指令刪除文件,由於該文件在工作區和暫存區均已刪除,需要先將版本庫中最近一次的提交恢復到暫存區:

git reset HEAD 

執行上面的指令可以將當前所在分支(見後文詳述)的暫存區重置爲最新一次提交的樣子。接着就和上面恢復刪除文件的做法一樣,將文件從暫存區恢復到工作區即可:

git checkout -- <file>

兩種刪除方式總結:

  • rm <file>:只刪除工作區的文件,直接執行 git checkout -- <file> 就可以還原(未納入版本庫的文件無法還原)
  • git rm <file>:同時將該文件在工作區和暫存區刪除,需要從版本庫恢復被刪除的文件,直接執行 git checkout -- <file> 是無法還原的。

當然 git rm 也可以只刪除暫存區的文件,只需要加上 --cached 參數即可:

git rm --cached <file>

該指令只會刪除暫存區的文件,工作區的文件仍然會保留。

如果是沒有納入 Git 版本庫的已跟蹤文件(執行過 git add 但是沒有執行 git commit),使用 git rm 刪除該文件,也會出現警告信息。

這是因爲刪除此文件只存在於工作區和暫存區,版本庫中並沒有該文件,所以無法從版本庫中復原該文件。想要刪除此類文件需要使用 -f 參數:

git rm -f <file>

 

clean

藉助於 git rm 指令,可以刪除已跟蹤的文件。那麼未跟蹤的文件(既沒有 git add 也沒有 git commit)就只能通過 rm 指令刪除了嗎?自然 Git 不會允許這種情況出現,可以使用下面的指令刪除當前所在目錄下的未跟蹤文件:

git clean

如果直接執行該指令你應該可以看到報錯信息,這是因爲 Git 配置中有一個參數 clean.requireForce,該參數默認值爲 true,表示執行 git clean 需要確認。你可以使用下面的指令修改該參數的默認值(但是不建議這麼做,因爲有可能之後你不小心執行該指令就直接將文件刪除了):

git config --local clean.requireForce false

推薦的做法是在執行該指令的時候加上 -f 參數,表示強制刪除文件(不會刪除 .gitignore 文件中指定的文件, 不管這些文件是否被跟蹤):

git clean -f

如果想刪除 .gitignore 文件中的指定的文件,則需要使用 -x 參數:

git clean -xf

使用該指令會刪除當前所在目錄下所有未跟蹤的文件,不管是否是 .gitignore 文件中是否指定該文件。 

如果要連帶刪除文件夾,可以使用 -d 參數:

git clean -df

執行該指令會刪除當前目錄下的未跟蹤的文件和文件夾。

因爲在執行刪除之前,最好先執行下面的指令:

git clean -n

執行該指令會提示哪些文件會被刪除,並不會真正的刪除文件:

 

.gitignore

初始化 Git 倉庫並不提供該文件,.gitignore 文件需要手動創建,配置在 Git 倉庫根目錄下即可。執行某些指令時,Git 會自動忽略在該文件中指定的文件夾或文件。例如上一節提到的 git clean -f

常用忽略提交規則如下(**表示任意個文件夾):

# :表示此爲註釋,將被Git忽略

*.a :表示忽略所有 .a 結尾的文件

!lib.a :表示但lib.a除外

/TODO:表示僅僅忽略項目根目錄下的 TODO 文件,不包括 subdir/TODO

fd1/* :表示忽略目錄 fd1 下的全部內容;注意,不管是根目錄下的 /fd1/ 目錄,還是某個子目錄 /subdir/fd1/ 目錄,都會被忽略

/fd1/* :表示忽略根目錄下的 /fd1/ 目錄下的的全部內容

**/foo: 表示忽略/foo,a/foo,a/b/foo

a/**/b: 表示忽略a/b, a/x/b,a/x/y/b

debug/*.obj: 表示忽略debug/io.obj,不忽略 debug/common/io.obj和tools/debug/io.obj

 

branch

廣義上的分支

分支是 Git 最重要的特性,在講解分支操作之前,有必要說明一下分支是什麼。

在工作區、暫存區和版本庫小節,提到暫存區管理文件的索引,而文件的內容則保存在 Git 對象庫中。那麼對象庫中的對象指的是什麼呢?對象指的就是提交對象(commit object),在進行提交操作時,Git 會在對象庫中創建一個提交對象。

提交對象包含作者的名字和郵箱、提交時間和說明以及本次提交的 SHA-1 值,也就是執行 git log 時看到的信息。其實除了這些可見的信息之外,提交對象中還包含兩條不可見的信息:上一次和下一次提交記錄的 SHA-1 值(即父提交和子提交)。

藉助於可視化工具 gitk,我們來看一下這兩條信息:

通過提交對象的父提交和子提交屬性,所有的提交記錄就形成了一條提交鏈。廣義上,我們將提交鏈稱之爲分支。

分支操作

初始化 Git 倉庫,只有一個分支 —— master 分支。使用下面的指令可以查看所有分支以及當前所在分支:

git branch

創建分支(該分支默認指向所在分支最新一次提交):

git branch <branch>

創建完成之後,執行下面的指令就可以切換到新創建的分支:

git checkout <branch>

先創建分支再切換到新分支,兩條指令可以直接用下面的指令代替:

git checkout -b <branch>

需要注意的是,分支切換時如果原分支上有未提交的文件修改,且該文件修改和要切換後分支上的文件沒有衝突(衝突見後文詳述),那麼該文件修改會帶到切換後分支的工作區和暫存區。如果你在切換後分支上進行提交操作,那麼原分支上的文件修改會被記錄在切換後分支上。

但是如果原分支上未提交的文件修改和切換後分支上的文件存在衝突,那麼此時 Git 會提示你分支切換後你在原分支所做的修改會丟失,建議你在原分支提交修改或者將文件修改儲藏(見後文詳述)。如下圖所示:

不過一般來說,進行分支切換時我們都會選擇將原分支的工作內容儲藏。

各個分支都會有不同提交記錄,使用 -v 參數可以查看各分支最新的提交記錄:

git branch -v

上圖中,創建 newb 分支時 master 分支的最新一次提交的提交說明爲 first commit,所以 newb 分支默認指向此次提交。

如果某個分支不再需要,可以使用 -d 參數刪除:

git branch -d <branch>

注意刪除分支,不可以刪除所在的分支。如果要刪除所在分支,需要先切換到其他分支再刪除分支:

如果要刪除的分支存在沒有合併(見後文詳述)的提交記錄,則需要使用 -D 參數強制刪除分支:

git branch -D <branch>

使用 -m 參數可以修改分支名:

git branch -m <branch> <newbranch>

狹義上的分支

進入 .git/refs/heads 目錄,查看該目錄下的文件:

可以看到該目錄下存放着所有的分支信息,查看 master 分支信息:

對比 master 分支的歷史提交記錄,不難發現 master 分支記錄的是最新一次提交記錄的 SHA-1 值。換而言之,所謂的分支其實只是一個指針,指向最新一次的提交對象。

所以在解釋 git branch <branch> 指令時,我使用了“指向”這個詞。因爲狹義上,分支是一個指針

 

checkout

在 rm 與 git rm 小節 和 branch 小節中都提到了 git checkout 指令,兩次出現該指令的作用分別是:

git checkout -- <file> :將文件從暫存區恢復到工作區

git checkout <branch> :切換分支

除了切換分支,通過該指令還可以切換到歷史快照(即歷史提交記錄):

git checkout <SHA-1>

SHA-1 值長達40個字符,不需要全部輸入,只需輸入前7、8個字符即可。當切換到歷史快照時,你將處於一種遊離狀態:

在遊離狀態下,你當前所在的分支就像下面這樣:

而一旦你進行分支切換操作,這個“遊離分支”將會消失:

因此,如果你在遊離狀態下提交記錄,那麼最好在該提交記錄上創建分支,否則你很難找回該提交記錄。

方式一:在提交記錄上直接執行 git checkout -b <branch>。

方式二:如果你忘記創建分支就切換到了別的分支,那麼可以通過 git reflog 找到那次提交記錄的 SHA-1 值,然後執行 git branch <branch> <SHA-1> 在指定提交記錄上創建分支。

checkout 原理

當執行 git branch 指令時,你可以看到當前所在的分支。那麼必然存在着一個數據記錄我們當前所在的分支,這個數據就是 HEAD 文件,在 rm 與 git rm 小節中我們就已經見過它了(不過 reset 相關知識會在下一小節詳解,這裏不過多描述)。

查看 HEAD 文件中的內容:

HEAD 文件中存放的就是當前所在分支的引用,而通過上一節我們知道,分支本質上是指針,所以 HEAD 的本質也是一個指針,是一個指向指針的指針,最終指向一個提交對象。

切換到 newb 分支,再次查看 HEAD 文件的內容:

那麼當我們切換到歷史快照時,HEAD 文件中存放的又是什麼呢?

當我們處於遊離狀態時,HEAD 就充當了分支的作用,存放歷史提交記錄的 SHA-1 值,指向一個具體的提交對象。

 

reset

在 rm 與 git rm 小節中提到,git reset HEAD 指令可以將版本庫中最新一次的提交恢復到暫存區。更確切的說,應該是將所在分支回退到上一次提交時的狀態:

git reset HEAD

執行上面的指令回退版本會修改暫存區,但是不改變工作區。這就是爲什麼版本回退之後,還需要執行 git checkout -- <file> 指令的原因

當然你也可以在回退版本時,搭配參數使用。使用 --soft 參數,可以在版本回退時不改變暫存區和工作區:

git reset --soft HEAD

而 --hard 參數則會在版本回退的同時,改變暫存區和工作區:

git reset --hard HEAD

如果只想將某個文件版本回退,可以在執行 git reset HEAD 指令時加上文件名(使用此指令時,不可搭配 soft 或 hard 參數):

git reset HEAD <file>

有時候我們需要回退多個版本,應該怎麼做呢?此時可以在 git reset  HEAD 的後面加上 ^,有幾個 ^ 就表示回退多少個版本,例如回退到倒數第二個版本:

git reset HEAD^

如果需要回退多個版本時,豈不是需要使用很多的 ^?自然不是這樣,此時就可以使用 ~n 表示回退多少個版本:

git reset HEAD~n

如果需要回退到很久遠的版本,就可以使用 git log --abbrev-commit 查看版本的 SHA-1 值(只需要前7、8個字符就可以),然後使用下面的指令回退到指定版本:

git reset <SHA-1>

那麼問題來了,怎麼樣才能從歷史版本回到最新版本呢?這裏就需要使用之前提到過的一個指令:git reflog。藉由該指令可以查看每一次操作的記錄,找到需要回到那一次操作的 SHA-1 值,再使用上面的指令就可以回到最新的版本。

reset 原理

通過前文的學習,我們知道執行 git checkout <SHA-1>  可以修改 HEAD 文件,那麼 git reset HEAD 是否也修改了 HEAD 文件呢?從字面意思上看該指令是修改了 HEAD 文件,其實該指令修改的是 HEAD 記錄的分支的指向。

當我們在 master 分支上執行 git reset HEAD^ 指令時,Git 會根據 HEAD 文件中記錄的分支引用,找到該分支指向的提交對象,然後根據該提交對象的 parent 屬性找到父提交對象,最後將該分支指向提交對象替換爲父提交對象。

通過對 branch、checkout 和 reset 的講解,我們可以總結出提交對象、分支、HEAD 的關係,如下如所示:

 

 

merge

branch 小節中提到:刪除分支時,如果該分支存在沒有合併的提交記錄會提示警告信息。那麼如何進行分支合併呢?執行下面的指令,就可以將指定分支合併的當前所在的分支:

git merge <branch>

合併分支需要填寫提交說明,默認的說明格式爲“Merge branch <branch1> into <branch2>”:

上圖演示的是沒有衝突情況下的合併,如果要合併的分支和所在分支存在衝突,那麼就需要你手動解決衝突。

衝突

什麼是衝突呢?其實衝突很簡單,就是兩個分支對同一個文件的同一個位置都做了修改。如果出現衝突,Git 在進行合併操作時,就不知道以哪個分支爲準,所以需要你手動解決衝突——也就是選用哪個分支的修改。

出現衝突時,執行 git status 指令就能看到是哪個文件發生了衝突:

查看發生衝突的文件:

如上圖所示,發生衝突的文件中衝突部分會用不同符號標記出來:

<<<<<<< HEAD:表示所在分支文件衝突內容

=======:表示分割線

>>>>>>> m:表示合併分支文件衝突內容

解決衝突後,使用 git add <file> 指令標記衝突解決,然後提交修改即可。

在 add 小節中總結了 git add 指令有兩種用法:跟蹤文件、將文件從工作區提交到暫存區,在這裏出現的是該指令的第三種也是最後一種用法:標記衝突解決

fast-forward

除了衝突以外,在合併時我們還會遇到另一種情況—— fastforward(快進)。若你當前所在分支是要合併分支的 root commit,那麼執行上面的指令時就會觸發 fast-forward 。

例如,在 master 分支上創建並切換到 bugfix 分支後,一直在 bugfix 分支上提交記錄,而 master 分支上沒有新的提交記錄,那麼 master 分支就是 bugfix 分支的 root commit 。如下所示:

此時我所在分支爲 master,需要將 bugfix 分支合並過來。那麼執行合併操作時就會觸發 fast-forward,輸出信息會提示本次合併使用的是 fast-forward 方式:

可能你注意到了:使用 ff 合併不需要書寫提交記錄,這意味着這種方式的合併直接修改了當前分支的指向。因此使用 ff 合併,原有分支的特性會被覆蓋,不利於以後查看記錄

所以使用 ff 方式合併後,就應該將合併的分支刪除(Git 官方推薦)。或者合併分支的時候,不使用 ff 方式(順帶一提,我在GitBash 上執行這條指令時, GitBash直接就卡死了):

git merge --no-ff <branch>

使用該指令進行合併,合併之後的效果就變成了這樣:

從上圖可以看到,不使用 ff 方式合併,bugfix 分支的特性依然存在。

 

stash

實際開發中,時常會遇到這樣的狀況:所在分支的工作沒有完成,但是需要切換到其他的分支工作,而所在分支的工作內容不足以到達提交的要求。此時就可以使用下面的指令將工作目錄下已跟蹤的文件儲藏:

git stash

從提示信息可以看到,工作區和暫存區的文件都會被儲藏。執行該指令後,文件會暫時從工作區和暫存區移除,回到上次提交時的狀態。此時我們查看工作目錄下的文件狀態,就沒有文件修改信息了。

通過下面的指令可以查看儲藏的工作內容:

git stash list

WIP 即 Work In Process,正在進行中的工作。

等到在別的分支的工作結束後,我們就可以切換回原分支恢復之前的工作內容。執行下面的指令,可以將最新一次儲藏的工作內容恢復到工作區和暫存區:

git stash pop

執行上面的指令,恢復工作內容的同時也會刪除儲藏記錄:

如果想恢復工作內容的同時保留儲藏記錄可以使用下面的指令:

git stash apply

需要注意的是,多個分支共用一個儲藏室:

所以在恢復工作內容時一定要先查看儲藏記錄,而不要盲目的使用 git stash pop 指令恢復最新的一條儲藏記錄。如上圖所示,如果我們想恢復第三條儲藏記錄,就需要使用下面的指令:

git stash apply stash@{2}

前文我說過 git apply 指令不會刪除儲藏記錄,恢復工作內容後可以使用下面的指令刪除指定的儲藏記錄:

git stash drop stash@{1} 

 如果想要清空所有儲藏記錄,可以使用下面的指令:

git stash clear

默認情況下,儲藏記錄格式如下:

stash@{num}: WIP on <branch>: <SHA-1> <commit message>

如果全部使用默認的儲藏記錄格式,一旦儲藏記錄多起來就很難區別每條儲藏記錄的內容,因此在儲藏工作內容時最好加上備註:

git stash save <message>

使用備註後,儲藏記錄格式如下:

前文提到 git stash 指令會儲藏已跟蹤的文件。如果想要儲藏 文件,則需要使用 --include-untracked 參數:

git stash --include-untracked

此外還有一個參數:--keept-index,使用該參數表示不要儲藏暫存區的文件:

git stash --keep-index

 

blame 與 diff

如果一個文件被多個人修改,想知道某一行是誰修改的,此時就可以使用下面的指令:

git blame <file>

執行上面的指令會顯示文件每一行具體的來自於哪次提交:

除 blame 之外,Git 還提供 diff 操作,可以比較文件在工作區、暫存區和版本庫的差異: 

git diff

默認情況下 diff 操作比較比較暫存區和工作區的文件差異,--- 代表源文件,+++ 代表目標文件,通過合併的方式描述源文件如何變成目標文件:

上圖中,a/test.txt 表示暫存區的 test.txt 文件,b/test.txt 表示工作區的 test.txt 文件,暫存區的 test.txt 一共一行,增加一行即可變成工作區的 test.txt 文件。

如果要比較版本庫和工作區的文件,可以在指令後面加上 HEAD,比較最新一次提交和工作區的文件差異:

git diff HEAD

通過 reset 小節的我們可以知道,HEAD 本質上也是一個指針,指向一個提交對象。所以我們完全可以用 SHA-1 值代替 HEAD:

git diff <SHA-1>

上面指令的含義爲,比較某個歷史版本和工作區的文件差異。

當然,我們也可以將目標文件修改爲暫存區的文件,只需要加上 --cached 參數即可。執行下面的指令就可以比較版本庫和暫存區之間的文件差異:

git diff --cached

 默認情況下,diff 操作會比較兩個區域的所有文件,你也可以只比較單個文件差異:

git diff -- <file>

除了比較所在分支文件之間的差異,也可以比較其他分支和所在分支之間工作區的文件差異:

git diff <branch>

當然上面的兩條指令都可以和 --cached 、HEAD 參數配合使用。例如:

既然你可以比較其他分支和本分支的文件差異,那麼你也可以指定兩個分支進行比較:

git diff <branch1>..<branch2>

 

tag

當項目開發到一定程度,一般就會發佈一個新的版本,此時就可以給這一次具有特殊意義提交打上標籤。

標籤分爲兩種:輕量標籤附註標籤。創建一個輕量標籤:

git tag <tag>

通過下面的指令就可以查看標籤信息:

git show <tag>

創建一個附註標籤:

git tag -a <tag>

加上 -m 參數還可以爲輔助標籤添加備註信息:

git tag -a <tag> -m <message>

對比查看輕量標籤和附註標籤的輸出信息可知,除了打標籤時那次提交記錄的詳細信息,附註標籤會比輕量標籤多出標籤名、作者、郵箱、日期和備註信息。

通過下面的指令可以查看所有的標籤:

git tag 

如果錯過了打標籤的時機也沒有關係,你可以爲通過下面的指令爲歷史快照創建標籤:

git tag -a <tag> <SHA-1>

一旦項目大了起來,標籤的數量也會隨之水漲船高,此時就可以使用 -l 參數查找標籤: 

git tag -l 'v1.8.*'

和分支一樣,使用 -d 參數就可以刪除標籤:

git tag -d <tag>

tag 原理

對於一個輕量標籤,當我們執行 git show <tag> 指令時, 就會輸出打標籤時那次提交記錄的詳細信息。那麼標籤到底是什麼呢?標籤文件位於 .git/refs/tags 目錄中,查看標籤 v1.0:

和分支一樣,輕量標籤文件中存放的也是打標籤時那次提交記錄的 SHA-1 值。這樣一來我們就知道,輕量標籤本質上也是一個指針。這也就是爲什麼標籤文件夾和分支文件夾同在 refs 目錄下,因爲它們本質上是一類東西。

那麼附註標籤呢?它也是一個指針麼?確實,附註標籤也是一個指針,只不過它指向的不是提交對象,而是標籤對象。標籤對象中包含標籤名、作者、郵箱、日期、備註信息以及提交記錄的 SHA-1 值,所以附註標籤更多的是類似 HEAD,指向指針的指針。

從上圖可以看到,附註標籤文件中存放的 SHA-1 值和任何一次提交記錄的 SHA-1 值都不匹配。

 

順帶一提

1、Git 指令的參數提示符有時爲 --,有時又使用 - 。什麼時候用哪種參數提示符呢?一般來說使用 -- 時後面跟一個單詞,而使用  - 的時候後面跟一個單詞的首字母。

當然也存在特殊情況,例如僅靠一個單詞無法描述該參數的具體含義,就需要使用兩個單詞。第一個單詞和第二個單詞之間使用 - 隔開:--abbrev-commit。

2、使用 Git 管理項目時,一般會用到下面四類分支:

  • master:版本發佈分支
  • develop:開發分支(頻繁迭代)
  • test:測試分支
  • bugfix:緊急 bug 修復分支

 

下一篇:Git詳解(二) 遠程操作

 

參考:

Git 官方文檔

阮一峯 Git 教程

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

Git 的 Merge 與 Fast-Forward

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章