Change direction of git merge (after the merge is done) -
i have branch (e.g. feature-x) in git repo.
what did following
git checkout master git merge feature-x there quite few conflicts resolved. haven't committed changes yet.
but, turns out wanted reverse merge (i.e. merge master feature-x) because branch still unstable.
is there command can salvage work did in resolving conflicts or need resolution once more, time in branch feature-x?
i'm thinking whether there way current patch apply in branch feature-x in "reverse" order. e.g. when patch says line x changed y, relative feature-x going master. if want opposite, patch should line y changed x, right? thoughts. solution ok.
tl;dr: see end
there "recipe" @ end; scroll down find (and bit find description , explanation , safer method).
good news , bad news
in significant sense, merges don't have "direction". (the direction "see" when @ merge product of imagination, plus merge commit's message, plus 1 more important thing our "bad news"—but it's not fatally bad, in end.)
consider diagram of pair of branches commit * merge base:
o--o--...--o <-- branch1 / ...--o--* \ o--o--...--o <-- branch2 the process of merging ("merge verb") branch1 , branch2 achieved by:
- comparing, i.e.,
git diff, merge base commit*tip commit ofbranch1; - comparing merge base tip of
branch2; - then, starting base, combining both sets of changes.
hence actual merge should this:
o--o--...--o / \ ...--o--* m \ / o--o--...--o (i named merge commit m since don't know, or care, crazy git hash id deadbeef or badc0ffee or whatever have.) eventual merge commit m is merge ("merge noun"); stores final source tree, based on merge-as-a-verb combining process.
which "direction" merge? well, depends, @ least in part, on labels stick on it, doesn't it? :-) note stripped away both branch1 , branch2. let's put them back:
o--o--...--o <-- branch1? / \ ...--o--* m <-- branch1? branch2? \ / o--o--...--o <-- branch2? this might confusing. is confusing. it's big part of whole issue. the bad news is, it's not whole thing. there's cannot quite capture in these drawings. may not matter you, , if does, it's not big deal anyway; we'll fix making 1 more merge commit. now, let's press on.
making merge commit
once make merge commit m itself, have choose—or have git choose-which branch it's on. in simple, unconflicted merge, end having git choose this. git does simple: whatever branch on, when start git merge command, branch gets merge commit. so:
git checkout branch1 git merge branch2 means final picture is:
o--o--...--o / \ ...--o--* m <-- branch1 \ / o--o--...--o <-- branch2 which can re-draw as:
...--o--*--o--...--o--m <-- branch1 \ / o---...---o <-- branch2 which makes obvious merged branch2 branch1, not vice versa. nonetheless, source code attached commit m not depend on "merge direction".
sidebar: conflicted merges
conflicted merges unconflicted merges in terms of final result. it's git can't merge on own. stops , makes you resolve conflicts, , run git commit make merge commit. final git commit step makes merge commit m.
the new merge commit goes on current branch, other commit. that's how m winds on branch1. note merge commit's message says branch name merged other branch name—but have chance edit while make commit, can change suit whatever whim may have. :-)
(in fact, no different unconflicted case: both run git commit make final merge commit, , both give chance edit message.)
the bad news
the bad news these diagrams omit may matter you. git has notion of first parent: merge commit m has 2 parents, 1 of these first parent , 1 not.1 first parent how tell branch on when made merge.
hence, while these diagrams, , merge-as-a-verb process, show there isn't "merge direction", first-parent notion proves there is. if ever use first-parent notion, care this, , want right. fortunately, there solution. (in fact, there multiple solutions, i'll show klunky-but-straightforward ones first.)
1obviously, other 1 second parent: there 2 parents. git allows, however, merge commits 3 or more parents. can enumerate parents whenever have to; git considers first important, , has --first-parent flags various commands, follow "the original branch".
getting merge want
let's go ahead , make "wrong" merge commit:
# git add ... (if needed) git commit now have:
o--o--...--o <-- [old branch1 tip] / \ ...--o--* m <-- branch1 \ / o--o--...--o <-- branch2 where m's first parent old branch1 tip.
what want make new merge commit (with different id) that's same m except that:
branch2points it- its first parent tip of
branch2 - its second parent previous tip of
branch1
the trick save commit m somehow—that's easy enough, we'll use temporary name don't have copy down m's hash id—and reset branch1:
git branch temp # or git tag temp git reset --hard head~1 # move branch1 back, dropping m (note same brehonia's answer, far). have graph, quite unchanged except labels attached each commit:
o--o--...--o <-- branch1 / \ ...--o--* m <-- temp \ / o--o--...--o <-- branch2 now check out branch2 , run merge on again; fail conflicts before:
git checkout branch2 git merge branch1 # use --no-commit if git commit merge the commit have not yet made same, in sense, merge commit m—but once make it, first parent current tip of branch2, , second parent current tip of branch1. need right source go commit we're going make.
now use merge result saved name temp. assumes you're in top level of tree, . names everything:
git rm -rf -- . # remove git checkout temp -- . # temp we using merge result committed earlier. not have git add form of git checkout updates index, so:
git commit and have our new merge m2, on branch branch2, desired:
o--o--...--o__ <-- branch1 / \ \ ...--o--* m \ <-- temp \ / \ o--o--...--o-----m2 <-- branch2 now can delete temporary branch or tag:
git branch -d temp # or git tag -d temp and done. temporary name gone, can't see original merge m more.
sneaky plumbing method
there's shorter way this, using git's "plumbing" commands, it's bit tricky. once have git add-ed , have run git commit, have right tree, have commit has wrong first , second parent ids. may wish edit commit message well, looks you're merging branch1 branch2 in message. then:
# git add ... (if / needed, above) # git commit (make merge) git branch -f branch2 $(git log --pretty=format:%b --no-walk head | git commit-tree -p head^2 -p head^1 -f -) git reset --hard head^1 the git commit-tree step makes new merge commit that's copy of 1 made, 2 parents swapped. make branch2 point commit, using git branch -f. be sure name (branch2) right @ step. head^1 , head^2 names refer 2 (first , second) parents of current commit, of course merge commit made. has right tree, , right commit message text; has wrong first , second parent hashes.
once have new commit safely added branch2, reset our current (branch1) branch remove merge commit made.
Comments
Post a Comment