回滚提交单个文件git reset


Rollback commit of a single file git reset

我在终端上使用Git。我在一个文件夹中有几个HTML文件。我使用git init创建了一个存储库。

我使用以下方法将index.html添加到临时区域:

1
git add index.html

现在,我要从临时区域中删除index.html。我尝试了git reset--head index.html等,但似乎没有任何东西可以将文件从临时区域中取出。

我只想从临时区域中删除index.html文件。

这是运行git reset head后的输出。如您所见,index.html丢失了。

1
2
3
4
5
6
7
8
9
10
On branch master
Untracked files:
  (use"git add <file>..." to include in what will be committed)

    landscape.css
    max-640px.css
    min-640px.css
    portrait.css
    result.html
    styles.css

这里是git日志输出:

1
2
3
4
$ git log
commit d70e697ed08fbe6709ba3f13d202bade793e7b1a
Author: johnson
Date:   Tue May 30 19:36:12 2017 -0500


在我开始之前,让我们先说明第一次提交是特殊的。原因是,在进行第一次提交之前,没有当前提交。出于Git自身的内部目的,Git有时会假装当前提交确实存在,而且它确实是空的:它根本没有文件。(事实上,在每个存储库中都有一个特殊的半秘密Git,仅用于此目的。)好的。

临时区域1位于当前提交(head)和工作树之间:好的。

1
2
3
4
5
current commit:        staging area:         work-tree:
  file1                  file1                 file1
  file2                  file2                 file2
   ...                    ...                   ...
  fileN                  fileN                 fileN

临时区域(总是)保存将进入下一次提交的所有文件。如果您有一个当前提交(您做了/做了);您将其显示在上面,那么临时区域将开始充满该提交中的所有文件。(工作树也是如此:正如您所见,每个文件都有三份副本!通常,其中两个当前提交和临时区域副本被秘密压缩和/或秘密共享。Git可以这样做,因为这两个副本都在Git的控制之下。只有工作树副本才具有您计算机上任何文件的正常格式;Git不能将这些文件保持为花哨、压缩和消除重复的格式。)好的。

换句话说,只要您有一个当前的(HEAD@提交),就可以很好地保证临时区域装载了大量的文件。只是存储在那里的文件与当前提交中存储的文件相同!因此,比较HEAD与阶段的git status没有提到它们。好的。

(注:git status还将临时区域与工作树进行了比较。下面我们将看到一个示例,其中git status --short打印两个问号来表示未跟踪的文件。这是因为文件将在工作树中,但不在当前提交或临时区域中。)好的。

运行git reset --mixed commit path时(默认为--mixed时),2提示git:请将path的分阶段副本与commit副本匹配。因为这是git reset HEAD index.html,这意味着:如果文件不在HEAD提交中,将其从临时区域中删除;如果在HEAD提交中,将其从HEAD提交中复制,返回临时区域。好的。

因为index.html是在HEAD的承诺中,所以你将永远被困在承诺中。但是你不能把它困在集结区:你可以用git rm index.html把它移走。当然,git rm index.html也会从你的工作树中删除它!幸运的是,有一个选项可以告诉git rm:将它从临时区域移除,但不要触摸工作树。拼写为--cached:好的。

1
git rm --cached index.html

现在集结区没有index.html的副本:好的。

1
2
3
4
current commit:        staging area:         work-tree:
  foo.css                foo.css               foo.css
  index.html              ---                  index.html
  README                 README                README

这不是您使用git revert所做的,它会做出一个新的承诺来撤销先前的承诺,但这里还有一个重要的点,如果您这样做了,这将适用。git status命令比较HEAD提交到临时区域。文件index.htmlHEAD提交中(或者在还原之前可能是)。git rm --cached后,不在集结区。所以文件变成了deleted。好的。

1仅因为它是Git,临时区域还有两个名称:它也被称为缓存,最常见的是索引。当然,名称索引与index.html非常相似。:-)因此,对于这个答案的其余部分,我试图坚持短语分段区域。好的。

2这是--mixed的原因,因为git reset--soft的程度较低,不接触集结区。还有一个更高的等级,--hard,写在登台区和工作树上。好的。当你恢复时,你做了一个新的承诺

如果您使用git revert来"撤销"一个提交,那么只会产生一个新的提交,它与恢复的提交相反。您恢复的提交是有史以来的第一次提交,根据定义,第一次提交包括添加该提交中的所有文件。所以恢复第一次提交意味着:删除所有文件。好的。

Git的提交实际上并不存储更改,而是存储快照。既然您有两个提交,那么HEAD提交(或当前)是第二个提交,与第一个提交相比,它删除了所有文件:好的。

1
2
3
4
5
6
7
8
9
10
11
12
$ mkdir tt
$ cd tt
$ git init
Initialized empty Git repository in ...
$ echo test revert of first commit > README
$ echo la la la > file
$ git add README file
$ git commit -m initial
[master (root-commit) 2cff9aa] initial
 2 files changed, 2 insertions(+)
 create mode 100644 README
 create mode 100644 file

到目前为止,还不错:我们有一个初始提交,它(与初始提交之前的空白相比)添加了所有文件。好的。

1
2
3
4
5
6
$ git revert HEAD
[editor session, snipped]
[master 92f4eea] Revert"initial"
 2 files changed, 2 deletions(-)
 delete mode 100644 README
 delete mode 100644 file

还原将撤消初始提交的效果,因此它将删除所有文件。Git将"上一次提交"与"当前提交"进行比较,发现删除了READMEfile。好的。

1
2
$ ls
$

工作树现在是空的。所有文件都不见了!这是因为我们已经恢复了它们。好的。

不过别担心!所有的文件都还在那里!他们在第一次交涉中。我们只需要把它们拿回来。现在我们来做:好的。

1
2
3
$ git log --all --decorate --oneline --graph
* 92f4eea (HEAD -> master) Revert"initial"
* 2cff9aa initial

(只有--oneline选项在这里起到了很大作用,但其他选项通常是个好主意。我其实把这个化名为git。记住这一点:当使用git log时,从一只狗那里得到帮助:所有的装饰都是单线图。)好的。

我们需要从第一次提交(2cff9aa中)返回所有文件,因此:好的。

1
2
3
$ git checkout 2cff9aa -- .
$ ls
README  file

有一个好处是:两个文件现在都在临时区域,因此将进入下一个提交:好的。

1
2
3
4
5
6
7
8
$ git status
git status
On branch master
Changes to be committed:
  (use"git reset HEAD <file>..." to unstage)

    new file:   README
    new file:   file

(发生这种情况是因为git checkout在将文件从临时区域复制到工作树之前,将文件从指定的提交复制到临时区域。)好的。

我们想去掉其中一个(在本例中是file)。现在我们可以使用git rm --cachedgit reset HEAD file。当前提交名为HEAD@92f4eea的提交中没有文件file。实际上,当前提交中没有文件!这是一个真正空的提交:签出它会使您的工作树为空。好的。

现在让我们重置(或rm --cached:好的。

1
2
3
4
5
6
$ git reset file
$ git status --short
A  README
?? file
$ ls
README  file

A状态表示"已添加",两个问号表示文件file未被跟踪(我们可以在较长的默认git status输出中看到这一点)。现在我们可以承诺:好的。

1
2
3
4
5
6
7
8
$ git commit -m 'this should have been the first commit'
[master 0945e1d] this should have been the first commit
 1 file changed, 1 insertion(+)
 create mode 100644 README
$ git log --oneline
0945e1d (HEAD -> master) this should have been the first commit
92f4eea Revert"initial"
2cff9aa initial

现在这里有一个问题,也许最好用插图来说明。注意,我们的文件file现在安全地保存在工作树中。让我们再次检查初始提交,看看其中包含什么:好的。

1
2
3
4
5
$ git checkout 2cff9aa
error: The following untracked working tree files would be overwritten by checkout:
    file
Please move or remove them before you switch branches.
Aborting

假设我们确实设法签出了该提交,例如,使用--force:好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ git checkout --force 2cff9aa
Note: checking out '2cff9aa'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b <new-branch-name>

HEAD is now at 2cff9aa... initial
$ ls
README  file

现在让我们回到master的tip commit,回到master分支,退出这个"分离头"模式。好的。

1
2
3
4
5
$ git checkout master
Previous HEAD position was 2cff9aa... initial
Switched to branch 'master'
$ ls
README

等等,我们叫file的文件去哪儿了?好的。

这就是我们创建的陷阱:有一个commit,2cff9aa,其中包含commit文件。如果我们能够以某种方式使当前的提交成为现实,那么在临时区域和工作树中也会有该版本的file。然后,当我们离开承诺时,比如说,回到master--git将从临时区域和工作树中删除文件!好的。

这是不可能的。如果该文件处于提交状态,则签出时它将进入临时区域(和工作树)。然后,当您将该提交移到没有该文件的提交时,它将被删除(从这两个相同的位置)。好的。

正如我们上面看到的,Git会在您将要进入该提交时警告您。这会给您一个保存数据的机会。您需要知道必须这样做(保存数据),或者避免签出特定的提交。好的。

唯一的另一个选择是重写您的存储库以完全消除提交。在大多数情况下,大多数存储库中都有大量的历史,也许还有许多克隆,等等,这些都不值得。在一个只有一次提交的新存储库中,这可能是值得的!好的。好啊。