git command for making one branch like another

2018-12-31 15:21发布

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.

I've also tried git merge with various strategies but none of them undo the local changes, i.e. 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.

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?

8条回答
看风景的人
2楼-- · 2018-12-31 15:59

change to the remote upstream branch and do a git merge with the merge strategy set to 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.

查看更多
人气声优
3楼-- · 2018-12-31 16:00

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:

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). Note that head can be just 1 (so equivalent of cherry-pick with "theirs").

Note, that which parent head is specified first, can influence some stuff (see e.g. --first-parent of git-log command) - so keep that in mind.

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, ...). You can follow everything with git commit --amend to interactively beautify commit message.

查看更多
伤终究还是伤i
4楼-- · 2018-12-31 16:01

You could merge your upstream branch to your dev branch, with a custom merge driver "keepTheirs":
See "git merge -s theirs” needed — but I know it doesn't exist".
In your case, only one .gitattributes would be required, and a keepTheirs script like:

mv -f $3 $2
exit 0

git merge --strategy=theirs Simulation #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:

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

This workflow has been reported in this blog post by the 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, i.e. no more rebase and reset on things I've pushed to the remote backup, since it's now on GitHub and public.

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.
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.
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. I.e. i want a merge with upstream that ends up with the changeset that make my copy identical to upstream.
And that's what git merge --strategy=theirs should do.


git merge --strategy=theirs Simulation #2

Shows as a merge, with ours as the first parent.

(proposed by 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

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

(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).
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

(proposed by 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

(proposed by the same Michael Gebetsroither):

Michael Gebetsroither chimed in, claiming I was "cheating" ;) and gave another solution with lower-level plumbing commands:

(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 ;).

# 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
查看更多
长期被迫恋爱
5楼-- · 2018-12-31 16:02

Heavy handed, but hell, what can possibly go wrong?

  • Check out the branch X you want to look like the Y
  • cp -r .git /tmp
  • Check out branch Y
  • rm -rf .git && cp -r /tmp/.git .
  • Commit & push any difference
  • DONE.
查看更多
弹指情弦暗扣
6楼-- · 2018-12-31 16:04

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.

查看更多
看风景的人
7楼-- · 2018-12-31 16:05

Another simulation for 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
查看更多
登录 后发表回答