現代化 Web 開發技術學習分享

0%

Git 版本控制系統 - 比對檔案版本差異與標示說明

前言

目前我們已經學會如何提交、切換、還原與重製版本了,現在只差比對了;身為版控的主流,Git 本身就已擁有強大的版本比對功能,在我們之前的分支合併衝突文章中,Git 之所以能夠告訴我們在何處發生了衝突,就是因為在內部運用了版本比對功能,事實上,我們也可以自己進行版本比對,這樣在合併分支時就不會造成所謂的衝突,除此之外,當你今天想要了解特定版本之間的差異性,也可以透過版本比對功能一目了然,省去了大部分時間。

筆記重點

  • 比對 HEAD 與 uncommitted 差異
  • 比對 HEAD 與指定版本差異
  • 比對不同版本間差異
  • 使用 difftool 比對檔案
  • Git 指令回顧

比對 HEAD 與 uncommitted 差異

讓我們先新增一個資料夾並提交數個 commit 紀錄:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
mkdir project

cd project

git init

touch index.html

git add .

git commit -m 'add index.html'

touch all.css

git add .

git commit -m 'add all.css'

... edit index.html

git commit -am 'edit index.html'

... edit all.css

git commit -am 'edit all.css'

這邊做一個指令的補充:

1
git commit -am 'message'

完整指令為:

1
git commit -a -m 'message'

-m 大家應該都很熟悉了才對,即提交訊息,-a 全名為 --all,可使狀態為 Changes not staged for commit 或 Changes to be committed 的檔案直接被提交至數據庫,省略了 git add 動作,但要注意 Untracked files 狀態的檔案是不受此命令作用的。

讓我們來看目前的日誌長什麼樣子:

查看目前 commit 紀錄-1

現在請隨意修改 index.htmlall.css 成以下狀態:

查看檔案狀態-1

在這邊我們只有將 all.css 的修改提交至索引區,而 index.html 的修改則是還停留在工作目錄,接著請執行以下命令:

1
git diff

這是比對檔案中最基本的指令,主要比對 HEAD (這指最新的 commit 紀錄) 與尚未進索引區 (unstaged) 全部檔案的差異,結果如下:

比對結果-1

這邊你會發現只有 index.html 被進行比對,因為 all.css 已經被提交至索引區了,不在比對的範圍,如果你想比對以進索引區 (staged) 全部檔案的差異,也就是 all.css 的差異,可以使用以下指令:

1
git diff --cached

這樣便可比對目前 HEAD (這指最新 commit 紀錄) 與以進索引區 (staged) 全部檔案的差異,此時結果如下:

比對結果-2

--cached 代表只比對 uncommitted 狀態下的已進索引區 (staged) 檔案,與 --staged 結果相同,後者只是前者的別名,兩者作用完全一樣。

那假設我們要同時比對尚未進索引區 (unstaged) 與已進索引區 (staged) 的檔案呢?可以使用以下指令:

1
git diff HEAD

這道指令代表自從上次 commit 以來做了什麼變化,這邊的 HEAD 也可以換成最新 commit 紀錄的 SHA-1 值,此時結果如下:

比對結果-3

以上就是 uncommitted 狀態如何進行比對的各種方式,讓我們簡單說明一下比對的標示:

  • diff --git:將哪兩個檔案進行比對
  • 10064:檔案屬性,表示 Regular non-executable file
    • 100664:Regular non-executable group-writeable file
    • 100755:Regular executable file
    • 120000:Symbolic link
    • 160000:Gitlink
  • @@ -1,3 +1,7 @@:前面為舊版本總行數,後面為新版本總行數

比對 HEAD 與指定版本差異

上面都是介紹 uncommitted 狀態與 HEAD (這指最新 commit 紀錄) 比對的差異,以確認在每次提交 commit 時做了些什麼事情,事實上,我們也可以指定比對的版本,輸入以下指令:

1
git diff HEAD~1

之前我們有提到 ^~ 相對路徑的表示方法,這邊我們使用 ~1 比較 HEAD 的前一個版本差異,此時結果如下:

比對結果-4

在添加指定版本的 git diff 指令中,預設比對的是 HEAD (這指最新 commit 紀錄),但你會發現它連同 uncommitted 的內容也進行了比對,我個人建議,如果你要指定版本進行比對,最好在無 uncommitted 狀態下進行比對,避免混亂,執行以下命令:

1
2
3
git add .

git commit -m 'edit index.html all.css'

因為我們又一次提交了 commit 紀錄,所以相對路徑須改為 ~2

1
git diff HEAD~2

此時結果為:

比對結果-5

這樣就可以確保當前比對的版本為 HEAD (指最新 commit 紀錄) 與 HEAD 的前一個版本。

比對不同版本間差異

事實上,剛剛的 git diff HEAD~2 等同於以下指令:

1
git diff HEAD^^ HEAD

我們可以比對指定兩個版本間的差異,前面的 HEAD^^ 代表較舊的版本,後面的 HEAD 代表較新的版本,千萬不要把兩者寫反了,這樣的結果也會是相反的,讓我們試試比較不同的版本:

比對結果-6

請注意,我們之前有提到 HEAD 代表的是我們當前所在的版本,你也可以使用 SHA-1 值作為比對的依據,任何能代表 commit 節點的稱謂都可以。

使用 difftool 比對檔案

到這邊你會發現 diff 都是在 Terminal 內顯示以進行比對,在較單純的情況下,是沒什麼問題,但只要檔案結構一變複雜,查看起來真的挺費勁的。

Git 自己也深知這點,所以提供對第三方 diff 工具的支持;在之前的 Vim 章節,我們就有嘗試將預設的 Vim 編輯器更改為 VSCode,在這邊我們也如法炮製,將 VScode 設置為我們的比對工具,以下為示範:

通過 git bash 配置 difftool (VSCode 示範):

1
2
3
git config --global diff.tool vscode

git config --global difftool.vscode.cmd 'code --wait --diff $LOCAL $REMOTE'

通過 .gitconfig 配置 difftool (VSCode 示範):

1
2
3
4
[diff]
tool = vscode
[difftool "vscode"]
cmd = code --wait --diff $LOCAL $REMOTE

此時即可使用 difftool 比對檔案:

1
git difftool -y HEAD^

這邊的 -y 代表同意啟動第三方比對工具,如果沒有設置,在每次開啟比對檔案時,都會詢問是否同意開啟,你也可以使用以下命令全局關閉對話提示:

1
git config --global difftool.prompt false

讓我們來看結果如何:

比對結果-7

比對結果-8

Git 指令回顧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 省略 git add 提交至本地數據庫 (限以追蹤或存在於索引區檔案)
git commit -am 'message'

# 省略 git add 提交至本地數據庫 (同上)
git commit -a -m 'message'

# 比對 HEAD 與尚未進索引區檔案差異
git diff

# 比對 HEAD 與已進索引區檔案差異
git diff --cached

# 比對 HEAD 與已進索引區檔案差異 (同上)
git diff --staged

# 比對 HEAD 與 uncommitted 檔案差異
git diff HEAD

# 比對 uncommitted 與指定版本差異 (當前為 uncommitted 狀態)
git diff HEAD~1

# 比對 HEAD 與指定版本差異 (當前為 nothing to commit 狀態)
git diff HEAD^

# 比對兩個不同版本間差異 (舊版本 - 新版本)
git diff HEAD^^ HEAD

# 將 VSCode 設為 difftool 默認開啟工具
git config --global diff.tool vscode
git config --global difftool.vscode.cmd 'code --wait --diff $LOCAL $REMOTE'

# 全局關閉 difftool 對話提示
git config --global difftool.prompt false