git - How did I cross the streams, and how can I uncross them? -
i've been using git happily better part of year, , our team using git-flow model (although not git-flow itself). last week created release branch , have been happily merging changes development/unstable branch - until strange happened today.
somehow, release branch got hyper-merged, or something. can't think of better way describe "crossing streams". here's visual git extensions:
i'm obfuscating of commit comments, sorry. starting bottom, can see development branch on left , release branch on right being periodically merged it. until halfway through, when must have done wrong merge, because @ point release branch literally merged development branch - appear share merge commit should have been on development branch.
github presents clearer view:
this doesn't unusual, except fact blue , black lines supposed same branch. tick off obvious:
there commits other contributors, specific commits in question me. know can trust commit messages.
the commit messages all same thing: "merge branch 'release-1.3' develop." know didn't, example, accidentally merge development branch release branch instead.
i never created new branches or used commands other
git pull --rebase
,git commit
, ,git merge
. wasn't experimenting or trying creative repository.
it looks dropped original release-1.3
branch , created new 1 develop
branch near middle. didn't that. release-1.3
branch has been active whole time. swear blue , black commits same branch , never did merges other merging them both development (pink) branch.
so have 2 questions:
how possible? understand technically how possible, git commits being graph nodes , that, can't quite figure out how happened procedural point of view. mistake have made cause this, , how can avoid doing again?
how can these branches untangled, don't have bunch of half-finished code unstable branch in squeaky-clean release branch? should 2 parallel lines connected merges. preferably, i'd without forced push or destructive rebase, since shared repository, although it's small team , private repository can people re-clone if absolutely necessary.
the possibilities
i can think of couple of ways have happened.
fast-forward merge. believe cause. believe happened:
release-1.3
mergeddevelop
- immediately after,
develop
fast-forward mergedrelease-1.3
that's all.release-1.3
mergeddevelop
, before additional commits made onrelease-1.3
mergeddevelop
release-1.3
. when possible,git
fast-forward merge default (a "feature" wrong thing half time), why graph looks confusing.note fast-forward merge leaves no direct evidence in resulting graph. unlike regular merge, fast-forward merge not create new commit object, , no existing commit modified. fast-forward merge adjusts branch reference point existing commit.
even though there no direct evidence of fast-forward merge, fact can reach 1 merge commit following each branch's first-parent path indicates result of fast-forward merge.
accidental
git branch
instead ofgit checkout
. on project working on, developer new git made mistake of typinggit branch foo
in attempt switch branchfoo
. natural mistake, , 1 expert can make when tired. anyway, mistake resulted in looked fast-forward merge thoughgit merge
never typed. similar thing have happened here. how scenario plays out:- the user has
develop
branch checked out, ,develop
happens pointing merge commit @ center of mystery. - the user wishes switch
release-1.3
branch work, accidentally typesgit branch release-1.3
instead ofgit checkout release-1.3
. - because fresh clone, there no local
release-1.3
branch yet (onlyorigin/release-1.3
). thus, git happily creates new local branch namedrelease-1.3
pointing @ same merge commit. - some time passes while user edits files.
- preparing commit, user runs
git status
. because current branch stilldevelop
, notrelease-1.3
, git prints "on branch develop". - the user caught surprise, , thinks, "didn't switch release-1.3 long time ago? oh well, must have forgotten switch branches."
- the user runs
git checkout release-1.3
, time remembering correct command. - the user creates commit , runs
git push
. - git's default push behavior (see
push.default
ingit config
)matching
, thoughrelease-1.3
doesn't have configured upstream branch,git push
chooses upstream'srelease-1.3
branch push to. - the new version of
release-1.3
descendant of previousrelease-1.3
, remote repository happily accepts push.
this take produce graph provided in question.- the user has
assuming develop
intentionally merged release-1.3
if merge of develop
release-1.3
intentional (someone made conscious decision develop
enough ship), normal , correct. despite visual differences, blue , black lines both on release-1.3
branch; blue line happens also on develop
branch.
the thing wrong it's little awkward reviewing history figure out what's going on (you wouldn't have question otherwise). prevent happening again, follow these rules of thumb:
- if merging 2 differently-named branches (e.g.,
develop
,release-1.3
, alwaysgit merge --no-ff
. - if merging 2 versions of same branch (e.g.,
develop
,origin/develop
), alwaysgit merge --ff-only
. if fails because can't fast-forward, it's timegit rebase
.
if had followed above rules of thumb, graph have looked this:
* (develop) | * (release-1.3) * | merge... |\| | * added... | * using ... * | adding... | * hide s... * | date ... * | updati... * | candi... | * locali... | * <---- merge commit have been created |/| 'git merge' had used '--no-ff' option * | merge... |\| | * un-ign... | * added... * | merge... |\| | * remov... | * move... * | fixed...
notice how merge commit makes history more readable.
if merge of develop
release-1.3
mistake
doh! looks have rebasing , force-pushing do. not make other users of repository happy.
here's how can fix it:
- run
git checkout release-1.3
. - find sha1 of middle commit (where 2 branches come together). let's call
x
. run
git rebase --onto x^2 x
. resulting graph this:* (develop) | * (release-1.3) * | merge... |\ | | * | added... | | * added... | * | using ... | | * using ... * | | adding... | * | hide s... | | * hide s... * | | date ... * | | updati... * | | candi... | * | locali... |/ * locali... * / merge... |\| | * un-ign... | * added... * | merge... |\| | * remov... | * move... * | fixed...
this fixes
release-1.3
branch, notice how have 2 versions ofrelease-1.3
commits. next steps remove these duplicatesdevelop
branch.- run
git checkout develop
. - run
git branch temp
act temporary placeholder commit. - run
git reset --hard head^^
remove 2 commitsdevelop
branch: tipdevelop
commit , commit merging old version ofrelease-1.3
develop
. we'll restore tip commit later. - run
git merge --no-ff release-1.3^
merge second commit on newrelease-1.3
branch , ancestorsdevelop
. - run
git cherry-pick temp
bring tip commit removed in step #6. run
git branch -d temp
rid of temporary placeholder branch. graph should this:* (develop) | * (release-1.3) * | merge... |\| | * added... | * using ... * | adding... | * hide s... * | date ... * | updati... * | candi... | * locali... * | merge... |\| | * un-ign... | * added... * | merge... |\| | * remov... | * move... * | fixed...
run
git push -f origin release-1.3 develop
force-update upstream branches.
preventing happening again in future
if have control on upstream repository , can install hooks, can create hook rejects pushes old version of branch isn't reachable starting @ new version of branch , walking first-parent path. has advantage of rejecting those stupid commits created git pull
.
Comments
Post a Comment