Sean Allred
Sean Allred

Reputation: 3658

Convenience script for renaming commit authors with git-filter-branch

For convenience, everything in this post is runnable as a Bash command.

I have the following script based off of GitHub's own:

cat <<EOF > git-unite
#!/usr/bin/env bash

# Usage:
#
# git-unite "User Name" "[email protected]" \
#           "[email protected]" \
#           "[email protected]" \
#           ...

name="$1"
email="$2"

git config user.name "$name"
git config user.email "$email"

shift 2

for old_email in $*; do
echo "changing $old_email"
git filter-branch --force --env-filter '
if [ "$GIT_COMMITTER_EMAIL" = "$(echo $old_email)" ]
then
  export GIT_COMMITTER_NAME="$(echo $name)"
  export GIT_COMMITTER_EMAIL="$(echo $email)"
fi
if [ "$GIT_AUTHOR_EMAIL" = "$(echo $old_email)" ]
then
  export GIT_AUTHOR_NAME="$(echo $name)"
  export GIT_AUTHOR_EMAIL="$(echo $email)"
fi
' --tag-name-filter cat -- --branches --tags
done
EOF
chmod u+x git-unite

Yet, when I run the script on a test repository I've set up:

git clone https://gist.github.com/dc896ccd9a272a126436.git
cd dc896ccd9a272a126436
git-unite "Test Author" "[email protected]" "hehe2" "hehe"

nothing is changed. What is the trouble?

changing hehe2
Rewrite be8d35aca918caaa86035ab8f8011d5ff6131939 (3/3)
WARNING: Ref 'refs/heads/master' is unchanged
changing hehe
Rewrite be8d35aca918caaa86035ab8f8011d5ff6131939 (3/3)
WARNING: Ref 'refs/heads/master' is unchanged

Using exports, the problem can be solved. Is there any way to do this without exporting these variables?

Upvotes: 1

Views: 820

Answers (1)

NoDataFound
NoDataFound

Reputation: 11959

If you need to "inject" the $name and $email, you can export it before git filter-branch, or you can generate a bash script using printf.

Notice: the $(echo $old_email) does the same result than using "$old_email" and you could fork a process for nothing (well, if bash fork when you use builtins).

You can also use printf: the idea is to use the %q to dump the different variable in a quoted form (suitable for use in a bash script, or in eval). You also gain the advantage to split the script that effectively change the user name/email from the one that run the git command.

#action.bash:
declare -r _NEW_NAME="%q"
declare -r _NEW_EMAIL="%q"
declare -r _OLD_EMAIL="%q"
if [ "$GIT_COMMITTER_EMAIL" = %v ]
then
  export GIT_COMMITTER_NAME="${_NEW_NAME}"
  export GIT_COMMITTER_EMAIL="${_NEW_EMAIL}"
fi
if [ "$GIT_AUTHOR_EMAIL" = "${_OLD_EMAIL}" ]
then
  export GIT_AUTHOR_NAME="${_NEW_NAME}"
  export GIT_AUTHOR_EMAIL="${_NEW_EMAIL}"
fi

And in your script:

git filter-branch --force --env-filter $(printf "$(<action.bash)" \
   "$name" "$email" "$old_email") --tag-name-filter cat -- --branches --tags

Notice:

  1. $(<foobar) does the same work that cat foobar does. When you use cat, you might however except bash to create a sub-process instead of just reading the file foobar.
  2. I truncated the git filter-branch (the \) line to avoid horizontal scrollbars.

This example is fine, but after thinking about it a little, I think you should not do it like that, but in a more bash way:

#action.bash:
declare -r _NEW_NAME="$1"
declare -r _NEW_EMAIL="$2"
declare -r _OLD_EMAIL="$3"
if [ "$GIT_COMMITTER_EMAIL" = %v ]
then
  export GIT_COMMITTER_NAME="${_NEW_NAME}"
  export GIT_COMMITTER_EMAIL="${_NEW_EMAIL}"
fi
if [ "$GIT_AUTHOR_EMAIL" = "${_OLD_EMAIL}" ]
then
  export GIT_AUTHOR_NAME="${_NEW_NAME}"
  export GIT_AUTHOR_EMAIL="${_NEW_EMAIL}"
fi

And instead:

git filter-branch --force --env-filter "$(printf 'action.bash "%q" "%q" "%q"' \
   "$name" "$email" "$old_email")" --tag-name-filter cat -- --branches --tags

That way, even if the script does nothing at all, it would still work if you needed it in a static way:

git filter-branch --force --env-filter 'action.bash name email old_email' \
  --tag-name-filter cat -- --branches --tags

Upvotes: 3

Related Questions