summaryrefslogtreecommitdiffstats
path: root/bin/git-rewrite-author
blob: 25e97bf196e91efe4a270917747d1d2c1e2735a8 (plain)
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
#!/usr/bin/perl
# Copyright (C) 2015 The Qt Company Ltd.
# Contact: http://www.qt.io/licensing/
#
# You may use this file under the terms of the 3-clause BSD license.
# See the file LICENSE from this package for details.
#

use strict;
use warnings;

use Getopt::Long;

sub usage {
    print STDERR
q{Usage: git rewrite-author --from <from> --to <to> [ -f | --force ]
    [--] [<rev-list options>...]

  Replace author and committer field in some commits.


EXAMPLE:

  You've accidentally made some commits like this:

    Author: bjones <bjones@localhost.localdomain>

  ... where it should have been:

    Author: Bob Jones <bob.jones@example.com>

  The first bad commit is a1b2c3d4...

  Fix it up by:

   $ git rewrite-author --from "bjones <bjones@localhost.localdomain>" \
      --to "Bob Jones <bob.jones@example.com>" a1b2c3d4^..HEAD

  If you omit a range of commits, this command will scan _all_ commits
  leading up to HEAD, which will take a long time for repositories with
  a large history.

  WARNING: this script will change the SHA1 of every commit including
  and following the first rewritten commit. You can't do this for commits
  you've already pushed unless you really know what you're doing!
};
}

sub parse_author
{
    my $author = shift;
    if ($author =~ /^(.*?) <([^>]+)>$/) {
        return ($1, $2);
    }
    else {
        die "Could not parse `$author'; expected something like \"Some Name <some.address\@example.com>\"";
    }
}


# Return a string single-quoted for use in shell
sub sh_squote
{
    my $out = shift;
    $out =~ s/'/'"'"'/g;
    return "'$out'";
}

# Return fragment of shell code to rewrite a specific env var
sub rewrite_env
{
    my %opts = @_;
    my $env = $opts{env};
    my $from = sh_squote($opts{from});
    my $to = sh_squote($opts{to});

    my $cmd = <<EOF
if [ "\$$env" = $from ]; then
    $env=$to;
    export $env;
    if [ \$REWROTE = 0 ]; then
        echo;
        echo Hit for \$GIT_COMMIT:;
        REWROTE=1;
    fi;
    echo "   $env:" $from '->' $to;
fi
EOF
    ;

    $cmd =~ s/\n */ /sg;

    return $cmd;
}

my $from;
my $to;
my $force;
if (!GetOptions(
    "from=s"        => \$from,
    "to=s"          => \$to,
    "f|force"       => \$force,
)) {
    usage;
    exit 2;
}

if (!$from || !$to) {
    print STDERR "Need `--from' and `--to' arguments\n";
    usage;
    exit 2;
}

my ($from_name, $from_address) = parse_author($from);
my ($to_name, $to_address)     = parse_author($to);

my $env_filter_command = "REWROTE=0; ".
    rewrite_env(
        env     =>  'GIT_AUTHOR_NAME',
        from    =>  $from_name,
        to      =>  $to_name,
    )
    . "; " .
    rewrite_env(
        env     =>  'GIT_COMMITTER_NAME',
        from    =>  $from_name,
        to      =>  $to_name,
    )
    . "; " .
    rewrite_env(
        env     =>  'GIT_AUTHOR_EMAIL',
        from    =>  $from_address,
        to      =>  $to_address,
    )
    . "; " .
    rewrite_env(
        env     =>  'GIT_COMMITTER_EMAIL',
        from    =>  $from_address,
        to      =>  $to_address,
    )
;

my @git_filter_branch = ("git", "filter-branch", "--env-filter", $env_filter_command);
if ($force) {
    push @git_filter_branch, "--force";
}
push @git_filter_branch, @ARGV;

exec @git_filter_branch;