| #!/bin/bash |
| |
| # Copyright (C) 2009 Google Inc. |
| # |
| # This program is free software; you can redistribute it and/or modify |
| # it under the terms of the GNU General Public License as published by |
| # the Free Software Foundation; either version 2 of the License, or |
| # (at your option) any later version. |
| # |
| # This program is distributed in the hope that it will be useful, but |
| # WITHOUT ANY WARRANTY; without even the implied warranty of |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| # General Public License for more details. |
| # |
| # You should have received a copy of the GNU General Public License |
| # along with this program; if not, write to the Free Software |
| # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| # 02110-1301, USA. |
| |
| # To set user mappings, use this command: |
| # git config gnt-review.johndoe 'John Doe <johndoe@example.com>' |
| |
| # To disable strict mode (enabled by default): |
| # git config gnt-review.strict false |
| |
| # To enable strict mode: |
| # git config gnt-review.strict true |
| |
| set -e |
| |
| # Get absolute path to myself |
| me_plain="$0" |
| me=$(readlink -f "$me_plain") |
| |
| add_reviewed_by() { |
| local msgfile="$1" |
| |
| grep -q '^Reviewed-by: ' "$msgfile" && return |
| |
| perl -i -e ' |
| my $reviewer = $ENV{"REVIEWER"}; |
| defined($reviewer) or $reviewer = ""; |
| my $sob = 0; |
| while (<>) { |
| if ($sob == 0 and m/^Signed-off-by:/) { |
| $sob = 1; |
| |
| } elsif ($sob == 1 and not m/^Signed-off-by:/) { |
| print "Reviewed-by: $reviewer\n"; |
| $sob = -1; |
| } |
| |
| print; |
| } |
| |
| if ($sob == 1) { |
| print "Reviewed-by: $reviewer\n"; |
| } |
| ' "$msgfile" |
| } |
| |
| replace_users() { |
| local msgfile="$1" |
| |
| if perl -i -e ' |
| use strict; |
| use warnings; |
| |
| my $error = 0; |
| my $strict; |
| |
| sub map_username { |
| my ($name) = @_; |
| |
| return $name unless $name; |
| |
| my @cmd = ("git", "config", "--get", "gnt-review.$name"); |
| |
| open(my $fh, "-|", @cmd) or die "Command \"@cmd\" failed: $!"; |
| my $output = do { local $/ = undef; <$fh> }; |
| close($fh); |
| |
| if ($? == 0) { |
| chomp $output; |
| $output =~ s/\s+/ /; |
| return $output; |
| } |
| |
| unless (defined $strict) { |
| @cmd = ("git", "config", "--get", "--bool", "gnt-review.strict"); |
| |
| open($fh, "-|", @cmd) or die "Command \"@cmd\" failed: $!"; |
| $output = do { local $/ = undef; <$fh> }; |
| close($fh); |
| |
| $strict = ($? != 0 or not $output or $output !~ m/^false$/); |
| } |
| |
| if ($strict and $name !~ m/^.+<.+\@.+>$/) { |
| $error = 1; |
| } |
| |
| return $name; |
| } |
| |
| while (<>) { |
| if (m/^Reviewed-by:(.*)$/) { |
| my @names = grep { |
| # Ignore empty entries |
| !/^$/ |
| } map { |
| # Normalize whitespace |
| $_ =~ s/(^\s+|\s+$)//g; |
| $_ =~ s/\s+/ /g; |
| |
| # Map names |
| $_ = map_username($_); |
| |
| $_; |
| } split(m/,/, $1); |
| |
| # Get unique names |
| my %saw; |
| @names = grep(!$saw{$_}++, @names); |
| undef %saw; |
| |
| foreach (sort @names) { |
| print "Reviewed-by: $_\n"; |
| } |
| } else { |
| print; |
| } |
| } |
| |
| exit($error? 33 : 0); |
| ' "$msgfile" |
| then |
| : |
| else |
| [[ "$?" == 33 ]] && return 1 |
| exit 1 |
| fi |
| |
| if ! grep -q '^Reviewed-by: ' "$msgfile" |
| then |
| echo 'Missing Reviewed-by: line' >&2 |
| sleep 1 |
| return 1 |
| fi |
| |
| return 0 |
| } |
| |
| run_editor() { |
| local filename="$1" |
| local editor=${EDITOR:-vi} |
| local args |
| |
| case "$(basename "$editor")" in |
| vi* | *vim) |
| # Start edit mode at Reviewed-by: line |
| args='+/^Reviewed-by: +nohlsearch +startinsert!' |
| ;; |
| *) |
| args= |
| ;; |
| esac |
| |
| $editor $args "$filename" |
| } |
| |
| commit_editor() { |
| local msgfile="$1" |
| |
| local tmpf=$(mktemp) |
| trap "rm -f $tmpf" EXIT |
| |
| cp "$msgfile" "$tmpf" |
| |
| while : |
| do |
| add_reviewed_by "$tmpf" |
| |
| run_editor "$tmpf" |
| |
| replace_users "$tmpf" && break |
| done |
| |
| cp "$tmpf" "$msgfile" |
| } |
| |
| copy_commit() { |
| local rev="$1" target_branch="$2" |
| |
| echo "Copying commit $rev ..." |
| |
| git cherry-pick -n "$rev" |
| GIT_EDITOR="$me --commit-editor \"\$@\"" git commit -c "$rev" -s |
| } |
| |
| usage() { |
| echo "Usage: $me_plain [from..to] <target-branch>" >&2 |
| echo " If not passed from..to defaults to target-branch..HEAD" >&2 |
| exit 1 |
| } |
| |
| main() { |
| local range target_branch |
| |
| case "$#" in |
| 1) |
| target_branch="$1" |
| range="$target_branch..$(git rev-parse HEAD)" |
| ;; |
| 2) |
| range="$1" |
| target_branch="$2" |
| if [[ "$range" != *..* ]]; then |
| usage |
| fi |
| ;; |
| *) |
| usage |
| ;; |
| esac |
| |
| git checkout "$target_branch" |
| local old_head=$(git rev-parse HEAD) |
| |
| for rev in $(git rev-list --reverse "$range") |
| do |
| copy_commit "$rev" |
| done |
| |
| git log "$old_head..$target_branch" |
| } |
| |
| if [[ "$1" == --commit-editor ]] |
| then |
| shift |
| commit_editor "$@" |
| else |
| main "$@" |
| fi |