6

I have a repository where there are some (actually a lot) commits generated by a program that look like:

Adding new domains: example.com

It happened (because of a wrong configuration) that these commits were made in the name of a co-worker. Now, I want to change these commits author:

Some Bot <bot@example.com>

Github has a public script posted here to change the commits filtered by an old author. In this case, I want to change the author of commits whom messages start with a pattern: Adding new domains:. How can I do that?

Ionică Bizău
  • 109,027
  • 88
  • 289
  • 474

2 Answers2

6

You can use the --env-filter and rev-list arguments (--grep=<pattern> in this case) to limit the commits to the ones you want to modify:

#!/bin/sh

git filter-branch --env-filter '

CORRECT_NAME="Your Correct Name"
CORRECT_EMAIL="your-correct-email@example.com"

export GIT_AUTHOR_NAME="$CORRECT_NAME"
export GIT_AUTHOR_EMAIL="$CORRECT_EMAIL"

' --tag-name-filter cat -- --branches --tags --grep='Adding new domains'

You could save this script with the git- prefix and place it somewhere in your PATH, eg. /usr/local/bin/git-change-author (make sure it's executable) and run it from your repository as:

git change-author

Don't forget to git push --force since you rewrote the history.

Calin
  • 2,110
  • 1
  • 21
  • 36
  • Huh, cool. Where is `--grep` documented? I didn't see it in the `filter-branch` man page. – George Hilliard Jul 02 '15 at 14:35
  • @Calin This did something, but didn't change the author of these commits. After running the script I got `WARNING: Ref 'refs/heads/master' is unchanged`. Any ideas? – Ionică Bizău Jul 02 '15 at 14:38
  • Try without the ':' in --grep, I updated the script. – Calin Jul 02 '15 at 14:39
  • Hmm, I just tried it on a commit that had the subject you mentioned and it worked without any problems. Try `--all` instead of `--branches --tags`, although I think it won't make a difference. Maybe you already changed the author on the previous run? – Calin Jul 02 '15 at 14:48
  • 1
    @Calin Used the other script, but I'm voting you up too! :) Thanks both! – Ionică Bizău Jul 02 '15 at 14:53
  • 1
    Thanks, don't forget to `git push -f` (force) since you rewrote the history. – Calin Jul 02 '15 at 14:54
4

In this case I think you will have to use the --commit-filter filter. This is the only way to get your hands on both the commit message (from stdin) and the environment variables at the same time. The new script will have to completely replicate all the functionality of commit-tree. Fortunately, it won't look all that different from the GitHub script you linked.

First, read the entire commit message into a Bash variable:

commitmsg=$(cat)

Now get the first line of it and test whether it matches:

firstline=$(echo "$commitmsg" | head -n1 | grep "Adding new domains:")
if [ "$firstline" ]; then

We matched the commit message, so do the regular replacement, like in GitHub's script.

    export GIT_AUTHOR_NAME="$CORRECT_NAME"
    export GIT_AUTHOR_EMAIL="$CORRECT_EMAIL"
fi

Now we must call git commit-tree. The commit message is provided on stdin, just like we got it. Since our script is acting as a commit-tree surrogate, we need to pass all arguments on as well. The environment variables we (may have) modified will take care of themselves.

echo "$commitmsg" | git commit-tree "$@"

This will emit the hash onto stdout, just like we are required to do. At this point, we're done!

Here's a copy of the completed script. YMMV, of course, but I tested it and it works fine.

#!/bin/bash

git filter-branch --commit-filter '

CORRECT_NAME="Your Correct Name"
CORRECT_EMAIL="your-correct-email@example.com"

commitmsg=$(cat)

firstline=$(echo "$commitmsg" | head -n1 | grep "Adding new domains:")
if [ "$firstline" ]; then
    export GIT_AUTHOR_NAME="$CORRECT_NAME"
    export GIT_AUTHOR_EMAIL="$CORRECT_EMAIL"
fi

echo "$commitmsg" | git commit-tree "$@"

' --tag-name-filter cat -- --branches --tags
George Hilliard
  • 15,402
  • 9
  • 58
  • 96
  • Thanks! It's pretty readable what you did, however instead of ` "$firstline" = "Adding new domains: example.com"` I need to have *if commit message starts with `Adding new domains:`* -- because the domain values are different. PS: Maybe it would be better to include the whole script here. Thanks again! – Ionică Bizău Jul 02 '15 at 14:14
  • Oh gotcha, missed that. Good points. Everything should be working with the edits I made. – George Hilliard Jul 02 '15 at 14:19
  • 1
    I'm getting errors like *Rewrite 41...a9 (86/1243)git commit-tree: 56: git commit-tree: [[: not found* – Ionică Bizău Jul 02 '15 at 14:32
  • Hmm, are you using GNU bash? I haven't tried it on sh and other bashes, and I don't know what the feature matrix for `[[` is. – George Hilliard Jul 02 '15 at 14:37
  • 1
    Yeah, I'm using Ubuntu 15.04 -- `GNU bash, version 4.3.30(1)-release (x86_64-pc-linux-gnu)`. – Ionică Bizău Jul 02 '15 at 14:38
  • Ahh, here's your problem: http://stackoverflow.com/a/9666169/1830736. I bet Git is invoking `/bin/sh`. I wonder if putting a shebang `#!/bin/bash` at the top of the quoted script would help. – George Hilliard Jul 02 '15 at 14:43
  • Alternatively, you could use `firstline=$(echo "$commitmsg" | head -n1 | grep "Adding new domains:")`, then check this for emptiness with `if [ "$firstline" ]` (thereby avoiding Bashisms). – George Hilliard Jul 02 '15 at 14:49
  • 1
    This fixed the problem. It's working! Thanks a lot. Don't forget to update your script. :) – Ionică Bizău Jul 02 '15 at 14:53