在 Day 20 文章中,我們提到要讓一個檔案退出預存區,除了 git reset
之外,還有一種指令是 git rm --cached
,但當時只是快速帶過,在這篇文章中,我們將細究 git rm
指令,最後說明 git reset
跟 git rm --cached
的差別。
如果我們在工作目錄新增一個檔案,並要將其成為 commit 的一部分,要怎麼做呢?
我知道!就是 git add
與 git commit
嘛!前面文章一直有在操作,例如若想創建一個內文為 "Hello, world"
、檔名為 hello_world.txt
的檔案,並把它加進 commit,指令分別為:
echo "Hello, world" > hello_world.txt
git add hello_world.txt
git commit -m "Initial commit"
再用 git log
檢視,就發現做好一個 commit 了:
用同樣的步驟,我們再建立第二個檔案,並形成第二個 commit:
echo "Bye" > goodbye.txt
git add goodbye.txt
git commit -m "Add goodbye.txt"
剛剛是用 git log
檢視 commit:
前面的文章還有教我們透過觀察 .git/
資料夾:
.git/
├── ...
│
├── objects/
│ ├── 09/
│ │ ├── 170083fcc42fe6f8a3335a9bfa1f11e614638c # goodbye.txt的blob物件
│ │ └── e9df849399a112e444eef8eb8de187737a294f # 指向0917008...與a5c1966...的tree物件
│ ├── a5/ # hello_world.txt的blob物件
│ │ └── c19667710254f835085b99726e523457150e03
│ ├── bf/ # 指向09e9df8...的commit物件
│ │ └── 7347ba1ea1356cd881fd142e6467a3048cc7f9
│ ├── cf/ # 指向e59896e...的commit物件
│ │ └── 771be40f6f2aaf1393647bee7b422589a474d0
│ ├── e5/ # 指向a5c1966...的tree物件
│ │ └── 9896eb1f48ef9c75e675692e23966e5ba7cdfb
│ ├── info/
│ └── pack/
│
├── refs/
│ ├── heads/
│ └── tags/
│
├── ...
└── index # 包含100644 09170083fcc42fe6f8a3335a9bfa1f11e614638c 0 goodbye.txt
# 以及100644 a5c19667710254f835085b99726e523457150e03 0 hello_world.txt
對透過 .git/objects
裡的 git 物件做多次 git cat-file
還可得下列關係:
那如果要刪除檔案呢?
這流程非常有趣,跟上面幾乎是一樣的流程,差別在新增或更動檔案時,是使用 git add
把檔案放進預存區、讓 git 追蹤到變化;但在刪除檔案時,把檔案放進預存區的則是 git rm
指令。
以既有的例子為例,假如我們要刪除 hello_world.txt
,就可以輸入以下指令:
git rm hello_world.txt
根據 ls
指令檢視,此時 hello_world.txt
檔案已經從工作目錄消失,再根據 git status
確認,此變化有被 git 追蹤到:
這時預存區長什麼樣子呢?
100644 a5c19667710254f835085b99726e523457150e03 0 hello_world.txt
不見了!
一個 git rm
指令就同時改完工作目錄與預存區,比新增檔案少一個步驟,當然要刪除檔案也可以改用 rm
,但這樣就只會改到工作目錄,還要另外 git add
才能讓此變化被 git 追蹤到。
使用 rm
刪除檔案要重新做一次 git add
再來看看 .git/
資料夾:
.git/
├── ...
│
├── objects/ # 沒有變化
│ ├── 09/
│ │ ├── 170083fcc42fe6f8a3335a9bfa1f11e614638c
│ │ └── e9df849399a112e444eef8eb8de187737a294f
│ ├── a5/
│ │ └── c19667710254f835085b99726e523457150e03
│ ├── bf/
│ │ └── 7347ba1ea1356cd881fd142e6467a3048cc7f9
│ ├── cf/
│ │ └── 771be40f6f2aaf1393647bee7b422589a474d0
│ ├── e5/
│ │ └── 9896eb1f48ef9c75e675692e23966e5ba7cdfb
│ ├── info/
│ └── pack/
│
├── ...
└── index # 只剩下100644 09170083fcc42fe6f8a3335a9bfa1f11e614638c 0 goodbye.txt
除了預存區之外,看起來還沒有變化。
我們再輸入以下指令建立新的 commit:
git commit -m "Delete hello_world.txt"
此時的 .git/
資料夾如下:
.git/
├── ...
│
├── objects/
│ ├── 3b/ # 新增的commit物件
│ │ └── 0ff9b17ef7cff41cc5aa2807f2b082d07ee13e
│ ├── 09/
│ │ ├── 170083fcc42fe6f8a3335a9bfa1f11e614638c
│ │ └── e9df849399a112e444eef8eb8de187737a294f
│ ├── 33/ # 新增的tree物件,指向0917008...
│ │ └── 92d30461dc295ab8d321913b69489ac440085b
│ ├── a5/
│ │ └── c19667710254f835085b99726e523457150e03
│ ├── bf/
│ │ └── 7347ba1ea1356cd881fd142e6467a3048cc7f9
│ ├── cf/
│ │ └── 771be40f6f2aaf1393647bee7b422589a474d0
│ ├── e5/
│ │ └── 9896eb1f48ef9c75e675692e23966e5ba7cdfb
│ ├── info/
│ └── pack/
│
├── ...
└── index # 只剩下100644 09170083fcc42fe6f8a3335a9bfa1f11e614638c 0 goodbye.txt
這時 git 物件之間的關係如下:
舊有的 blob 物件與 tree 物件都沒有消失,且依然被舊的 commit 物件指著,但在新的 commit 物件指向之新的 tree 物件,就不再指向被刪除之檔案對應之 blob 物件。
剛剛的 git rm
會把檔案從工作目錄移除,但如果我希望移除預存區就好、工作目錄還是要有這份檔案,可以如何進行呢?
現在在一個新資料夾中輸入依序以下指令,回到有兩個 commit 的狀態:
git init
echo "Hello, world" > hello_world.txt
git add hello_world.txt
git commit -m "Initial commit"
echo "Bye" > goodbye.txt
git add goodbye.txt
git commit -m "Add goodbye.txt"
這時有兩個 commit,且工作目錄包含 hello_world.txt
與 goodbye.txt
兩個檔案、預存區包含兩檔案形成的 blob 物件:
.git/
資料夾結構如下:
.git/
├── ...
│
├── objects/
│ ├── 09/
│ │ ├── 170083fcc42fe6f8a3335a9bfa1f11e614638c # goodbye.txt的blob物件
│ │ └── e9df849399a112e444eef8eb8de187737a294f # 指向0917008...與a5c1966...的tree物件
│ ├── 74/ # 第一個commit物件
│ │ └── 85b5bd411fe0849011205679b1d8732fac9ea3
│ ├── a5/ # hello_world.txt的blob物件
│ │ └── c19667710254f835085b99726e523457150e03
│ ├── c4/ # 第二個commit物件
│ │ └── 6981bd89ebf017dbc5ee0fbbb493ae7ab9fede
│ ├── e5/ # 指向a5c1966...的tree物件
│ │ └── 9896eb1f48ef9c75e675692e23966e5ba7cdfb
│ ├── info/
│ └── pack/
│
├── ...
└── index # 目前仍有100644 09170083fcc42fe6f8a3335a9bfa1f11e614638c 0 goodbye.txt
# 以及100644 a5c19667710254f835085b99726e523457150e03 0 hello_world.txt
圖示如下:
這時如果下 git rm --cached
把 hello_world.txt
移出預存區:
從 git status
還可以看出,git 已經偵測到 hello_world.txt
被移出預存區(綠色文字)、而現在 hello_world.txt
已不再被追蹤(紅色文字)。
如果這時候下 git commit
會發生什麼事呢?
git commit -m "Stop tracking hello_world.txt"
這時觀察終端機發現:
新的 commit 被建立了,根據 git status
,hello_world.txt
依舊不會被 git 追蹤。
而此時 .git/
資料夾長這樣:
.git/
├── ...
│
├── objects/
│ ├── 09/
│ │ ├── 170083fcc42fe6f8a3335a9bfa1f11e614638c
│ │ └── e9df849399a112e444eef8eb8de187737a294f
│ ├── 33/ # 新的tree物件,指向0917008...
│ │ └── 92d30461dc295ab8d321913b69489ac440085b
│ ├── 74/
│ │ └── 85b5bd411fe0849011205679b1d8732fac9ea3
│ ├── a5/
│ │ └── c19667710254f835085b99726e523457150e03
│ ├── c4/
│ │ └── 6981bd89ebf017dbc5ee0fbbb493ae7ab9fede
│ ├── ca/ # 新的commit物件
│ │ └── df0e8f04fc35a1c3468f85bc59e77ff34906f3
│ ├── e5/
│ │ └── 9896eb1f48ef9c75e675692e23966e5ba7cdfb
│ ├── info/
│ └── pack/
│
├── ...
└── index # 剩下100644 09170083fcc42fe6f8a3335a9bfa1f11e614638c 0 goodbye.txt
圖示如下,其實跟剛才 git rm
一樣,舊的 commit 結構依然保持:
git rm
會把檔案從「工作目錄」跟「預存區」都刪除,但只從下一次 commit 起有效。git rm --cached <filename>
只會把檔案從「預存區」刪除,工作目錄中的檔案仍然保留,但一樣從下一次 commit 起有效。在 Day 20 文章中,我們說 git reset
跟 git rm --cached <filename>
都可以把檔案退出預存區,但後者還會進一步告訴 git 說:之後不要再追蹤指定檔案 <filename>
。