March 10, 2012

Amending a non-HEAD commit in Git without changing commit dates

Given this history of commits to my master branch.

refs/heads/master:   A → B → C

$ git log --patch master
commit fa4e0e063a8d062038463264386eb5604ce2abfe
Author: Example User
Date: Sat Mar 10 07:47:51 2012 -0800

Third commit (C)

diff --git a/foo b/foo
index 5716ca5..8e60d72 100644
--- a/foo
+++ b/foo
@@ -1 +1 @@
-bar
+barter

commit 5aa9ba4c12d3b9ab7e878ef477f850c8b13550f7
Author: Example User
Date: Sat Mar 10 07:47:14 2012 -0800

Second commit (B)

diff --git a/bar b/bar
new file mode 100644
index 0000000..7601807
--- /dev/null
+++ b/bar
@@ -0,0 +1 @@
+baz

commit 1dc62c387c39caeb25440326729659f9ff2c9d6d
Author: Example User
Date: Sat Mar 10 07:46:25 2012 -0800

First commit (A)

diff --git a/foo b/foo
new file mode 100644
index 0000000..5716ca5
--- /dev/null
+++ b/foo
@@ -0,0 +1 @@
+bar


I had created commit C and then realized I had a typo in commit B that I wanted to fix. The commit --amend option doesn't allow editing a non-HEAD commit, and rebase --interactive alters commit dates.

Create a new temporary branch from commit B.

$ git branch temp 5aa9ba4

Checkout the new temp branch.

$ git checkout temp
Switched to branch 'temp'


Fix the relevant file(s) and add them to the index.

$ git add bar

Amend the commit.

$ git commit --amend
[temp 4168d24] Second commit (B)
1 files changed, 1 insertions(+), 0 deletions(-)
create mode 100644 bar


The history now looks like:

refs/heads/master:   A → B → C
refs/heads/temp:   A → B′

Switch back to the master branch.

$ git checkout master
Switched to branch 'master'


Remove the problematic commit (B) from the history (h/t StackOverflow).

$ git rebase --committer-date-is-author-date \
--onto 5aa9ba4^ 5aa9ba4 HEAD

Current branch HEAD is up to date, rebase forced.
First, rewinding head to replay your work on top of it...
Applying: Third commit (C)


The history now looks like:

refs/heads/master:   A → C′
refs/heads/temp:   A → B′

Re-play master's commits (in this case C′) against temp branch with the revised commit.

$ git rebase --committer-date-is-author-date temp

The history now looks like:

refs/heads/master:   A → B′ → C″
refs/heads/temp:   A → B′

Remove the temp branch as its history has been included in the revised master branch.

$ git branch --delete temp

The branch history has been altered after commit A.
$ git log --patch master
commit 61789d3254aae0a77389480724304b1f8de774a1
Author: Example User
Date: Sat Mar 10 07:47:51 2012 -0800

Third commit (C)

diff --git a/foo b/foo
index 5716ca5..8e60d72 100644
--- a/foo
+++ b/foo
@@ -1 +1 @@
-bar
+barter

commit 4168d24fa8b382062b24013bdcc5d2b923f3d816
Author: Example User
Date: Sat Mar 10 07:47:14 2012 -0800

Second commit (B)

diff --git a/bar b/bar
new file mode 100644
index 0000000..d9ce209
--- /dev/null
+++ b/bar
@@ -0,0 +1 @@
+bazaar

commit 1dc62c387c39caeb25440326729659f9ff2c9d6d
Author: Example User
Date: Sat Mar 10 07:46:25 2012 -0800

First commit (A)

diff --git a/foo b/foo
new file mode 100644
index 0000000..5716ca5
--- /dev/null
+++ b/foo
@@ -0,0 +1 @@
+bar


The commit SHA1s will change, so don't do this if you've already pushed your commits to a remote repository.

No comments:

Post a Comment