[英]Git checkout has deleted untracked files unintentionally
我遇到了Git的一個奇怪行為:我有一個存儲庫,其中包含一些在.gitignore
文件中指定的未跟蹤文件和文件夾。
我所做的確切步驟:
git stash
git checkout <hash of first commit>
git checkout <my working branch>
git stash apply
然后我注意到一些(不是全部)未跟蹤的文件和文件夾消失了。 怎么可能?
附加信息:
隱藏的文件與消失的文件無關,我注意到隱藏操作只是為了完整性
我沒有執行其中一個命令git stash --include-untracked
或git 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.