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:

crossed streams gitex

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:

crossed streams github

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:

  1. 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?

  2. 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:

    1. release-1.3 merged develop
    2. immediately after, develop fast-forward merged release-1.3


    that's all. release-1.3 merged develop, before additional commits made on release-1.3 merged develop 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 of git checkout. on project working on, developer new git made mistake of typing git branch foo in attempt switch branch foo. natural mistake, , 1 expert can make when tired. anyway, mistake resulted in looked fast-forward merge though git merge never typed. similar thing have happened here. how scenario plays out:

    1. the user has develop branch checked out, , develop happens pointing merge commit @ center of mystery.
    2. the user wishes switch release-1.3 branch work, accidentally types git branch release-1.3 instead of git checkout release-1.3.
    3. because fresh clone, there no local release-1.3 branch yet (only origin/release-1.3). thus, git happily creates new local branch named release-1.3 pointing @ same merge commit.
    4. some time passes while user edits files.
    5. preparing commit, user runs git status. because current branch still develop , not release-1.3, git prints "on branch develop".
    6. the user caught surprise, , thinks, "didn't switch release-1.3 long time ago? oh well, must have forgotten switch branches."
    7. the user runs git checkout release-1.3, time remembering correct command.
    8. the user creates commit , runs git push.
    9. git's default push behavior (see push.default in git config) matching, though release-1.3 doesn't have configured upstream branch, git push chooses upstream's release-1.3 branch push to.
    10. the new version of release-1.3 descendant of previous release-1.3, remote repository happily accepts push.


    this take produce graph provided in question.

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, always git merge --no-ff.
  • if merging 2 versions of same branch (e.g., develop , origin/develop), always git merge --ff-only. if fails because can't fast-forward, it's time git 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:

  1. run git checkout release-1.3.
  2. find sha1 of middle commit (where 2 branches come together). let's call x.
  3. 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 of release-1.3 commits. next steps remove these duplicates develop branch.

  4. run git checkout develop.
  5. run git branch temp act temporary placeholder commit.
  6. run git reset --hard head^^ remove 2 commits develop branch: tip develop commit , commit merging old version of release-1.3 develop. we'll restore tip commit later.
  7. run git merge --no-ff release-1.3^ merge second commit on new release-1.3 branch , ancestors develop.
  8. run git cherry-pick temp bring tip commit removed in step #6.
  9. 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... 
  10. 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

Popular posts from this blog

javascript - Count length of each class -

What design pattern is this code in Javascript? -

hadoop - Restrict secondarynamenode to be installed and run on any other node in the cluster -