0

我想做cherry-pick/merge/rebase/checkout的“最难版本”,这意味着我的分支上的应用程序状态开始看起来与cherry-picked提交中的完全一样(但保留了我的分支的历史记录)。事实上,我可以复制我的 repo,删除我的分支中的所有内容,然后将整个内容从复制的版本集中复制到所需的提交。但是,这并不方便,我相信有一些更简单的方法。

我已经尝试过git cherry-pick <hash> --strategy-option theirs,但这并不完美,因为它不会删除樱桃挑选的提交中不存在的文件,这在我的情况下会导致大混乱。

那么,我该怎么做呢?

编辑:我澄清说我还需要保留我的历史,首先不明显。

4

3 回答 3

3

这根本不是樱桃采摘。不要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

如果您想从 commit 复制提交消息等<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 存储提交,每个提交都是所有源文件的完整快照,以及一些元数据。每个提交的元数据包括提交者的姓名和电子邮件地址;提交时的日期和时间戳;一条日志消息,说明提交的原因;并且,对于 Git 来说至关重要的是提交父级的哈希 ID。

每当您拥有某个提交的哈希 ID 时,我们就说您指向该提交。如果一个提交具有另一个提交的哈希 ID,则具有哈希 ID 的提交指向另一个提交。

这意味着嵌入在每个提交中的这些哈希 ID 形成了一个向后看的链。如果我们使用单个字母来代表提交,或者按顺序编号它们,我们得到C1C2

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

或者:

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

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

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

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

这些向后看的链接,从HGF,加上每次提交保存的快照以及关于谁提交和为什么提交的元数据,都是你的存储库中的历史。要保留以 结尾的历史记录H,您只需要确保下一次提交,当您提交时,H它的父项是:

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

通过进行新提交,Git 更改名称master以记住新提交的哈希 ID I,其父级为H,其父级为 (still) G,依此类推。

您的目标是I使用与其他提交相关联的快照K进行提交,如下所示:

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

Git 实际上是根据index中的内容而不是源代码树中的内容构建新提交。因此,我们首先git checkout master将 commitH设置为当前提交和master当前分支,它从 commit 的内容中填充索引和工作树H

接下来,我们希望索引与提交匹配K——除了其中的文件之外没有其他文件——所以K我们首先从索引中删除每个文件。为了理智(即,这样我们可以看到我们在做什么),我们让 Get 对工作树做同样的事情,它会自动完成。因此,我们git rm -r .在确保它.引用整个索引/工作树对之后运行,确保我们位于工作树的顶部而不是某个子目录/子文件夹中。

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

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

最后,现在我们有了与 commit 匹配的索引(和工作树)K,我们可以安全地进行新的提交,它类似于K但不连接 K.

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

上述序列导致:

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

commit中保存的源快照与 commit 中的I完全匹配K。但是,通过阅读master,发现它指向I,然后将 commitI和 on 向后读取到HandG等等所产生的历史F,根本没有提到commit K

相反,您可能想要一个如下所示的历史记录:

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

在这里, commitI回溯到commits.H K

进行这种提交变体I有点棘手,因为除了使用管道命令之外,进行提交git commit-tree的唯一方法是使用.Igit 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,然后我们完成合并以获得一个I指向H和的合并提交K

和以前一样,有一些管道命令技巧可以让这变得更加容易。除非您了解 Git 内部使用的低级存储格式,否则它们并不明显。“删除所有内容,然后检查不同的提交内容”的方法非常明显,并且很容易记住。

于 2019-05-19T21:57:22.617 回答
2

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

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

现在完成特技运行git commit --amend --no-edit,你就完成了。如果你喜欢结果,移动原始分支的分支指针,git branch -f the-original-branch你就完成了。

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

于 2019-05-19T15:26:35.737 回答
0
$ git checkout --orphan new-branch-name
$ git cherry-pick <hash>
于 2019-05-19T14:13:19.953 回答