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$