git reset HEAD filename 有什么作用?

what does git reset HEAD filename do?

我在分支 B 上,我正在尝试将分支 A 合并到分支 B,但我想合并除一个文件之外的所有更改。经过一番搜索,我找到了这个解决方案:

git merge --no-commit <merge-branch>
git reset HEAD myfile.txt
git checkout -- myfile.txt
git commit -m "merged <merge-branch>"

我对第二行感到很困惑:git reset HEAD myfile.txt;这条线有什么作用?它对合并除一个文件以外的所有文件的目标有何帮助?

它将它重置为当前分支的头部 (HEAD)。

您有一个文件希望不受合并影响。但该文件实际上可能会受到合并的影响。因此,您执行了合并,但没有声明完成就停止了。

git merge --no-commit <merge-branch>

现在撤消合并对该文件的影响(如果有的话)。

git reset HEAD myfile.txt
git checkout -- myfile.txt

现在您声明合并完成。

git commit -m "merged <merge-branch>"

使用 git reset 时要记住的是 git 跟踪两组文件:

  • 您的"working tree",您直接编辑的检出文件。
  • "index" 或 "staging area" 将在您下次 运行 git commit 时提交的文件,您通常使用 git addgit rm.

它还会跟踪您当前签出的提交以及哪个分支。

git reset 有多种模式和参数,which you can look up in the documentation.

当使用一个或多个路径以及分支或提交引用(任何 "tree-ish",如 git 手册所述)指定时,git reset 设置这些文件 在索引/暂存区到他们在提交时的状态,但不触及工作副本或分支指针

最后,HEAD 指的是您当前签出的任何提交。

所以在这种情况下 git reset HEAD myfile.txt 将索引更改为 "when I next commit, please make the content of myfile.txt the same as in the currently checked out commit"。换句话说,您不想提交对该文件的任何更改。

下一行 (git checkout -- myfile.txt) 对 工作树 做同样的事情。如果您 运行 和 git status 之间,您会看到对 myfile.txt 的所有更改都列为 "unstaged".

and 都是正确的(我已经赞成它们)但是除非你已经阅读了 Git 的索引/staging-area 的功能,其中的一些部分有些晦涩难懂。事实上,如果不深入了解索引的一些细节,就无法正确解释合并的过程——而 git reset 的主要工作是操纵索引。所以: 索引,为什么它有三个名字?首先,简要了解一下 commits.

很有用

Git 提交是每个源文件的冻结快照,加上一些元数据

当您进行 Git 提交时,您真正做的是永久冻结 所有 文件的副本(好吧,所有你提交的文件,但这是一种同义反复)。该提交还存储了您的姓名和电子邮件地址,以及一个 date-and-time 标记——实际上,每个标记 两个 ——以及我们将在这里忽略的其他一些有用的东西,为了专注于每个文件的这些冻结副本。

显然,如果 Git 每次都为每个文件创建一个新副本,您的 Git 存储库会很快变得很大。所以 Git 不会那样做。每个冻结文件首先被压缩,变成read-only、Git-only形式。以这种方式存储的数据永远不会改变,事实上不能改变。它只是存储在一个大数据库中(Git 的 object 数据库,我们在这里可以忽略它)。这意味着一旦它们被提交,您的文件将一直以这种形式保存,如果您需要它们,这很好。但是,如果文件的冻结、压缩、read-only、Git-only 数据与(或任何)早期提交中的 相同,这也意味着Git 可以 re-use 之前保存的文件,而不是保存新的副本。由于大多数提交大多不会更改大多数文件,因此这很容易节省大量 space.

Frozen 对于完成任务来说很好,但对于完成工作毫无用处

这种冻结的 read-only、Git-only、压缩形式(我喜欢称之为 freeze-dried)有一些缺点。 freeze-dried 文件保存 space 并且可以被许多提交使用,但它们 作为存档有用。它们必须扩展——再水化——成为一种有用的形式,以便你对它们进行任何工作,或者改变它们。

因此,Git也为您提供了一个工作区,Git调用work-tree工作区treeworking tree 或该名称的某些变体。 Git 可以到此为止,freeze-dried Git-only 提交的文件和 work-tree 有用的文件。其他版本控制系统到此为止。但是 Git 没有。 Git 继续为上次提交 out 后的每个 freeze-dried 文件保留一个副本——实际上是一个引用。因此,不是每个文件的 两个 副本——HEAD 提交中的冻结副本,以及 work-tree 中的工作文件——Git 具有 三个份每个文件。第三个副本位于 HEAD 副本和 work-tree 副本之间,是索引(主要)的内容。

索引有几个作用

索引,Git 也称为 暂存区——在很多方面这是一个更好的名字,但它遗漏了一些极端情况——保存了一份每个将进入 next 提交的文件。此副本采用 freeze-dried 格式,即已准备好进入新提交。这里的一个技巧是,展开冻结文件的过程比 re-freezing work-tree 文件的过程更快。因此,当您第一次 git checkout 一些提交时,Git 会用该提交的 来自 的每个文件填充索引,因为它出现在该提交中。这意味着他们也都准备好进入 next 提交。 Git 然后将这些冻结的副本再水化到 work-tree 中,这样您就可以看到并处理它们 on/with。

编辑 work-tree 副本后,git add 获取 work-tree 副本和 re-freeze-dries 副本,并将该版本填充到索引中(间接但不会'在这里真的很重要)。所以现在索引已更新——更新后的文件 staged 被复制到索引中,替换旧版本。同样,索引已为下一次提交做好准备。 Git 将 自动 re-use 所有未更改的 freeze-dried 文件!它真的很聪明,除非索引妨碍了你——例如,有时在合并过程中会发生这种情况。

上面的描述有点谎言。有关详细信息,请参阅 Checkout another branch when there are uncommitted changes on the current branch。但它是考虑 Git 的合理起始模型。这也导致了索引的第三个名称:Git 有时称它为 cache,因为它缓存了有关您的 work-tree 的信息。不过,这两个通用名称仍然是 staging area——这是有道理的,因为 git add copi把东西放进去,然后它就像一个舞台场景,你可以通过提交来拍照和存档——index.

当您 运行 git merge 时,该索引的作用将大大扩展。当您有合并冲突时,您只会看到。如果您没有任何合并冲突,git merge 会扩展索引,完成所有工作,然后将其折叠起来。为了避免这个答案变得更长,让我们假设情况确实如此。 :-) 在那种特殊情况下——一切顺利——索引现在包含每个 merged 文件,除了 freeze-dried 之外——匹配合并的 work-tree 也复制,就好像您(或 Git)对所有合并文件都有 运行 git add

通常,如果 git merge 能够像这样独立完成所有事情,git merge 会继续进行 合并提交 。合并提交几乎与常规 (non-merge) 提交完全相同——它像往常一样有一个快照,像往常一样从索引中创建。唯一真正的区别是在它的元数据中,合并提交记录 both 当前 HEAD 提交 and 另一个提交作为它的 (二)parent秒。普通提交只记录当前提交作为他们的(一个)parent.

由于您使用了 --no-commitgit merge 进行此合并提交之前停止了 ,但保留设置以便 git commitgit merge --continue 将进行合并提交。 此时,请记住,索引包含所有合并的文件。这就是 git reset 的用武之地。

git reset

git reset 所做的是......好吧,它可能真的很复杂,因为 git reset,就像许多其他 Git 命令一样,将太多不同的东西塞进一个 user-facing命令。但是 在这种情况下 git reset 所做的是 git add 的一种对应。让我们绘制 HEAD -- index -- work-tree 设置如下:

  HEAD         index      work-tree
---------    ---------    ---------
README.md    README.md    README.md
Makefile     Makefile     Makefile
myfile.txt   myfile.txt   myfile.txt
...          ...          ...

这些不同文件的每个副本的内容可能不同。也就是说,HEAD:README.md 将永远冻结在当前提交的 README.md 中。它采用 freeze-dried 格式,并且 无法更改 README.md 的索引副本——例如,你可以用 git show :README.md 查看——也是 freeze-dried 格式,但 你可以随时更改它。 当然,普通 READMD.md 文件是您 work-tree 中的普通普通文件,您可以用它做任何您想做的事情。

git reset HEAD myfile.txt 所做的是告诉 Git:将 freeze-dried HEAD:myfile.txt 复制到索引 :myfile.txt. 将此与 git add myfile.txt 进行比较,它告诉 Git: 复制 work-tree,将 myfile.txt 重新水化为 :myfile.txt,freeze-drying 它过程中.

git checkout

更新索引副本后,您可能想查看更新的内容。你可以使用 git show :myfile.txt 在你的屏幕上看到它(或者在 window 或其他什么)。但这就是 git checkout -- myfile.txt 的用武之地。

就像git reset一样,git checkout可以做的事情太多了(in Git 2.23 git checkout is to be replaced with two separate Git commands, which each do fewer things)。 git checkout 的这种特殊形式告诉 Git: 将文件 myfile.txt 从索引(从 :myfile.txt 复制到 work-tree,再水化它过程中。

你可以通过一个命令缩短整个事情,因为git checkout可以将文件从commit复制到你的work-tree:

git checkout HEAD myfile.txt

会完成这项工作。这种git checkout先把HEAD:myfile.txt拷贝到:myfile.txt——在这个操作中保持freeze-dried格式——然后把索引拷贝拷贝到work-tree拷贝.

总结

索引或临时区域位于当前提交和work-tree之间。当您在 Git 存储库上工作时,使用 work-tree 中的文件,Git 并不关心您对 work-tree 做了什么。 Git 关心你对 index 做什么,因为 Git 让你的 next 提交——无论是普通的使用一个 parent 提交,或使用两个合并 - 从您当时在索引中拥有的任何内容 运行 git commit.

索引的存在使您能够更改将进入下一次提交的内容。这包括您使用 --no-commit 故意停止的合并的情况。 (索引也是使任何给定文件 trackeduntracked 的原因,并且 - 因为它在合并期间扮演扩展角色 - 处理未合并 文件,其中 Git 无法自行合并。有时试图忽略索引很诱人,因为您无法 查看 它直接(好吧,你可以用 git show 一点点,用 git ls-files --stage 更多)。但它在 Git 的大部分内容中起着巨大而核心的作用忽略它是不明智的操作。