Unfucking Authorship Attribution in Git
Let’s say you’ve been handed a commit from another person (hereafter referred to as “John Doe”) and you have just accidentally git-commit-ammended your own changes to that commit, effectively marking your changes as made by that person:
$ git checkout johns-branch
Branch 'johns-branch' set up to track remote branch 'johns-branch' from 'origin'.
Switched to a new branch 'johns-branch'
# Make your changes...
# git-commit-amend your changes as usual...
$ git commit --amend
[johns-branch e826647ce] contacts: update phone numbers
Author: John Doe <john@example.org>
Date: Wed Apr 15 15:19:39 2020 +0300
4 files changed, 16 insertions(+), 17 deletions(-)
# Oh crap, I forgot that the last commit is John’s, not mine.
The repo graph would look like this (assuming that John has pushed his commit to a git remote, and you haven’t force-pushed your changes yet):
$ git log --color --graph --pretty=format:'%Cgreen%h%Creset %C(yellow)(%ad)%Creset %C(bold blue)<%an>%Creset -%C(yellow)%d%Creset %s %Creset' johns-branch origin/johns-branch
* e826647ce (Wed Apr 15 15:19:39 2020 +0300) <John Doe> - (HEAD -> johns-branch) contacts: update phone numbers
| * 272223da2 (Wed Apr 15 15:19:39 2020 +0300) <John Doe> - (origin/johns-branch) contacts: update phone numbers
|/
* 75ada6111 (Wed Apr 15 10:15:12 2020 +0300) <Jane Roe> - Add footer
That commit shouldn’t have been ammended. Instead, a new commit should have been created with your name in its author.
Why Does It Matter
Although resulting changeset is the same, the authorship attribution in git has been corrupted.
Here’s what’s wrong with that:
- This makes
git blamereport an incorrect author name. Months after the change someone will be very confused to see John’s name on the changes which he couldn’t have possibly made! - This breaks KPI. Particularly, the GitHub’s green boxes charts. Giving proper credit is especially important in Open Source!
- This might violate some licenses.
- This is just ethically incorrect.
Despite all this, I believe there’s one (but only one) exclusion to the rule, where amending your changes to the commit of another person is acceptable: it’s backporting changes to the release branches. These changes are usually quite minor, and the original author would have probably done them the some way if they were targeting their commit against that release branch.
How to Unfuck
Rebase your ammended commit on top of the John’s commit:
$ git rebase origin/johns-branch
First, rewinding head to replay your work on top of it...
Applying: contacts: update phone numbers
Using index info to reconstruct a base tree...
M public/index.html
M public/contacts.html
Falling back to patching base and 3-way merge...
Auto-merging public/contacts.html
CONFLICT (content): Merge conflict in public/contacts.html
error: Failed to merge in the changes.
Patch failed at 0001 contacts: update phone numbers
hint: Use 'git am --show-current-patch' to see the failed patch
Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".
The rebase has expectedly failed, because the ammended commit has conflicting changes with the John’s commit.
Inspect the repo state:
$ git status
rebase in progress; onto 272223da2
You are currently rebasing branch 'johns-branch' on '272223da2'.
(fix conflicts and then run "git rebase --continue")
(use "git rebase --skip" to skip this patch)
(use "git rebase --abort" to check out the original branch)
Unmerged paths:
(use "git restore --staged <file>..." to unstage)
(use "git add <file>..." to mark resolution)
both modified: public/contacts.html
no changes added to commit (use "git add" and/or "git commit -a")
Reset the index to the state of your ammended commit:
$ git reset e826647ce .
Unstaged changes after reset:
M public/contacts.html
Reset unstaged changes in the working copy (which contain merge conflicts):
$ git checkout .
Updated 1 path from the index
Inspect the repo state. No unstaged changes should be left:
$ git status
rebase in progress; onto 272223da2
You are currently rebasing branch 'johns-branch' on '272223da2'.
(all conflicts fixed: run "git rebase --continue")
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: public/contacts.html
Continue the rebase:
$ git rebase --continue
Applying: contacts: update phone numbers
Reset author to yourself (and you would probably want to change the commit message):
$ git commit --amend --reset-author
[johns-branch f8d650514] contacts: update sales phone number
1 file changed, 1 insertion(+), 1 deletion(-)
All done!
Inspect the result:
$ git log --color --graph --pretty=format:'%Cgreen%h%Creset %C(yellow)(%ad)%Creset %C(bold blue)<%an>%Creset -%C(yellow)%d%Creset %s %Creset' johns-branch origin/johns-branch
* f8d650514 (Sun Apr 19 02:29:22 2020 +0300) <Kostya Esmukov> - (HEAD -> johns-branch) contacts: update sales phone number
* 272223da2 (Wed Apr 15 15:19:39 2020 +0300) <John Doe> - (origin/johns-branch) contacts: update phone numbers
* 75ada6111 (Wed Apr 15 10:15:12 2020 +0300) <Jane Roe> - Add footer
The changes have been split!
Inspect your own changes:
$ git show
Make sure the diff between the initial ammended commit and the result is empty:
$ git diff e826647ce HEAD
# should be empty!
That’s all!