My friend Ævar Arnfjörð Bjarmason (a name you may have seen in the commit logs of git itself) recently showed me the joys of "git merge --no-ff" to combine stacks of commits in a way that communicates something about the logical grouping of work, but in a way which doesn't make for irritating history representation nor super-irritating bisecting.

The essence is that we try to keep a linear history by heavy use of rebase, but sometimes want to have groups of commits treated as a unit. Creating these "extra merges" communicates the grouping. I've tried to write up an illustration of the use.

Assume a git repo:

/tmp$ mkdir logical-chunks
/tmp$ cd logical-chunks/
/tmp/logical-chunks$ git init
Initialized empty Git repository in /tmp/logical-chunks/.git/
/tmp/logical-chunks$ git commit --allow-empty -m'init'
[master (root-commit) b5d8f2f] init
/tmp/logical-chunks$ vim foo
/tmp/logical-chunks$ git add foo
/tmp/logical-chunks$ git commit -m 'v1'
[master 0c92e29] v1
 1 files changed, 9 insertions(+), 0 deletions(-)
 create mode 100644 foo
/tmp/logical-chunks$

create a local clone:

/tmp$ git clone logical-chunks logical-chunks-downstream Cloning into logical-chunks-downstream... done. /tmp$ cd logical-chunks-downstream/

Make some changes:

/tmp/logical-chunks-downstream$ vim foo
/tmp/logical-chunks-downstream$ git add foo
/tmp/logical-chunks-downstream$ git commit -m 'downstream change 2'
[master 4794e53] downstream change 2
 1 files changed, 1 insertions(+), 1 deletions(-)
/tmp/logical-chunks-downstream$

Assume upstream changes:

/tmp/logical-chunks$ vim foo
/tmp/logical-chunks$ git commit -am 'upstream change 2'
[master 0a49586] upstream change 2
 1 files changed, 1 insertions(+), 1 deletions(-)

Make more local changes:

/tmp/logical-chunks-downstream$ vim foo
/tmp/logical-chunks-downstream$ git commit -am 'downstream change 3'
[master 8530f6d] downstream change 3
 1 files changed, 1 insertions(+), 1 deletions(-)
/tmp/logical-chunks-downstream$

Pull in any upstream changes:
/tmp/logical-chunks-downstream$ git pull --rebase remote: Counting objects: 5, done. remote: Compressing objects: 100% (2/2), done. remote: Total 3 (delta 0), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. From /tmp/logical-chunks 0c92e29..0a49586 master -> origin/master First, rewinding head to replay your work on top of it... Applying: downstream change 2 Applying: downstream change 3 /tmp/logical-chunks-downstream$

Before pushing upstream, we want to create a single commit which logically groups our work:

/tmp/logical-chunks-downstream$ git checkout -b logical-chunks Switched to a new branch 'logical-chunks' /tmp/logical-chunks-downstream$

The branch 'logical-chunks' has our work rebased, so next we switch back to what upstream looks like:
/tmp/logical-chunks-downstream$ git checkout master Switched to branch 'master' Your branch is ahead of 'origin/master' by 2 commits. /tmp/logical-chunks-downstream$ git reset --hard origin/master HEAD is now at 0a49586 upstream change 2 /tmp/logical-chunks-downstream$

Then we can merge in all of our changes with a nice, easy to revert, merge commit:

/tmp/logical-chunks-downstream$ git merge --no-ff logical-chunks Merge made by recursive. foo | 4 ++-- 1 files changed, 2 insertions(+), 2 deletions(-) /tmp/logical-chunks-downstream$

Which is easy to examine:
/tmp/logical-chunks-downstream$ git log --pretty=format:\"%h %ad | %s%d [%an]\" --graph --date=short * 785742b 2012-10-19 | Merge branch 'logical-chunks' (HEAD, master) [Eric Herman] |\ | * df9d5e2 2012-10-19 | downstream change 3 (logical-chunks) [Eric Herman] | * fc6c9a4 2012-10-19 | downstream change 2 [Eric Herman] |/ * 0a49586 2012-10-19 | upstream change 2 (origin/master, origin/HEAD) [Eric Herman] * 0c92e29 2012-10-19 | v1 [Eric Herman] * b5d8f2f 2012-10-19 | init [Eric Herman] /tmp/logical-chunks-downstream$


And easy to see where we may wish to revert to. The "git show" command tells us that the first commit is 0a49586 and the second is df9d5e2 The "git log" above tells us that the first is the older/upstream parent.
/tmp/logical-chunks-downstream$ git show commit 785742bf341b0d72c44540b0938af1c63d29e832 Merge: 0a49586 df9d5e2 Author: Eric Herman Date: Fri Oct 19 02:03:56 2012 -0700
Merge branch 'logical-chunks'
/tmp/logical-chunks-downstream$

To revert, we use the index of the desired commit from the merge. The index is not zero offset, we want the first parent:
/tmp/logical-chunks-downstream$ git revert --mainline 1 785742b Finished one revert. [master 1c19cb1] Revert "Merge branch 'logical-chunks'" 1 files changed, 2 insertions(+), 2 deletions(-) /tmp/logical-chunks-downstream$

And again easy to examine:
/tmp/logical-chunks-downstream$ git log --pretty=format:\"%h %ad | %s%d [%an]\" --graph --date=short * 1c19cb1 2012-10-19 | Revert "Merge branch 'logical-chunks'" (HEAD, master) [Eric Herman] * 785742b 2012-10-19 | Merge branch 'logical-chunks' [Eric Herman] |\ | * df9d5e2 2012-10-19 | downstream change 3 (logical-chunks) [Eric Herman] | * fc6c9a4 2012-10-19 | downstream change 2 [Eric Herman] |/ * 0a49586 2012-10-19 | upstream change 2 (origin/master, origin/HEAD) [Eric Herman] * 0c92e29 2012-10-19 | v1 [Eric Herman] * b5d8f2f 2012-10-19 | init [Eric Herman] /tmp/logical-chunks-downstream$