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


80

我正在尝试进行更改的分支,并将其恢复到与其分支不同的上游。更改既是本地的,也已推送到github,因此它们都不可行,git reset或者说git rebase是真正可行的,因为它们更改了历史记录,这对于已经被推送的分支是一件坏事。

我也尝试git merge了各种策略,但是没有一个能够撤消本地更改,即,如果我添加了一个文件,则合并可能会使其他文件恢复原状,但我仍然会拥有上游没有的文件有。

我可以在上游创建一个新分支,但是我真的很想合并,就修订历史而言,所有更改都将应用到我的分支中,并使它再次与上游相同,以便我可以安全地推送该更改不会破坏历史。有这样的命令或一系列命令吗?


9
如果您不关心保留更改,为什么不删除并重新创建分支呢?“项目的历史”不必是神圣的。Git是帮助开发人员交流的工具。如果这些更改没有帮助,请将其丢弃。
wnoise 2011年

+100 @wnoise -特别是如果变化已经合并英寸
wuputah

7
我关心保存历史,因为它是为合作而出版的,我可能想回到它。如果只保留最新版本,为什么还要使用版本控制呢?
Arne Claassen

1
这是一个主观论点,但对我而言,VCS的目的不是记录项目历史记录的每个细节,而仅记录内容的更改(提交),以便您可以基于树/历史记录进行操作这些提交(分支,合并,重新设置基准,重置等),并允许您基于历史记录(差异,日志,归咎等)查看报告。git是“愚蠢的内容跟踪器”-我将其视为管理源代码的工具,而不是时间机器。
wuputah'2

5
如您所说,这是主观的。我关心的是能够审查废弃的方法,并能够查看过去某个时间做出的决定。而且我在乎我放弃某些内容的决定不会破坏其他人可能指向的合并点。
阿内·克拉森

Answers:


105

您可以dev使用自定义合并驱动程序“ keepTheirs”将上游分支合并到分支:
请参阅所需的git merge -s theirs”,但我知道它不存在“。
在您的情况下,只.gitattributes需要一个,keepTheirs脚本如下:

mv -f $3 $2
exit 0

git merge --strategy=theirs 模拟#1

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

Jefromimerge -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):

OP在此博客文章中报告了此工作流程:

为什么我要再次?

只要我的存储库与公共版本无关,那都很好,但是从现在起,我希望能够与其他团队成员和外部贡献者一起在WIP上进行合谋,所以我想确保我的公共分支机构是对于其他人来说,可以可靠地分支和退出,即不再需要重新设置和重置我推送到远程备份的内容,因为它现在已在GitHub和Public上发布。

这样,我就可以继续进行下去了。
99%的时间我的副本将进入上游母版,因此我想工作我的母版并大部分时间推入上游。
但是每隔一段时间,wip进入上游的资源将使我拥有的资源失效,并且我将放弃我的部分资产wip
那时,我想使我的主服务器与上游保持同步,但不破坏公开推送的主服务器上的任何提交点。即我想要与上游合并,最后合并更改集,使我的副本与上游相同
那就是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

这篇博客文章提到

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

必要步骤可以描述为:

  1. 用上游替换您的工作树
  2. 将更改应用到索引
  3. 将上游添加为第二父级
  4. 承诺

该命令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

7
您总是可以只使用我们的分支而不是他们的分支:签出另一个分支,将您的分支合并到其中,然后将您的分支快速转发到合并。git checkout upstream; git merge -s ours downstream; git checkout downstream; git merge upstream。(如果需要,在上游使用临时分支。)这样做的好处是将上游祖先记录为第一父分支,这样合并意味着“吸收了这个过时的主题分支”,而不是“销毁了这个主题分支并替换了”与上游”。
卡斯卡贝尔2011年

@Jefromi:像往常一样非常好。我已将其包含在我的答案中。
VonC'2

另一个选项-像git merge --strategy = theirs Simulation#1-除外,它保留了brange作为第一个合并父对象:git checkout -b tmp origin / upstream git merge -s我们的下游#忽略下游git的所有更改签出下游git merge --squash tmp#应用来自tmp的更改,但不作为合并。git rev-parse上游> .git / MERGE_HEAD#将上游记录为第二个合并头git commit -m“从上游对我们的基线进行基线化”#进行提交。git branch -D tmp#删除tmp
jcwenger 2011年

1
哇,谁会想到--strategy = theirs可以以多种方式实施。现在,如果它只是下一版的git
Arne Claassen

VonC和他的知识令人惊讶。他就像git的JonSkeet。:)
sjas

13

在我看来,您只需要这样做:

$ git reset --hard origin/master

如果没有任何变化可推动上游,而您只是希望上游分支成为您的当前分支,则可以做到这一点。在本地进行此操作无害,但是您将丢失所有尚未推送的本地更改**。

**实际上,如果您已在本地提交更改,更改仍会存在,因为提交仍会存在于您的中git reflog,通常至少持续30天。


如果您需要以任何价格进行更改,则此方法有效,因为它可能会更改分支的历史记录(您必须使用-f进行推送)。可以将其配置为阻止,因此实际上,它基本上仅对您自己的私有存储库有效。
ribamar

13

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

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

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


5
git merge -s recursive -Xtheirs不会自动合并二进制文件,因此您最终陷入冲突,必须手动解决。基于的工作流git merge -s ours不会因此受到影响。
Stefaan

这似乎创建一个空的提交。
罗宾·格林

抱歉,它确实有效,但是git show在合并提交中仅显示冲突解决方案,并且如果-Xtheirs使用冲突解决方案,则显然没有。
罗宾·格林

5

另一个模拟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
VonC

重置对我不起作用,我得到了“致命:模棱两可的参数'HEAD2':未知的修订或路径不在工作树中”。。(是的,我输入了HEAD^2)补丁方法起作用了。
user247702 '16

@Stijn:很可能您实际上没有^正确键入。有时其他击键(例如“ ctrl-c”)显示为“ ^ C”。-如果您确实输入了正确的“ ^”,则会在git版本中发现一个严重的错误。
michas '16

@michas在Windows上,^字符用于转义,因此,要将其用作文字,您必须将其自身转义。git reset --hard HEAD^^2; git reset --soft HEAD@{1}
约书亚·沃尔什

4

还有一种几乎不需要管道命令的方法-IMHO最简单。假设您要为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

这会使用其中一个头的树(上例中的条,提供“他们的”树)合并任意数量的头(上例中为2),而不考虑任何差异/文件问题(commit-tree是低级命令,因此它不在乎那些)。请注意,head可以仅为1(因此相当于“ theirs-pick”和“ theirs”)。

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

除了git-show之外,还可以使用其他任何能够输出树和提交哈希值的东西-用来解析的东西(cat文件,rev-list等)。您可以使用git commit --amend遵循所有内容,以交互方式美化提交消息。


这是我眼中最直接的。您正在使用管道命令说“使用此树,第一个父级,第二个父级创建一个新的提交对象。然后将头指向这个新的提交。”,这正是我们想要的“ git merge -s他们的”去做。缺点是您需要保存4个不同的哈希值才能使此操作起作用。
Michael R

2

沉重的双手,但地狱,可能出什么毛病?

  • 签出您想要看起来像Y的分支X
  • cp -r .git /tmp
  • 结帐Y git checkout y
  • rm -rf .git && cp -r /tmp/.git
  • 承诺并推动任何差异
  • 完成。

假设您不关心维护合并历史记录,这是使两个分支相同的最简单的暴力方式。
戴蒙·D

1

更改为远程上游分支,然后git merge将合并策略设置为ours

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

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


1
我需要相反。这将占用我的分支并将其集成到上游,但不更改上游头。但是我需要采取上游措施,并将其整合到我的分支机构中,使自己的头看起来像上游。基本上像--strategy=theirs,除了最接近的东西--strategy=recursive -X=theirs并不能完全做到这一点。
Arne Claassen

--strategy=theirs与恰恰相反--strategy=ours。您从另一端开始(因此从github开始并以另一种方式合并)。
kelloti 2011年

没有--strategy=theirs,这就是问题所在。最接近的--strategy=recursive -X theirs不是完全相反,因为如果它们不冲突,它不会删除无关的本地更改。
阿内·克拉森

这两个是相反的:git checkout dev; git merge origin/master --strategy=oursgit checkout origin/master; git merge dev --strategy=ours
wuputah 2011年

2
@Arne:请参阅我对VonC答案的评论。该ours策略的存在使完全有可能执行theirs策略。
卡斯卡贝尔2011年

1

使用git reset向后!

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

要使提交分支<old>看起来像一个提交<new>,您可以执行

git reset --hard <new>

为了使<new>工作树的内容。

然后做

git reset --mixed <old> 

将分支更改回原始提交,但使工作树保持 <new> 状态

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

<old>状态转移到状态,<new>您需要执行git reset from <new> ,这是违反直觉的<old>。但是,使用该选项时--mixed,工作树保留在其中<new>,并且分支指针设置为<old>,以便在提交更改后,分支看起来像我们想要的那样。

警告

不要忘记自己的提交,例如,忘了<old>做什么git reset --hard <new>


0

我遵循这些角色:

获取原点,从分支硬复位,然后从分支递归,然后强制推送到分支

自己承担风险

git fetch origin
git reset --hard origin/<branch>
git merge origin/<branch> -s recursive -Xtheirs
git push -f <remote> <branch>
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.