git 命令使一个分支像另一个分支

Posted

技术标签:

【中文标题】git 命令使一个分支像另一个分支【英文标题】:git command for making one branch like another 【发布时间】:2011-06-22 03:49:17 【问题描述】:

我正在尝试获取一个带有更改的分支,并将其恢复为与它分歧的上游相同。这些更改都是本地的并且已被推送到 github,因此 git resetgit rebase 都不是真正可行的,因为它们会更改历史记录,这对于已经被推送的分支来说是一件坏事。

我也尝试了git merge 的各种策略,但它们都没有撤消本地更改,即如果我添加了一个文件,合并可能会使其他文件重新排列,但我仍然拥有那个文件上游没有。

我可以从上游创建一个新分支,但我真的很想要一个合并,在修订历史方面应用所有更改以获取我的分支并再次使其与上游相同,这样我就可以安全地在不破坏历史的情况下推动这一变化。有没有这样一个命令或一系列命令?

【问题讨论】:

如果您不关心保留更改,为什么不直接删除并重新创建分支? “项目的历史”不必是神圣的。 Git 是一个帮助开发者交流的工具。如果这些更改没有帮助,请扔掉它们。 +100 @wnoise - 特别是如果更改已合并。 我关心保存历史,因为它是为合作而发布的,我可能想回到它。如果您只保留最新版本,为什么还要使用修订控制? 这是一个主观的论点,但对我来说,VCS 的目的不是记录项目历史的每一个细节,而只是记录对内容(提交)的更改,以允许您可以根据这些提交(分支、合并、变基、重置等)操作树/历史记录,并允许您根据历史记录(差异、日志、责备等)查看报告。 git 是“愚蠢的内容跟踪器”——我将其视为管理源代码的工具,而不是时间机器。 正如你所说,这是主观的。我关心的是能够审查放弃的方法,并能够看到过去某个时间做出了什么决定。而且我关心我放弃某些东西的决定不会破坏其他人可能指向的合并点。 【参考方案1】:

更改到远程上游分支并执行git merge,合并策略设置为ours

git checkout origin/master
git merge dev --strategy=ours
git commit ...
git push

所有历史记录仍然存在,但您将有一个额外的合并提交。这里重要的是从你想要的版本开始,将ours与github实际所在的分支合并。

【讨论】:

我需要相反的。这将把我的分支整合到上游,但保持上游头部不变。但我需要将上游整合到我的分支中,让我的头看起来像上游。基本上像--strategy=theirs 这样的东西,除了最接近的--strategy=recursive -X=theirs 并没有做到这一点。 --strategy=theirs--strategy=ours 正好相反。您从另一端开始(所以从 github 开始并以另一种方式合并)。 没有--strategy=theirs,这就是问题所在。最接近的是--strategy=recursive -X theirs,这并不完全相反,因为如果它们不冲突,它不会删除无关的本地更改。 这两个是相反的:git checkout dev; git merge origin/master --strategy=oursgit checkout origin/master; git merge dev --strategy=ours @Arne:请参阅我对 VonC 答案的评论。 ours 策略的存在使得执行theirs 策略完全成为可能。【参考方案2】:

您可以使用自定义合并驱动程序“keepTheirs”将您的上游分支合并到您的dev 分支: 请参阅““git merge -s theirs” needed — but I know it doesn't exist”。 在您的情况下,只需要一个 .gitattributes 和一个 keepTheirs 脚本,例如:

mv -f $3 $2
exit 0

git merge --strategy=theirs模拟#1

显示为合并,上游为第一个父级。

Jefromi 提到(在 cmets 中)merge -s ours,通过合并您在上游(或从上游开始的临时分支)上的工作,然后将您的分支快速转发到该合并的结果:

git checkout -b tmp origin/upstream
git merge -s ours downstream         # ignoring all changes from downstream
git checkout downstream
git merge tmp                        # fast-forward to tmp HEAD
git branch -D tmp                    # deleting tmp

这样做的好处是把上游的祖先记录为第一个父母,这样合并的意思是“吸收这个过时的主题分支”而不是“销毁这个主题分支并用上游代替”。

(2011 年编辑):

此工作流程已在此blog post by the OP 中报告:

为什么我又想要这个?

只要我的 repo 与公共版本无关,这一切都很好,但现在我希望能够与其他团队成员和外部贡献者在 WIP 上进行协作,我想确保我的公共分支对于其他人来说是可靠的分支和拉取,即不再对我推送到远程备份的内容进行变基和重置,因为它现在在 GitHub 和公共上。

所以这让我知道我应该如何进行。 99% 的时间我的副本会进入上游 master,所以我想大部分时间都在我的 master 工作并推送到上游。 但是每隔一段时间,我在wip 中的内容会因进入上游的内容而失效,我将放弃我的wip 的某些部分。那时我想把我的主人带回来与上游同步,但不会破坏我公开推送的 master 上的任何提交点。 IE。我想要与上游合并,最终得到使我的副本与上游相同的变更集。 这就是git merge --strategy=theirs 应该做的。


git merge --strategy=theirs模拟#2

显示为合并,我们的作为第一个父级。

(由jcwenger提议)

git checkout -b tmp upstream
git merge -s ours thebranch         # ignoring all changes from downstream
git checkout downstream
git merge --squash tmp               # apply changes from tmp but not as merge.
git rev-parse upstream > .git/MERGE_HEAD #record upstream 2nd merge head
git commit -m "rebaselined thebranch from upstream" # make the commit.
git branch -D tmp                    # deleting tmp

git merge --strategy=theirs模拟#3

这个blog post mentions:

git merge -s ours ref-to-be-merged
git diff --binary ref-to-be-merged | git apply -R --index
git commit -F .git/COMMIT_EDITMSG --amend

有时您确实想这样做,并不是因为您的历史记录中有“废话”,而是可能是因为您想更改公共存储库中的开发基线,因此应该避免变基 .


git merge --strategy=theirs模拟#4

(同一篇博文)

或者,如果您想保持本地上游分支可快速转发,一个潜在的折衷办法是理解对于 sid/unstable,上游分支可以不时重置/重新设置(基于事件在上游项目方面最终无法控制)。 这没什么大不了的,使用这个假设意味着很容易将本地上游分支保持在只需要快速更新的状态。

git branch -m upstream-unstable upstream-unstable-save
git branch upstream-unstable upstream-remote/master
git merge -s ours upstream-unstable
git diff --binary ref-to-be-merged | git apply -R --index --exclude="debian/*"
git commit -F .git/COMMIT_EDITMSG --amend

git merge --strategy=theirs模拟#5

(由Barak A. Pearlmutter提议):

git checkout MINE
git merge --no-commit -s ours HERS
git rm -rf .
git checkout HERS -- .
git checkout MINE -- debian # or whatever, as appropriate
git gui # edit commit message & click commit button

git merge --strategy=theirs模拟#6

(同Michael Gebetsroither提议):

Michael Gebetsroither 插话,声称我在“作弊”;) 并给出了另一种使用低级管道命令的解决方案:

(如果仅使用 git 命令无法实现,那就不是 git,git 中所有带有 diff/patch/apply 的东西都不是真正的解决方案;)。

# get the contents of another branch
git read-tree -u --reset <ID>
# selectivly merge subdirectories
# e.g superseed upstream source with that from another branch
git merge -s ours --no-commit other_upstream
git read-tree --reset -u other_upstream     # or use --prefix=foo/
git checkout HEAD -- debian/
git checkout HEAD -- .gitignore
git commit -m 'superseed upstream source' -a

git merge --strategy=theirs模拟#7

必要的步骤可以描述为:

    用上游替换你的工作树 将更改应用到索引 将上游添加为第二个父项 提交

命令git read-tree 用不同的树覆盖索引,完成第二步,并具有更新工作树的标志,完成第一步。提交时,git 使用 .git/MERGE_HEAD 中的 SHA1 作为第二个父级,因此我们可以填充它以创建合并提交。因此,这可以通过以下方式完成:

git read-tree -u --reset upstream                 # update files and stage changes
git rev-parse upstream > .git/MERGE_HEAD          # setup merge commit
git commit -m "Merge branch 'upstream' into mine" # commit

【讨论】:

您总是可以只使用我们的而不是他们的:检查另一个分支,将您的合并到其中,然后将您的快进到合并。 git checkout upstream; git merge -s ours downstream; git checkout downstream; git merge upstream。 (如有必要,在上游使用临时分支。)这样做的好处是将上游祖先记录为第一个父级,因此合并意味着“吸收这个过时的主题分支”而不是“销毁这个主题分支并替换它与上游”。 @Jefromi:很好,像往常一样。我已将其包含在我的答案中。 另一个选项——比如 git merge --strategy=theirs Simulation #1——除了这个保留 brange 作为第一个合并父项: git checkout -b tmp origin/upstream git merge -s ours下游 # 忽略来自下游的所有更改 git checkout 下游 git merge --squash tmp # 应用来自 tmp 的更改,但不作为合并。 git rev-parse upstream > .git/MERGE_HEAD #record upstream as the second merge head git commit -m "rebaselined ours from upstream" # 提交。 git branch -D tmp #删除tmp 哇,谁能想到 --strategy=theirs 可以通过这么多方式实现。现在如果它可以在下一个版本的 git 中 VonC 和他的知识令人震惊。他就像 git 的 JonSkeet。 :)【参考方案3】:

听起来你只需要这样做:

$ git reset --hard origin/master

如果没有更改推送上游,并且您只是希望上游分支成为您的当前分支,则可以这样做。在本地执行此操作无害您将丢失任何尚未推送到 master 的本地更改**。

** 实际上,如果您已在本地提交更改,这些更改仍然存在,因为提交仍将在您的git reflog 中,通常至少保留 30 天。

【讨论】:

如果您需要不惜一切代价进行更改,这将起作用,因为它可能会更改分支的历史记录(您必须使用 -f 推送)。可以配置为被阻止,因此实际上它基本上只适用于您自己的私有存储库。【参考方案4】:

还有一种几乎不需要管道命令的方法 - 恕我直言,这是最直接的。假设您想为 2 个分支案例模拟“他们的”:

head1=$(git show --pretty=format:"%H" -s foo)
head2=$(git show --pretty=format:"%H" -s bar)
tree=$(git show --pretty=format:"%T" -s bar)
newhead=$(git commit-tree $tree -p $head1 -p $head2 <<<"merge commit message")
git reset --hard $newhead

这会使用其中一个的树(上例中的 bar,提供“他们的”树)合并任意数量的头(上例中为 2 个),而忽略任何差异/文件问题(提交树是低级命令,所以它不关心那些)。请注意,head 只能是 1(因此相当于带有“theirs”的cherry-pick)。

请注意,首先指定哪个父头会影响某些内容(例如,请参阅 git-log 命令的 --first-parent ) - 所以请记住这一点。

除了 git-show,可以使用任何其他能够输出树和提交哈希的东西 - 任何用于解析的东西(cat-file,rev-list,...)。您可以使用 git commit --amend 跟踪所有内容以交互美化提交消息。

【讨论】:

这是我眼中最直接的。您正在使用管道命令说“用这棵树、这个第一个父级、这个第二个父级创建一个新的提交对象。然后将头部指向这个新的提交。”这正是我们想要的“git merge -s theirs”去做。缺点是您需要保存 4 个不同的哈希值才能使此操作生效。【参考方案5】:

你现在可以很容易地做到这一点:

$ git fetch origin
$ git merge origin/master -s recursive -Xtheirs

这会使您的本地存储库与源同步,并保留历史记录。

【讨论】:

git merge -s recursive -Xtheirs 不会自动合并二进制文件,因此您最终会遇到必须手动解决的冲突情况。基于git merge -s ours 的工作流不会受此影响。 这似乎创建了一个空提交。 我很抱歉 - 它确实有效,但合并提交时的 git show 仅显示冲突解决方案,如果使用 -Xtheirs 显然没有冲突解决方案。 工作就像一个魅力!在我的情况下,我检查了一个旧的提交,因此处于分离状态,但在该状态下继续编码,并最终想将该代码准确地带到我最初从 (dev) 分离的分支上。我创建了一个新分支(临时),提交了所有内容,然后结帐到 dev 并执行此操作: git merge temp -s recursive -Xtheirs【参考方案6】:

git merge -s theirs ref-to-be-merged 的另一个模拟:

git merge --no-ff -s ours ref-to-be-merged         # enforce a merge commit; content is still wrong
git reset --hard HEAD^2; git reset --soft HEAD@1 # fix the content
git commit --amend

双重重置的替代方法是应用反向补丁:

git diff --binary ref-to-be-merged | git apply -R --index

【讨论】:

反向补丁的有趣使用。 +1 重置对我不起作用,我得到了 “致命:不明确的参数 'HEAD2':未知修订版或路径不在工作树中。”。 (是的,我确实输入了HEAD^2)补丁方法确实有效。 @Stijn:很可能您实际上没有正确输入^。有时像“ctrl-c”这样的其他击键会显示为“^C”。 - 如果你真的输入了正确的“^”,你会在你的 git 版本中发现一个严重的错误。 @michas 在 Windows 上,^ 字符用于转义,因此为了将其用作文字,您必须将其与自身一起转义。 git reset --hard HEAD^^2; git reset --soft HEAD@1【参考方案7】:

手忙脚乱,但见鬼,怎么可能出问题?

查看你想要看起来像 Y 的分支 X cp -r .git /tmp 检查分支 Y git checkout y rm -rf .git &amp;&amp; cp -r /tmp/.git. 提交并推动任何差异 完成。

【讨论】:

这是使两个分支相同的最简单的蛮力方法,假设您不关心维护合并历史记录。【参考方案8】:

使用 git reset BACKWARDS!

您可以使用git reset 使分支看起来像任何其他提交,但您必须以一种迂回的方式进行。

要使提交 &lt;old&gt; 上的分支看起来像提交 &lt;new&gt;,您可以这样做

git reset --hard <new>

为了使&lt;new&gt;成为工作树的内容。

那就做吧

git reset --mixed <old> 

将分支更改回原始提交但将工作树留在 &lt;new&gt; 状态

然后您可以添加并提交更改,以使您的分支与&lt;new&gt; 提交的内容完全匹配。

&lt;old&gt; 状态转移到&lt;new&gt; 是违反直觉的,您需要执行git reset from &lt;new&gt; to @987654333 @。但是,使用选项 --mixed 时,工作树将保留在 &lt;new&gt; 并且分支指针设置为 &lt;old&gt;,因此当提交更改时,分支看起来就像我们想要的那样。

警告

不要忘记你的提交,例如在做git reset --hard &lt;new&gt;时忘记&lt;old&gt;是什么。

【讨论】:

【参考方案9】:

我遵循了这些角色:

获取原点,从分支硬重置,然后从他们的递归,然后强制推送到分支

风险自担

git fetch origin
git reset --hard origin/<branch>
git merge origin/<branch> -s recursive -Xtheirs
git push -f <remote> <branch>

【讨论】:

以上是关于git 命令使一个分支像另一个分支的主要内容,如果未能解决你的问题,请参考以下文章

Git 常用命令

Git本地仓库远程仓库操作和分支操作命令

Git分支管理

git 团队协作的一些命令

怎么用git命令创建远程仓库分支

git常用命令备忘