4

Question says it all.

I want to give my users some privacy by obfuscating their real email addresses in commits by aliases of my own. Is there a hook that can help me do this?

svick
  • 236,525
  • 50
  • 385
  • 514
Alexandre Nizoux
  • 196
  • 2
  • 17

3 Answers3

7

You could just get them to change the user.email property in their local repository.

git config user.email email@server.com
Abizern
  • 146,289
  • 39
  • 203
  • 257
5

No matter what else happens, if you change something—even a single character in the user name and/or email—in some commit, you get a new, different commit. (This is because the commit ID consists of a crypographic checksum of the contents of the commit. So, OK, if you can break the cryptography, you can come up with two different commits that would have the same ID. But then you've completely broken git, and probably won a Turing award or a Nobel prize too. :-) )

The "working hook" you've linked to in comments on another answer is probably your best bet. This problem boils down to one thing: you can't make any changes to a published commit (without causing problems for consumers of that publication), but you can make any changes you want to a private commit as long as you "know what you're doing". Using git commit --amend --author "$user <$email>" -C HEAD on a per-commit basis, as they go in to your copy of the repo, guarantees that you replace an unpublished commit with a new, slightly different unpublished commit. (I assume you have put this in the post-commit hook.)

I'm not sure which part you're unhappy with, maybe the [ -n "$richo_git_rewrite" ] && exit 0? That's a reasonably clever method of detecting recursion. An alternative is to skip the recursion detection, and instead compare the existing user and email in the commit with the desired ones.

Here's a script that does that (except I used the env variable SWITCHY=true to do my testing):

#! /bin/sh
# first, pick which git config variables to get
if ${SWITCHY-false}; then
    config=user.work
else
    config=user
fi
# next, find out if they're set
can_rewrite=false
target_author=$(git config --get $config.name) &&
    target_email=$(git config --get $config.email) &&
    can_rewrite=true
# If they ARE set, we can "rewrite" (replace) the commit;
# if not, we can't.  Just because we can, though, does not
# mean we should.  Find out if the current author and email
# differ from the desired ones.
if $can_rewrite; then
    current_author=$(git log --pretty=format:%an HEAD -n 1)
    current_email=$(git log --pretty=format:%ae HEAD -n 1)
    if [ "$current_author" != "$target_author" -o \
          "$current_email" != "$target_email" ]; then
        # may want --allow-empty here, if you're allowing empty commits
        # at all, otherwise empty ones don't get the rewrite done
        git commit --amend --author "$target_author <$target_email>" -C HEAD
    fi
fi

Note: you'll need to set this same hook in hooks/post-merge, to get amended merges with the updated name-and-email. And of course you can simplify the hook a bit (don't need an actual can_rewrite variable, just do the two git config get ops with &&s and keep going with another &&). It would also make sense to have more than just user vs user.work, maybe user vs user.ModeA vs user.ModeB, etc., and you can drive it off whatever tests you like (env variables, presence or absence of commands, etc).


OK, per comments, here's a pre-commit hook. Unfortunately it doesn't work with git merge (the pre-commit hook runs and complains and then the merge commit goes in).
#! /bin/sh
fatal()
{
    echo "$@" 1>&2
    exit 1
}
# pick which git config variables to get
if ${SWITCHY-false}; then
    config=user.work
else
    config=user
fi
# tn, te = target author name/email
# an, ae = what git will use for author name/email
tn=$(git config --get $config.name) || exit 0
te=$(git config --get $config.email) || exit 0

an=${GIT_AUTHOR_NAME-$(git config --get user.name)} ||
    fatal "no author name set"
ae=${GIT_AUTHOR_EMAIL-$(git config --get user.email)} ||
    fatal "no author email set"
[ "$an" = "$tn" -a "$ae" = "$te" ] ||
    fatal "git will use author $an <$ae>
but you want them as $tn <$te>
fix your environment variables and try again"

You could combine this pre-commit hook with a post-merge rewrite-the-commit hook (eww :-) ). I did not try a conflicted commit but presumably the pre-commit hook would catch conflicted merges that require you to do your own commit.

(It's also worth noting that this pre-commit hook does not look at the working tree or index, so it does not have the usual "checking the work tree but committing the index" flaw.)

torek
  • 448,244
  • 59
  • 642
  • 775
  • My issue is with the "commit, test if we need to rewrite, rewrite" pattern. I'd like to just write the commit out as the correct user in the first place, my workflow involves a lot of rebasing and I'd wager that what I have now will bite me in the ass sooner or later. After looking into it further I'd say I'll have to implement it inside git and beg to merge it into the next release. Thanks! – richo Mar 23 '12 at 00:53
  • You can't *quite* do that, but if you like, you could have a pre-commit hook that checks whether "what git will set" is "what I want it to set". Have the filter grab `$GIT_AUTHOR_NAME` and `$GIT_AUTHOR_EMAIL`, use git config get if they're not set, and then compare them (a la my example post-commit hook) to the "desired" values. If they don't match, exit 1, stopping the commit. (And of course, put this on pretty much pre-everthing.) – torek Mar 23 '12 at 01:02
  • I hadn't considered that option, just bail if it's wrong.Thanks! – richo Mar 23 '12 at 01:30
  • Hm... I've tried setting the user.name via `git config user.name "something"` in a a pre-commit hook, but this doesn't work for the current commit. The name of the *next* commit is change. I guess GIT is reading the config at the very beginning once, then running the pre-commit hook and then doing everything else. Setting the env variable GIT_AUTHOR_NAME doesn't help much either. – lumbric Mar 08 '18 at 15:53
  • @lumbric: that's why the original hack makes a *new* commit (see https://gist.github.com/richo/1d7b0437ebdaa9834b61). And yes, setting the env var in the hook won't do anything as env vars pass "downward" (from parents to children) and never upwards: if Git is running the hook as a sub-process, the sub-process cannot affect the parent Git process. – torek Mar 08 '18 at 15:59
1

http://progit.org/book/ch6-4.html explains how to rewrite history. Near the bottom, there ist this command

git filter-branch --commit-filter '
    if [ "$GIT_AUTHOR_EMAIL" = "schacon@localhost" ];
    then
            GIT_AUTHOR_NAME="Scott Chacon";
            GIT_AUTHOR_EMAIL="schacon@example.com";
            git commit-tree "$@";
    else
            git commit-tree "$@";
    fi' HEAD

which changes all commits from schacon@localhost to be from schacon@example.com.

Caveat: this changes ALL commit-ids from the earliest email-change onward.

plaugg
  • 420
  • 3
  • 11
  • I don't really want to rewrite history (particularly as I like to push during the day). At commit time I just want a hook to inspect some environment and make a decision which author to put on there. – richo Mar 20 '12 at 22:49
  • You ARE going to rewrite history, as the commits were you want to change the author are in your past. The filter-branch command changes only commits with the original email, thus you are not going to change parts of your history that you already corrected. – plaugg Mar 22 '12 at 08:18
  • The point is that if I wrote them in my own time then they are not "bad" commits. Filtering on the email is not the correct approach. I have a working hook but I'm unhappy with it – richo Mar 22 '12 at 09:39
  • Also, the point is that they're changed as I'm committing them in a perfect world. Atm setting GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL conditionally when I'm at work is a kludge that's getting me by. – richo Mar 22 '12 at 09:47