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?
Git Merge Vs Git Rebase:Git merge is a command that allows you to merge branches from Git. Git rebase is a command that allows developers to integrate changes from one branch to another. In Git Merge logs will be showing the complete history of the merging of commits.
To make a merge, you check out some branch ( A in these cases) and then run git merge and give it at least one argument, typically another branch name like B . The merge command starts by turning the name into a commit ID. A branch name turns into the ID of the tip-most commit on the branch.
To merge branches locally, use git checkout to switch to the branch you want to merge into. This branch is typically the main branch. Next, use git merge and specify the name of the other branch to bring into this branch. This example merges the jeff/feature1 branch into the main branch.
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 #1Shows 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 inwip
will get invalidated by what goes into upstream and I will abandon some part of mywip
.
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 whatgit merge --strategy=theirs
should do.
git merge --strategy=theirs
Simulation #2Shows 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 #3This 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
git merge --strategy=theirs
Simulation #7The necessary steps can be described as:
- Replace your worktree with upstream
- Apply the changes to the index
- Add upstream as the second parent
- 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. When committing, git uses the SHA1 in .git/MERGE_HEAD as the second parent, so we can populate this to create a merge commit. 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
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.
** 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With