简体   繁体   English

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

[英]git command for making one branch like another

I'm trying to take a branch with changes and bring it back to be identical to the upstream it diverged from.我正在尝试采用具有更改的分支并将其恢复为与它从中分离出来的上游相同。 The changes are both local and have been pushed to github, so neither git reset or git rebase are really viable, since they change history, which is a bad thing with a branch that's already been pushed.这些更改都是本地的,并且已推送到 github,因此git resetgit rebase都不是真正可行的,因为它们更改了历史记录,这对于已经推送的分支来说是一件坏事。

I've also tried git merge with various strategies but none of them undo the local changes, ie if I'd added a file, a merge might bring other files back in line, but I'll still have that file that the upstream doesn't have.我也尝试过git merge与各种策略,但没有一个撤消本地更改,即如果我添加了一个文件,合并可能会使其他文件恢复原状,但我仍然拥有上游没有的那个文件有。

I could just create a new branch off the upstream, but i'd really like a merge that in terms of revision history applies all the changes to take my branch and make it identical to the upstream again, so that I can safely push that change without clobbering history.我可以在上游创建一个新分支,但我真的很喜欢合并,在修订历史方面应用所有更改以获取我的分支并使其再次与上游相同,这样我就可以安全地推送该更改没有破坏历史。 Is there such a command or series of commands?是否有这样的命令或一系列命令?

You could merge your upstream branch to your dev branch, with a custom merge driver "keepTheirs" :您可以使用自定义合并驱动程序“keepTheirs”将您的上游分支合并到您的dev分支:
See " git merge -s theirs ” needed — but I know it doesn't exist ".请参阅“需要git merge -s theirs但我知道它不存在”。
In your case, only one .gitattributes would be required, and a keepTheirs script like:在您的情况下,只需要一个.gitattributes和一个keepTheirs脚本,例如:

mv -f $3 $2
exit 0

git merge --strategy=theirs Simulation #1 git merge --strategy=theirs模拟#1

Shows as a merge, with upstream as the first parent.显示为合并,上游作为第一个父级。

Jefromi mentions (in the comments) the merge -s ours , by merging your work on the upstream (or on a temp branch starting from upstream), and then fast-forwarding your branch to the result of that merge: Jefromi提到(在评论中) 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

This has the benefit of recording the upstream ancestor as the first parent, so that the merge means "absorb this out-of-date topic branch" rather than "destroy this topic branch and replace it with upstream" .这样做的好处是将上游祖先记录为第一个父级,这样合并就意味着“吸收这个过时的主题分支”而不是“销毁这个主题分支并用上游替换它”

(Edit 2011): (2011 年编辑):

This workflow has been reported in this blog post by the OP : OP 在这篇博文中报告了此工作流程:

Why do I want this again?为什么我又要这个?

As long as my repo had nothing to do with the public version, this was all fine, but since now I'd want the ability to collorate on WIP with other team members and outside contributors, I want to make sure that my public branches are reliable for others to branch off and pull from, ie no more rebase and reset on things I've pushed to the remote backup, since it's now on GitHub and public.只要我的回购与公共版本无关,这一切都很好,但现在我希望能够与其他团队成员和外部贡献者在 WIP 上进行协作,我想确保我的公共分支是其他人可以可靠地分支和拉出,即不再对我推送到远程备份的东西进行变基和重置,因为它现在在 GitHub 上并且是公开的。

So that leaves me with how I should proceed.所以这让我知道我应该如何进行。
99% of the time my copy will go into the upstream master, so I want to work my master and push into upstream most of the time. 99% 的时间我的副本会进入上游 master,所以我想工作我的 master 并在大部分时间推入上游。
But every once in a while, what I have in wip will get invalidated by what goes into upstream and I will abandon some part of my wip .但是每隔一段时间,我在wip中的内容就会被进入上游的内容失效,我会放弃我的wip的一部分。
At that point I want to bring my master back in sync with upstream, but not destroy any commit points on my publicly pushed master.那时我想让我的主人与上游同步,但不破坏我公开推送的主人的任何提交点。 Ie i want a merge with upstream that ends up with the changeset that make my copy identical to upstream .即我想要与 upstream 合并,最终得到使我的副本与 upstream 相同的变更集
And that's what git merge --strategy=theirs should do.这就是git merge --strategy=theirs应该做的。


git merge --strategy=theirs Simulation #2 git merge --strategy=theirs模拟 #2

Shows as a merge, with ours as the first parent.显示为合并,我们的作为第一个父级。

(proposed by jcwenger ) (由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 Simulation #3 git merge --strategy=theirs模拟 #3

This 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

sometimes you do want to do this, and not because you have "crap" in your history, but perhaps because you want to change the baseline for development in a public repository where rebasing should be avoided .有时你确实想这样做,并不是因为你的历史中有“废话”,而是因为你想在公共存储库中更改开发基线,而在公共存储库中应该避免变基


git merge --strategy=theirs Simulation #4 git merge --strategy=theirs模拟 #4

(same blog post) (同一篇博文)

Alternatively, if you want to keep the local upstream branches fast-forwardable, a potential compromise is to work with the understanding that for sid/unstable, the upstream branch can from time to time be reset/rebased (based on events that are ultimately out of your control on the upstream project's side).或者,如果你想保持本地上游分支的快速转发,一个潜在的妥协是理解对于 sid/unstable,上游分支可以不时地重置/重新设置(基于最终发生的事件)您在上游项目方面的控制权)。
This isn't a big deal and working with that assumption means that it's easy to keep the local upstream branch in a state where it only takes fast-forward updates.这没什么大不了的,并且使用该假设意味着很容易将本地上游分支保持在只需要快进更新的状态。

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 Simulation #5 git merge --strategy=theirs模拟 #5

(proposed by Barak A. Pearlmutter ): (由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 Simulation #6 git merge --strategy=theirs模拟#6

(proposed by the same Michael Gebetsroither ): (由同一个Michael Gebetsroither 提议):

Michael Gebetsroither chimed in, claiming I was "cheating";) and gave another solution with lower-level plumbing commands: Michael Gebetsroither 插话,声称我在“作弊”;)并给出了另一个具有较低级别管道命令的解决方案:

(it wouldn't be git if it wouldn't be possible with git only commands, everything in git with diff/patch/apply isn't a real solution;). (如果仅使用 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 Simulation #7 git merge --strategy=theirs模拟#7

The necessary steps can be described as:必要的步骤可以描述为:

  1. Replace your worktree with upstream用上游替换你的工作树
  2. Apply the changes to the index将更改应用于索引
  3. Add upstream as the second parent添加上游作为第二父级
  4. Commit犯罪

The command git read-tree overwrites the index with a different tree, accomplishing the second step , and has flags to update the work tree, accomplishing the first step .命令git read-tree用不同的树覆盖索引,完成第二步,并有更新工作树的标志,完成第一步 When committing, git uses the SHA1 in.git/MERGE_HEAD as the second parent, so we can populate this to create a merge commit.提交时,git 使用 .git/MERGE_HEAD 中的 SHA1 作为第二个父级,因此我们可以填充它来创建合并提交。 Therefore, this can be accomplished with:因此,这可以通过以下方式实现:

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

You can do this rather easily now:你现在可以很容易地做到这一点:

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

This gets your local repo in-sync with the origin, and preserves the history.这使您的本地回购与原点同步,并保留历史记录。

It sounds to me like you just need to do:在我看来你只需要这样做:

$ git reset --hard origin/master

If there is no change to push upstream, and you simply want the upstream branch to be your current branch, this will do that.如果没有更改向上游推送,并且您只是希望上游分支成为您当前的分支,这将做到这一点。 It is not harmful to do this locally but you will lose any local changes** that haven't been pushed to master.在本地执行此操作没有害处,您将丢失所有未推送到 master 的本地更改**。

** Actually the changes are still around if you have committed them locally, as the commits will still be in your git reflog , usually for at least 30 days. ** 实际上,如果您在本地提交更改,更改仍然存在,因为提交仍将在您的git reflog中,通常至少 30 天。

Another simulation for git merge -s theirs ref-to-be-merged : 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

An alternative to the double reset would be applying the reverse patch:双重重置的替代方法是应用反向补丁:

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

There's also a way with little help of plumbing command - IMHO the most straightforward.还有一种方法几乎不需要管道命令的帮助 - 恕我直言,这是最直接的。 Say you want to emulate "theirs" for 2 branches case:假设您想为 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

This merges arbitrary number of heads (2 in the example above) using tree of one of them (bar in the example above, providing 'theirs' tree), disregarding any diff/file issues (commit-tree is low level command, so it doesn't care about those).这使用其中一个树(上面示例中的 bar,提供“他们的”树)合并任意数量的头(上例中为 2 个),忽略任何差异/文件问题(commit-tree 是低级命令,因此它不关心那些)。 Note that head can be just 1 (so equivalent of cherry-pick with "theirs").请注意,head 可以仅为 1(因此相当于用“他们的”挑选樱桃)。

Note, that which parent head is specified first, can influence some stuff (see eg --first-parent of git-log command) - so keep that in mind.请注意,首先指定哪个父头会影响某些东西(例如,参见 git-log 命令的 --first-parent)——所以请记住这一点。

Instead of git-show, anything else capable of outputting tree and commit hashes can be used - whatever one's is used to parsing (cat-file, rev-list, ...).除了 git-show,任何其他能够输出树和提交散列的东西都可以使用——任何用于解析的东西(cat-file,rev-list,...)。 You can follow everything with git commit --amend to interactively beautify commit message.您可以使用 git commit --amend 跟踪所有内容,以交互方式美化提交消息。

Heavy handed, but hell, what can possibly go wrong?笨手笨脚,但是地狱,什么可能出错?

  • Check out the branch X you want to look like the Y检查您想要看起来像 Y 的分支 X
  • cp -r.git /tmp
  • Check out branch Y git checkout y检查分支 Y git checkout y
  • rm -rf.git && cp -r /tmp/.git . rm -rf.git && cp -r /tmp/.git
  • Commit & push any difference提交并推动任何差异
  • DONE.完毕。

change to the remote upstream branch and do a git merge with the merge strategy set to ours .更改到远程上游分支并执行git merge并将合并策略设置为ours

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

All the history will still be present, but you'll have an extra merge commit.所有历史记录仍将存在,但您将有一个额外的合并提交。 The important thing here is to start from the version you want to be at and merge ours with the branch github is actually at.这里重要的是从你想要的版本开始,并将ours的版本与 github 实际所在的分支合并。

Use git reset BACKWARDS!向后使用 git reset!

You can make a branch look like any other commit with git reset , but you have to do it in a round-about way.您可以使用git reset使分支看起来像任何其他提交,但您必须以一种迂回的方式进行。

To make a branch on commit <old> look like a commit <new> , you can do要使提交<old>的分支看起来像提交<new> ,您可以这样做

git reset --hard <new>

in order to make <new> the contents of the working tree.为了使<new>成为工作树的内容。

Then do然后做

git reset --mixed <old> 

to change the branch back to the original commit but leaving working tree in the <new> state .将分支更改回原始提交,但将工作树留在<new>状态

Then you can add and commit the changes, in order to make your branch exactly match the contents of the <new> commit.然后您可以添加并提交更改,以使您的分支与<new>提交的内容完全匹配。

It's counter-intuitive that to move from the <old> state to the <new> you need to do a git reset from <new> to <old> .违反直觉的是,要从<old>状态移动到<new>您需要执行<new><old>git reset However with the option --mixed the working tree is left at <new> and the branch pointer set to <old> , so that when the changes are committed the branch looks how we want.然而,使用选项--mixed工作树保留在<new>并且分支指针设置为<old> ,以便在提交更改时分支看起来像我们想要的那样。

Warning警告

Don't lose track of your commits, eg forget what <old> is when doing git reset --hard <new> .不要忘记你的提交,例如在执行git reset --hard <new>时忘记<old>是什么。

I followed those roles:我遵循了这些角色:

Fetching, reset hard from the branch then recursive from theirs and then forced push to the branch从分支中获取,硬重置然后从他们的递归然后强制推送到分支

ON YOUR OWN RISK风险自负

git fetch
git reset --hard <branch>
git merge <branch> -s recursive -X theirs
git push -f <remote> <branch>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM