用另一个提交的状态替换整个应用程序状态

Replace whole state of application with state of another commit

我想做 cherry-pick/merge/rebase/checkout 的 "the hardest version",这意味着我的分支上的应用程序状态开始看起来与精心挑选的提交完全一样(但保留了我的历史记录分支)。事实上,我可以复制我的回购协议,删除我分支中的所有内容,然后将复制版本集中的全部内容复制到需要的提交。但是,这并不方便,我相信有一些更简单的方法。

我已经尝试过 git cherry-pick <hash> --strategy-option theirs,但这并不完美,因为它不会删除精选提交中不存在的文件,这导致我的情况变得一团糟。

那么,我该怎么做呢?

编辑:我澄清了我还需要保留我的历史记录,首先是不明显的地方。

$ git checkout --orphan new-branch-name
$ git cherry-pick <hash>

您需要使用 git reset --soft。首先,合并……策略或结果无关紧要。如果您有冲突,没关系...添加所有文件并保存(别担心...我们会在一秒钟内修改正确的内容)。

现在,签出您要从中获取内容的修订版。如果是分支,则使用--detach。现在,git reset --soft 到原始分支(我​​们之前进行合并的地方)。

现在完成技巧 运行 git commit --amend --no-edit,你就完成了。如果你喜欢这个结果,将原分支的分支指针移动git branch -f the-original-branch就大功告成了。

原始配方,不保存其他分店历史 你要做的是git reset --soft。检查您希望其他分支看起来像的修订版(或带有 --detach 的分支)(content-wise)。然后做git reset --soft the-other-branch。这会将分支 pointer 设置为此修订版或分支,工作树将与我们之前检查过的树一样,它们之间的所有差异都将在索引上。现在,当你提交时,你将得到一个单一的修订,其中分支看起来就像你第一次签出的原始版本....如果你喜欢最终结果,移动分支指针并签出它。

那根本不是 cherry-pick。不要用git cherry-pick来制作:用git commit来制作。这是一个非常简单的食谱1

$ git checkout <branch>                # get on the target branch
$ cd $(git rev-parse --show-toplevel)  # ensure you're at the top of the work-tree
$ git rm -r .                          # remove all tracked files from index and work-tree
$ git checkout <hash> -- .             # create every file anew from <hash>
$ git commit                           # make a new commit with all new info

如果要从提交 <hash> 复制提交消息等,请考虑将 -c <hash> 添加到 git commit 行。


1这不是最简单的,但应该可以理解。较简单的在初始 git checkout 之后使用管道命令,例如:

git read-tree -u <hash>
git commit

或:

git merge --ff-only $(git commit-tree -p HEAD <hash>^{tree} < /tmp/commit-msg)

(未经测试,对于第二个,您必须构建提交消息)。

请记住,Git 存储提交,每个提交都是所有源文件的完整快照,外加一些元数据。每个提交的元数据包括提交者的姓名和电子邮件地址; a date-and-time-stamp 表示提交的时间;一条日志消息,说明 为什么 提交;并且,对于 Git,提交的 父级 的哈希 ID 至关重要。

只要你有某个提交的哈希 ID,我们就说你指向 提交。如果一个提交具有另一个提交的哈希 ID,则具有哈希 ID 的提交指向另一个提交。

这意味着嵌入在每个提交中的这些哈希 ID 形成一个 backwards-looking 链。如果我们使用单个字母代表提交,或者按顺序编号 C1C2 等等,我们会得到:

A <-B <-C <-D ... <-G <-H

或:

C1 <-C2 <-C3 ... <-C7 <-C8

每个提交的实际名称当然是一些丑陋的大哈希 ID,但是使用像这样的字母或数字使我们作为人类更容易处理它们。无论如何,关键是如果我们以某种方式将 last 提交的哈希 ID 保存在链中,我们最终能够向后跟随链的其余部分,一次提交一次。

我们 Git 存储这些哈希 ID 的地方是 分支名称 。所以像master这样的分支名称只存储提交H的真实哈希ID,而H本身存储其父G的哈希ID,它存储的哈希ID它的父级 F,依此类推:

... <-F <-G <-H   <-- master

这些 backwards-looking 链接,从 HG 再到 F,加上每次提交时保存的快照以及关于提交者和原因的元数据, 存储库中的历史记录。要保留以 H 结尾的历史记录,您只需确保 next 提交在提交时将 H 作为其父项:

...--F--G--H--I   <-- master

通过进行新提交,Git 更改名称 master 以记住新提交 I 的哈希 ID,其父项为 H,其父项为(仍然)G,等等。

您的目标是使用与其他提交关联的 快照 进行提交 I,例如下面的 K

...--F--G--H   <-- master
   \
    J------K------L   <-- somebranch

Git 实际上是根据 index 中的内容构建新的提交,而不是源代码树中的内容。所以我们从git checkout master开始,让commitH成为当前commit,master成为当前分支,从commit[=30=的内容中填充index和work-tree ].

接下来,我们希望索引与提交 K 匹配——除 K 中的文件外没有其他文件——所以我们首先从索引中删除每个文件。为了理智(即,以便我们可以看到我们在做什么),我们让 Get 对 work-tree 执行相同的操作,它会自动执行。所以我们 运行 git rm -r . 在确保 . 指的是整个索引 / work-tree 对之后,通过确保我们在 work-tree 的顶部而不是某些 sub-directory / sub-folder.

现在我们的 work-tree 中只剩下未跟踪的文件。如果我们愿意,我们也可以删除它们,使用普通的 rmgit clean,尽管在大多数情况下它们是无害的。如果您想删除它们,请随时这样做。然后我们需要填写索引——work-tree 再次出现——来自提交 K,所以我们 运行 git checkout <hash-of-K> -- .-- . 很重要:它告诉 Git 不要 切换 提交,只需从此处命名的提交中提取所有内容。 我们的索引和 work-tree 现在匹配提交 K.

(如果提交 K 有我们在 H 中的 all 个文件,我们可以跳过 git rm 步骤。我们只需要git rm 删除 个在 H 中但不在 K 中的文件。)

最后,既然我们有索引(和 work-tree)匹配提交 K,我们可以安全地创建一个类似于 K 但不连接的新提交 K.

如果要合并,请使用git merge --no-commit

以上序列结果为:

...--F--G--H--I   <-- master
   \
    J-------K-----L   <-- somebranch

提交 I 保存的源快照 与提交 K 中的完全匹配。然而,通过读取 master 产生的历史,发现它指向 I,然后读取提交 I 并向后返回 HGF等等,从不提及 完全提交 K

您可能想要这样的历史记录:

...--F--G--H--I   <-- master
   \         /
    J-------K-----L   <-- somebranch

在这里,提交 I 返回到 both 提交 H K

制作这种提交变体 I 有点棘手,因为除了使用 git commit-tree 管道命令之外,唯一的方法是 制作 提交 I 就是使用 git merge.

这里,简单的方法是运行git merge -s ours --no-commit,如:

$ git merge -s ours --no-commit <hash>  # start the merge but don't finish it
$ git rm -r .                           # discard everything
$ git checkout <hash> -- .              # replace with their content
$ git commit                            # and now finish the merge

我们在这里使用-s ours是为了让事情进行得更快更顺利。我们正在构建的实际上是 git merge -s theirs 的结果,只是没有 git merge -s theirs-s ours 意味着 忽略他们的提交,只保留我们提交的内容 H. 然后我们将其丢弃并用他们提交的内容替换它 K,然后我们完成合并以获得指向 HK.

的合并提交 I

和以前一样, 管道命令技巧,使这更容易。它们只是 显而易见 除非您了解 Git 内部使用的低级存储格式。 "remove everything, then check out a different commit's contents" 方法很明显,也很容易记住。