Changing the Author of a Single Tag in git

My team recently moved from Subversion to git, a migration we have been considering and planning for the past several months. We tripped over many stumbling blocks on our way; I intend to write about many of those in the coming months.

The day after the migration, we found ourselves needing to create a support branch1 for one of our components. We do this by branching from a tag and naming it as such:

1
2
git checkout -b support/1.2.x component-1.2.0
git push -u origin support/1.2.x

That should just work. However, we have quite a few pre-commit hooks in place to keep everybody working with the same set of standards. One of these requires that you're pushing only your own changes, and that your author info matches. In SVN, our tags were all created by an internal service account running our releases (srv-clubuilder), so the release point and the tag created from it were both done under that user. Well, that user doesn't exist any more, and certainly I'm not him. When my team tried to do the above, we saw this lovely error:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Counting objects: 1, done.
Writing objects: 100% (1/1), 190 bytes | 0 bytes/s, done.
Total 1 (delta 0), reused 0 (delta 0)
remote:
remote:   (c).-.(c)    (c).-.(c)    (c).-.(c)    (c).-.(c)    (c).-.(c)
remote:    / ._. \      / ._. \      / ._. \      / ._. \      / ._. \
remote:  __\( Y )/__  __\( Y )/__  __\( Y )/__  __\( Y )/__  __\( Y )/__
remote: (_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)
remote:    || E ||      || R ||      || R ||      || O ||      || R ||
remote:  _.' `-' '._  _.' `-' '._  _.' `-' '._  _.' `-' '._  _.' `-' '.
remote: (.-./`-'\.-.)(.-./`-`\.-.)(.-./`-`\.-.)(.-./`-'\.-.)(.-./`-`\.-.)
remote:  `-'     `-'  `-'     `-'  `-'     `-'  `-'     `-'  `-'     `-'
remote:
remote:
remote: Push rejected.
remote:
remote: refs/heads/support/1.2.x: bb3ac1ef7b5ef61d9b999bd7ad231803f18de440: expected committer email 'Matt.Shelton@nuance.com' but found 'srv-clubuilder@nuance.com'
remote:
remote: refs/heads/support/1.2.x: bb3ac1ef7b5ef61d9b999bd7ad231803f18de440: expected committer name 'Shelton, Matt' but found 'srv-clubuilder'
remote:
To http://Matt_Shelton@git-host.nuance-internal.com/stash/git/CLU/clu-component.git
 ! [remote rejected] support/1.2.x -> support/1.2.x (pre-receive hook declined)
error: failed to push some refs to 'http://Matt_Shelton@git-host.nuance-internal.com/stash/git/CLU/clu-component.git'

Yes. Those are teddy bears.

I tried a few things like creating an empty commit as myself and pushing that, but it didn't matter—that wasn't the commit it was complaining about. I tried amending that commit, but since I hadn't changed anything, there was nothing to amend. In order to modify the branch, I needed to change the author of the tag itself. Most search results directed me to use git-filter-branch, and while I'm comfortable re-writing my repositories history (we did just do that, more or less, during the migration), I didn't want to cause any additional confusion.

Git, however, is pretty darn powerful. Some teams, after they have been using git a while, move from a traditional lightweight tag to a richer annotated tag, which might contain additional author information or be cryptographically signed by the author. We're sticking with lightweight tags for now, but the migration process for historical tags was similar to what I wanted to do. In the end, I was able to re-write only the tag and successfully create my branch:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
$ git checkout component-1.2.0
Note: checking out 'component-1.2.0'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b new_branch_name

HEAD is now at 86f405f... [maven-release-plugin]  copy for tag component-1.2.0
$ git tag -d component-1.2.0
Deleted tag 'component-1.2.0' (was 86f405f)
$ git commit --amend --allow-empty --author="Shelton, Matt <Matt.Shelton@nuance.com"
[detached HEAD 37a8015] [maven-release-plugin]  copy for tag component-1.2.0
 Date: Thu Feb 20 19:36:40 2014 +0000
$ export GIT_COMMITTER_DATE="$(git show --format=%aD | head -1)"; git tag component-1.2.0
$ git push origin :refs/tags/component-1.2.0 # Push that tag ref to the origin as re-written
To http://Matt_Shelton@git-host.nuance-internal.com/stash/git/CLU/clu-component.git
 - [deleted]         component-1.2.0
$ git push --tags
Counting objects: 1, done.
Writing objects: 100% (1/1), 290 bytes | 0 bytes/s, done.
Total 1 (delta 0), reused 0 (delta 0)
To http://Matt_Shelton@git-host.nuance-internal.com/stash/git/CLU/clu-component.git
 * [new tag]         component-1.2.0 -> component-1.2.0

The result of this was exactly as I expected. When I look at the commit in the history, I now see that I am the author of this tag:

1
2
$ git log --tags --show-notes --simplify-by-decoration --pretty="format:%ai %d %s (%an <%ae>)"| grep component-1.2.0
2014-02-20 19:36:40 +0000  (tag: component-1.2.0) [maven-release-plugin]  copy for tag component-1.2.0 (Shelton, Matt <Matt.Shelton@nuance.com>)

And now I'm in business.


  1. We’re using git-flow as a branching model, but have the need to keep some releases around long-term for production support of integrations which cannot upgrade. This varies from a traditional concept of a hotfix in that we also need to be able to port features back to this release stream.

Aug 12th, 2015

Comments