Skip to content

Instantly share code, notes, and snippets.

@canton7
Created January 6, 2012 13:45

Revisions

  1. canton7 revised this gist Jan 13, 2012. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion 0_main.md
    Original file line number Diff line number Diff line change
    @@ -5,7 +5,7 @@ These examples were created during a discussion with j416 on #github about how b

    The scenario is this:
    You have a long-running feature branch, based off master.
    The feature branch and master both touch the same files, so you know that there will be conflicts when you finally merge the feature branch in.
    The feature branch and master both touch the same parts of the same files, so you know that there will be conflicts when you finally merge the feature branch in.
    What can you do to minimise the conflicts?

    The simplest answer is to rebase the feature branch frequently onto master.
  2. canton7 revised this gist Jan 6, 2012. 1 changed file with 1 addition and 7 deletions.
    8 changes: 1 addition & 7 deletions 3_final_rebase.sh
    Original file line number Diff line number Diff line change
    @@ -57,13 +57,7 @@ j416_test ((f19dec2...)|REBASE) $ git st
    # both modified: file1
    #
    no changes added to commit (use "git add" and/or "git commit -a")
    j416_test ((f19dec2...)|REBASE) $ cat file1
    <<<<<<< HEAD
    A1
    =======
    B1
    >>>>>>> B1
    j416_test ((f19dec2...)|REBASE) $ echo B1 > file1
    j416_test ((f19dec2...)|REBASE) $ echo B1 > file1 # Resolve the conflict
    j416_test ((f19dec2...)|REBASE) $ git add file1
    j416_test ((f19dec2...)|REBASE) $ git rebase --continue
    Applying: B1
  3. canton7 created this gist Jan 6, 2012.
    54 changes: 54 additions & 0 deletions 0_main.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,54 @@
    Workflows for long-running feature branches
    ===========================================

    These examples were created during a discussion with j416 on #github about how best to manage long-running feature branches.

    The scenario is this:
    You have a long-running feature branch, based off master.
    The feature branch and master both touch the same files, so you know that there will be conflicts when you finally merge the feature branch in.
    What can you do to minimise the conflicts?

    The simplest answer is to rebase the feature branch frequently onto master.
    This will work fine, so long as the feature branch isn't public. If it is, you get the standard rewriting-public-history problem.
    Another consideration is that, if the feature branch is large, it might take a long time to rebase.

    The other option is to merge master into the feature branch frequently.
    This gist shows how this approach works, and

    This gist has four examples:
    1. The feature branch is never merged into.
    1. Master is merged into the feature branch.
    1. Master is merged into the feature branch, then the feature branch is rebased onto master before it's merged into master.
    1. The above, but with the help of git-rerere.

    In all of the examples, master makes a single change, A1, which modifies the two files, file1 and file2.
    The feature branch makes two changes, B1 (which modifies file1) and B2 (which modifies file2).

    A1 and B1 conflict, as do A1 and B2.

    Example 1: The feature branch is never merged into
    --------------------------------------------------

    Here, master makes the change A1, and the feature branch makes the changes B1 and B2.
    When the feature branch is merged into master, the conflicts A1/B1 (in file1) and A1/B2 (in file2) both occur.

    Example 2: Master is merged into the feature branch
    ---------------------------------------------------

    Here, master makes the change A1, and the feature branch makes the change B1.
    Master is then merged into feature, causing A1/B1 to conflict in file1.
    The feature branch then makes the change B2, and the feature branch is merged into master without conflict.

    Example 3: Master is merged into the feature branch, then the feature branch is rebased onto master before it's merged into master
    --------------------------------------------------------------------------------------------------------------------------------------

    This example is largely the same as above.
    However, before the final merge, the feature branch is rebased on top of master.
    This results in the merges into the feature branch being stripped, and the history is nice and linear (git-flow style).
    The downside is that the conflict A1/B1 must be resolved twice -- once on the merge into the feature branch, and once on the rebase.

    Example 4: The above, but with the help of git-rerere
    -----------------------------------------------------

    This example is exactly the same as before, except we use git-rerere to avoid the second merge conflict.
    Git-rerere automatically records the result of the first merge conflict, and then uses this to automatically resolve the second one (the results of the rebase).
    47 changes: 47 additions & 0 deletions 1_no_merging.sh
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,47 @@
    git $ git init j416_test
    Initialized empty Git repository in <blah>/git/j416_test/.git
    git $ cd j416_test
    j416_test (master) $ git commit --allow-empty -m "Initial commit"
    [master (root-commit) 611c451] Initial commit
    j416_test (master) $ touch file1 file2
    j416_test (master) $ git add file1 file2
    j416_test (master) $ git commit -m "Initial content"
    [master 39f5b84] Initial content
    0 files changed, 0 insertions(+), 0 deletions(-)
    create mode 100644 file1
    create mode 100644 file2
    j416_test (master) $ git branch feature
    j416_test (master) $ echo A1 > file1
    j416_test (master) $ echo A1 > file2
    j416_test (master) $ git commit -am "A1"
    [master 1683701] A1
    2 files changed, 2 insertions(+), 0 deletions(-)
    j416_test (master) $ git checkout feature
    Switched to branch 'feature'
    j416_test (feature) $ echo B1 > file1
    j416_test (feature) $ git commit -am "B1"
    [feature 67129fc] B1
    1 files changed, 1 insertions(+), 0 deletions(-)
    j416_test (feature) $ echo B2 > file2
    j416_test (feature) $ git commit -am "B2"
    [feature dcd3ded] B2
    1 files changed, 1 insertions(+), 0 deletions(-)
    j416_test (feature) $ git checkout master
    Switched to branch 'master'
    j416_test (master) $ git merge --no-ff feature
    Auto-merging file1
    CONFLICT (content): Merge conflict in file1
    Auto-merging file2
    CONFLICT (content): Merge conflict in file2
    Automatic merge failed; fix conflicts and then commit the result.
    j416_test (master|MERGING) $ git st
    # On branch master
    # Unmerged paths:
    # (use "git add/rm <file>..." as appropriate to mark resolution)
    #
    # both modified: file1
    # both modified: file2
    #
    no changes added to commit (use "git add" and/or "git commit -a")
    j416_test (master|MERGING) $
    # As you can see, two conflicts A1/B1 and A1/B2
    70 changes: 70 additions & 0 deletions 2_merge_from_master.sh
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,70 @@
    git $ git init j416_test
    Initialized empty Git repository in <blah>/j416_test/.git
    git $ cd j416_test/
    j416_test (master) $ git commit --allow-empty -m "Initial commit"
    [master (root-commit) 6439bf7] Initial commit
    j416_test (master) $ touch file1 file2
    j416_test (master) $ git add file1 file2
    j416_test (master) $ git commit -m "Initial content"
    [master 91de979] Initial content
    0 files changed, 0 insertions(+), 0 deletions(-)
    create mode 100644 file1
    create mode 100644 file2
    j416_test (master) $ git branch feature
    j416_test (master) $ echo A1 > file1
    j416_test (master) $ echo A1 > file2
    j416_test (master) $ git commit -am "A1"
    [master 21e8834] A1
    2 files changed, 2 insertions(+), 0 deletions(-)
    j416_test (master) $ git checkout feature
    Switched to branch 'feature'
    j416_test (feature) $ echo B1 > file1
    j416_test (feature) $ git commit -am "B1"
    [feature edbee1c] B1
    1 files changed, 1 insertions(+), 0 deletions(-)
    j416_test (feature) $ git merge master
    Auto-merging file1
    CONFLICT (content): Merge conflict in file1
    Automatic merge failed; fix conflicts and then commit the result.
    j416_test (feature|MERGING) $ git st
    # On branch feature
    # Changes to be committed:
    #
    # modified: file2
    #
    # Unmerged paths:
    # (use "git add/rm <file>..." as appropriate to mark resolution)
    #
    # both modified: file1
    #
    j416_test (feature|MERGING) $ echo B1 > file1 # by way of conflict resolution
    j416_test (feature|MERGING) $ git add file1
    j416_test (feature|MERGING) $ git commit
    [feature 88505f2] Merge branch 'master' into feature
    j416_test (feature) $ echo B2 > file2
    j416_test (feature) $ git commit -am "B2"
    [feature 4589026] B2
    1 files changed, 1 insertions(+), 1 deletions(-)
    j416_test (feature) $ git checkout master
    Switched to branch 'master'
    j416_test (master) $ git merge --no-ff feature
    Updating 21e8834..4589026
    Fast-forward
    file1 | 2 +-
    file2 | 2 +-
    2 files changed, 2 insertions(+), 2 deletions(-)
    j416_test (master) $ cat file1 file2
    B1 B2
    j416_test (master) $ git log --graph --decorate --oneline --all
    * 85a0b70 (HEAD, master) Merge branch 'feature'
    |\
    | * 4589026 (feature) B2
    | * 88505f2 Merge branch 'master' into feature
    | |\
    | |/
    |/|
    * | 21e8834 A1
    | * edbee1c B1
    |/
    * 91de979 Initial content
    * 6439bf7 Initial commit
    86 changes: 86 additions & 0 deletions 3_final_rebase.sh
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,86 @@
    git $ git init j416_test
    Initialized empty Git repository in <blah>/j416_test/.git
    git $ cd j416_test
    j416_test (master) $ git commit --allow-empty -m "Initial commit"
    [master (root-commit) 1eff68a] Initial commit
    j416_test (master) $ touch file1 file2
    j416_test (master) $ git add file1 file2
    j416_test (master) $ git commit -m "Initial content"
    [master 4773ffe] Initial content
    0 files changed, 0 insertions(+), 0 deletions(-)
    create mode 100644 file1
    create mode 100644 file2
    j416_test (master) $ git branch feature
    j416_test (master) $ echo A1 > file1
    j416_test (master) $ echo A1 > file2
    j416_test (master) $ git commit -am "A1"
    [master f19dec2] A1
    2 files changed, 2 insertions(+), 0 deletions(-)
    j416_test (master) $ git checkout feature
    Switched to branch 'feature'
    j416_test (feature) $ echo B1 > file1
    j416_test (feature) $ git commit -am "B1"
    [feature ca56202] B1
    1 files changed, 1 insertions(+), 0 deletions(-)
    j416_test (feature) $ git merge master
    Auto-merging file1
    CONFLICT (content): Merge conflict in file1
    Automatic merge failed; fix conflicts and then commit the result.
    j416_test (feature|MERGING) $ echo B1 > file1
    j416_test (feature|MERGING) $ git add file1
    j416_test (feature|MERGING) $ git commit
    [feature d2d7bfc] Merge branch 'master' into feature
    j416_test (feature) $ echo B2 > file2
    j416_test (feature) $ git commit -am "B2"
    [feature d8e987e] B2
    1 files changed, 1 insertions(+), 1 deletions(-)
    j416_test (feature) $ git rebase master
    First, rewinding head to replay your work on top of it...
    Applying: B1
    Using index info to reconstruct a base tree...
    Falling back to patching base and 3-way merge...
    Auto-merging file1
    CONFLICT (content): Merge conflict in file1
    Failed to merge in the changes.
    Patch failed at 0001 B1

    When you have resolved this problem run "git rebase --continue".
    If you would prefer to skip this patch, instead run "git rebase --skip".
    To restore the original branch and stop rebasing run "git rebase --abort".

    j416_test ((f19dec2...)|REBASE) $ git st
    # Not currently on any branch.
    # Unmerged paths:
    # (use "git reset HEAD <file>..." to unstage)
    # (use "git add/rm <file>..." as appropriate to mark resolution)
    #
    # both modified: file1
    #
    no changes added to commit (use "git add" and/or "git commit -a")
    j416_test ((f19dec2...)|REBASE) $ cat file1
    <<<<<<< HEAD
    A1
    =======
    B1
    >>>>>>> B1
    j416_test ((f19dec2...)|REBASE) $ echo B1 > file1
    j416_test ((f19dec2...)|REBASE) $ git add file1
    j416_test ((f19dec2...)|REBASE) $ git rebase --continue
    Applying: B1
    Applying: B2
    j416_test (feature) $ git checkout master
    Switched to branch 'master'
    j416_test (master) $ git merge --no-ff feature
    Merge made by recursive.
    file1 | 2 +-
    file2 | 2 +-
    2 files changed, 2 insertions(+), 2 deletions(-)
    j416_test (master) $ git log --graph --oneline --decorate --all
    * 9e0d666 (HEAD, master) Merge branch 'feature'
    |\
    | * f747817 (feature) B2
    | * 499ba9a B1
    |/
    * f19dec2 A1
    * 4773ffe Initial content
    * 1eff68a Initial commit
    92 changes: 92 additions & 0 deletions 4_final_rebase_rerere.sh
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,92 @@
    git $ git init j416_test
    Initialized empty Git repository in <blah>/j416_test/.git
    git $ cd j416_test
    j416_test (master) $ git config rerere.enabled true
    j416_test (master) $ git config rerere.autoupdate true
    j416_test (master) $ git commit --allow-empty -m "Initial commit"
    [master (root-commit) d63ab0c] Initial commit
    j416_test (master) $ touch file1 file2
    j416_test (master) $ git add file1 file2
    j416_test (master) $ git commit -m "Initial content"
    [master 3c7afb5] Initial content
    0 files changed, 0 insertions(+), 0 deletions(-)
    create mode 100644 file1
    create mode 100644 file2
    j416_test (master) $ git branch feature
    j416_test (master) $ echo A1 > file1
    j416_test (master) $ echo A1 > file2
    j416_test (master) $ git commit -am "A1"
    [master 090ff8d] A1
    2 files changed, 2 insertions(+), 0 deletions(-)
    j416_test (master) $ git checkout feature
    Switched to branch 'feature'
    j416_test (feature) $ echo B1 > file1
    j416_test (feature) $ git commit -am "B1"
    [feature 810f60d] B1
    1 files changed, 1 insertions(+), 0 deletions(-)
    j416_test (feature) $ git merge master
    Auto-merging file1
    CONFLICT (content): Merge conflict in file1
    Automatic merge failed; fix conflicts and then commit the result.
    j416_test (feature|MERGING) $ git st
    # On branch feature
    # Changes to be committed:
    #
    # modified: file2
    #
    # Unmerged paths:
    # (use "git add/rm <file>..." as appropriate to mark resolution)
    #
    # both modified: file1
    #
    j416_test (feature|MERGING) $ echo B1 > file1 # by way of conflict resolution
    j416_test (feature|MERGING) $ git add file1
    j416_test (feature|MERGING) $ git commit
    [feature 87d1c9a] Merge branch 'master' into feature
    j416_test (feature) $ ls .git/
    hooks/ info/ logs/ objects/ refs/ rr-cache/
    j416_test (feature) $ echo B2 > file2
    j416_test (feature) $ git commit -am "B2"
    [feature 41bf61a] B2
    1 files changed, 1 insertions(+), 1 deletions(-)
    j416_test (feature) $ git rebase master
    First, rewinding head to replay your work on top of it...
    Applying: B1
    Using index info to reconstruct a base tree...
    Falling back to patching base and 3-way merge...
    Auto-merging file1
    CONFLICT (content): Merge conflict in file1
    Staged 'file1' using previous resolution.
    Failed to merge in the changes.
    Patch failed at 0001 B1

    When you have resolved this problem run "git rebase --continue".
    If you would prefer to skip this patch, instead run "git rebase --skip".
    To restore the original branch and stop rebasing run "git rebase --abort".

    j416_test ((090ff8d...)|REBASE) $ git st
    # Not currently on any branch.
    # Changes to be committed:
    # (use "git reset HEAD <file>..." to unstage)
    #
    # modified: file1
    #
    j416_test ((090ff8d...)|REBASE) $ git rebase --continue
    Applying: B1
    Applying: B2
    j416_test (feature) $ git checkout master
    Switched to branch 'master'
    j416_test (master) $ git merge --no-ff feature
    Merge made by recursive.
    file1 | 2 +-
    file2 | 2 +-
    2 files changed, 2 insertions(+), 2 deletions(-)
    j416_test (master) $ git log --graph --decorate --oneline --all
    * 8aabdfc (HEAD, master) Merge branch 'feature'
    |\
    | * 727a3df (feature) B2
    | * d8f3d52 B1
    |/
    * 090ff8d A1
    * 3c7afb5 Initial content
    * d63ab0c Initial commit