簡體   English   中英

Git Checkout意外刪除了未跟蹤的文件

[英]Git checkout has deleted untracked files unintentionally

我遇到了Git的一個奇怪行為:我有一個存儲庫,其中包含一些在.gitignore文件中指定的未跟蹤文件和文件夾。

我所做的確切步驟:

  1. 存放了4個文件: git stash
  2. 簽出了我幾個月前的第一次提交: git checkout <hash of first commit>
  3. 環顧四周,不改變任何東西
  4. 回到我的工作分支進行git checkout <my working branch>
  5. 應用了存儲: git stash apply

然后我注意到一些(不是全部)未跟蹤的文件和文件夾消失了。 怎么可能?

附加信息:

  • 隱藏的文件與消失的文件無關,我注意到隱藏操作只是為了完整性

  • 我沒有執行其中一個命令git stash --include-untrackedgit stash save -u ,正如@Ashish Mathew猜測的那樣

  • 似乎只有文件和文件夾在第一次提交時就消失了,而.gitignore文件中還沒有,但是后來又添加了

隱藏的文件與消失的文件無關...

確實。

似乎只有文件和文件夾在第一次提交時就消失了,而.gitignore文件中還沒有,但是后來又添加了

這,再加上一件事,(幾乎可以肯定)是問題的根源。 幸運的是,您應該能夠取回這些文件,或者至少取回這些文件的某些版本 不幸的是,您必須將它們全部拼寫出來,然后與Git一起大驚小怪,您可能會得到錯誤的版本 請參閱底部的示例會話。

首先,請注意,只有未跟蹤的文件會被忽略

即使一個.gitignore文件說忽略它,也不會忽略未跟蹤(被跟蹤)的文件。 僅忽略未跟蹤的文件:已跟蹤文件,未跟蹤但不忽略文件或未跟蹤並忽略文件。

但是,等等: 未跟蹤的文件到底是什么?

未跟蹤的文件是不在索引中的文件

該定義是Git中為數不多的簡單明了的定義之一。 或者,確切地說是什么才是索引。 不幸的是,索引很難看到

我對索引的最好的一行描述是:*索引是構建下一個提交的地方。*

該索引,也稱為暫存區緩存 ,可跟蹤您的工作樹(即索引)。 工作樹是您工作的地方:它以正常的非Git格式存儲文件。 在Git信息庫中,永久且只讀存儲在提交中的文件具有特殊的,壓縮的,僅Git格式。 索引位於這兩個位置之間:它具有工作樹中所有可提交的文件, 都設置為commit 但是索引中的文件是可變的 (不同於內部提交中的文件),即使它們已經轉換為特殊的Git格式。

這意味着您的索引實際上為是非常罕見的。 大多數時候,它只與您當前的提交匹配。 那是因為您剛剛簽出該提交,這會將這些文件放入索引(僅Git形式,准備進行下一次提交)和工作樹(以常規普通文件形式,准備使用或編輯)。

如果修改文件F並運行git add F ,則git add 將替換以前在索引中(Git格式)的文件副本。 該指數是不是空的 -它有F它,一切沿着別人,它只是匹配當前提交 ,所以大多數Git命令不提F直到你改變F在工作樹。

因此,讓我們考慮:

簽出了我幾個月前的第一次提交: git checkout <hash of first commit>

這告訴Git: 從第一次提交開始就填充索引和工作樹。 假設我們尚未實際運行此命令,而只是考慮:這將做什么? 該提交中包含什么內容?

好了,該提交在創建時具有索引中的任何內容,無論您使用git add復制到索引中的是什么。 例如,其中包括文件abc.txt ,您后來決定必須將其取消跟蹤

要取消跟蹤,必須在某個時候從索引中刪除 abc.txt ,可能是:

git rm --cached abc.txt

(這將工作樹副本保留在原處,同時刪除了索引副本)。 git rm --cached ,您執行了git commit 從您運行git rm --cached到現在,該文件不在索引中。 它在工作樹中。 因此它是未跟蹤的

簽出任何提交將填充該提交的索引

現在,您已經告訴Git簽出您的第一個提交,但是...很好,該第一個提交中包含abc.txt Git需要將提交的abc.txt版本復制到索引工作樹中。

此時,如果工作樹中已經有一個abc.txt ,那么Git將檢查您是否要使用其他abc.txt對其進行破壞。 通常,Git會拒絕這樣做,告訴您先將其移開。 但是,如果工作樹中的abc.txt與提交中的abc.txt相匹配,那么可以安全地使用提交中的abc.txt填充索引。 畢竟,它與工作樹中的一個匹配。

因此,在這一點上,Git從該提交中提取所有文件,並將其提取到索引和工作樹中。 (有一些復雜,但試圖將要安全的,例外的總體思路:看結帳時,有對當前分支未提交的更改另一個分支 。)而且,哇哎,現在abc.txt 在索引中。 現在已被跟蹤!

因此,現在您環顧四周,看看您的舊提交,並決定:

git checkout <my working branch>

現在,Git必須將索引和工作樹的內容從其中包含abc.txt的第一個提交切換到<my working branch>的尖端提交。 該提交中沒有 abc.txt Git將從索引中刪除該文件...,並將其也從工作樹中刪除,因為它已被跟蹤

簽出完成后, 現在文件不在索引中。 嗯,它也不在工作樹( argh )中。 如果您將其放回工作樹中,則它現在是未跟蹤的。 但是,在哪里可以得到它?

答案正盯着我們: 是在第一次提交中。 當您運行git checkout <hash> ,Git會將文件復制到索引和工作樹中(除非它畢竟不必觸摸工作樹版本)。 當您運行git checkout <my working branch>取回文件時,Git 刪除了該文件,但是提交是只讀的,並且(通常)是永久的,因此該文件仍然以Git-only的形式存在於提交<hash>

訣竅是使它脫離 commit <hash> 而不將其放回索引中,從而使其以普通的非Git格式存在。 這幾天最簡單的方法是使用git show hash : path > path ,例如:

git show hash:abc.txt > abc.txt

(請注意,默認情況下git show並不應用行尾翻譯和污跡過濾器-在現代Git中,您應該可以使用--textconv--textconv )。

您將必須為Git刪除的每個文件執行此操作,這可能會很痛苦。


會話示例: .gitgnore通過破壞數據使Git正常運行

我為測試目的制作了一個小型存儲庫。 在該存儲庫中,我使用README和文件abc.txt進行了一次初始提交,其中包含一行讀取original

$ mkdir tt
$ cd tt
$ git init
Initialized empty Git repository in ...
$ echo original > abc.txt
$ echo for testing overwrite > README
$ git add README abc.txt
$ git commit -m initial
[master (root-commit) a721a23] initial
 2 files changed, 2 insertions(+)
 create mode 100644 README
 create mode 100644 abc.txt
$ git tag initial
$ git rm abc.txt
rm 'abc.txt'
$ git commit -m 'remove abc'
[master 20ba026] remove abc
 1 file changed, 1 deletion(-)
 delete mode 100644 abc.txt
$ touch unrelated.txt
$ echo abc.txt > .gitignore
$ git add .gitignore unrelated.txt 
$ git commit -m 'add unrelated file and ignore rule'
[master 067ea61] add unrelated file and ignore rule
 2 files changed, 1 insertion(+)
 create mode 100644 .gitignore
 create mode 100644 unrelated.txt

現在,我們有了一個包含三個提交的存儲庫:

$ git log --oneline --decorate
067ea61 add unrelated file and ignore rule
20ba026 remove abc
a721a23 (tag: initial) initial

讓我們在abc.txt放入一些珍貴的數據:

$ echo precious > abc.txt
$ git status
On branch master
nothing to commit, working tree clean
$ cat abc.txt   
precious

現在,讓我們檢查一下commit initial

$ git checkout initial
Note: checking out 'initial'.

You are in 'detached HEAD' state. [mass snip]

HEAD is now at a721a23... initial
$ cat abc.txt
original

糟糕,我們的寶貴數據已被破壞!

.gitignore指令賦予Git破壞文件的權限。 為了證明這一點,讓我們使abc.txt不被忽略(但也不被跟蹤):

$ cp /dev/null .gitignore
$ git add .gitignore
$ git commit -m 'do not ignore precious abc.txt'
[master 564c4fd] do not ignore precious abc.txt
 Date: Thu Feb 8 14:16:08 2018 -0800
 1 file changed, 1 deletion(-)
$ git log --oneline --decorate
564c4fd (HEAD -> master) do not ignore precious abc.txt
067ea61 add unrelated file and ignore rule
20ba026 remove abc
a721a23 (tag: initial) initial
$ echo precious > abc.txt
$ git status
On branch master
Untracked files:
  (use "git add <file>..." to include in what will be committed)

    abc.txt

nothing added to commit but untracked files present (use "git add" to track)

現在,如果我們要求切換到initial

$ git checkout initial
error: The following untracked working tree files would be overwritten by checkout:
    abc.txt
Please move or remove them before you switch branches.
Aborting

因此,忽略文件有一個令人討厭的副作用:文件變得容易崩潰。 我(與過去的其他人一起)一直在研究向Git教授“忽略並可能破壞”和“忽略但珍貴,不要破壞”之間的區別,並且無法簡單地解決它並放棄了努力。

(我認為Git在這一點上表現得更好,但是此示例表明,至少在Git 2.14.1(這是我在這組特定測試中使用的版本)上,它仍然很糟糕。)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM