Fix the downgrade function of cfgupgrade
[ganeti-local] / devel / review
1 #!/bin/bash
2
3 # Copyright (C) 2009 Google Inc.
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 # General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 # 02110-1301, USA.
19
20 # To set user mappings, use this command:
21 #   git config gnt-review.johndoe 'John Doe <johndoe@example.com>'
22
23 # To disable strict mode (enabled by default):
24 #   git config gnt-review.strict false
25
26 # To enable strict mode:
27 #   git config gnt-review.strict true
28
29 set -e
30
31 # Get absolute path to myself
32 me_plain="$0"
33 me=$(readlink -f "$me_plain")
34
35 add_reviewed_by() {
36   local msgfile="$1"
37
38   grep -q '^Reviewed-by: ' "$msgfile" && return
39
40   perl -i -e '
41   my $reviewer = $ENV{"REVIEWER"};
42   defined($reviewer) or $reviewer = "";
43   my $sob = 0;
44   while (<>) {
45     if ($sob == 0 and m/^Signed-off-by:/) {
46       $sob = 1;
47
48     } elsif ($sob == 1 and not m/^Signed-off-by:/) {
49       print "Reviewed-by: $reviewer\n";
50       $sob = -1;
51     }
52
53     print;
54   }
55
56   if ($sob == 1) {
57     print "Reviewed-by: $reviewer\n";
58   }
59   ' "$msgfile"
60 }
61
62 replace_users() {
63   local msgfile="$1"
64
65   if perl -i -e '
66   use strict;
67   use warnings;
68
69   my $error = 0;
70   my $strict;
71
72   sub map_username {
73     my ($name) = @_;
74
75     return $name unless $name;
76
77     my @cmd = ("git", "config", "--get", "gnt-review.$name");
78
79     open(my $fh, "-|", @cmd) or die "Command \"@cmd\" failed: $!";
80     my $output = do { local $/ = undef; <$fh> };
81     close($fh);
82
83     if ($? == 0) {
84       chomp $output;
85       $output =~ s/\s+/ /;
86       return $output;
87     }
88
89     unless (defined $strict) {
90       @cmd = ("git", "config", "--get", "--bool", "gnt-review.strict");
91
92       open($fh, "-|", @cmd) or die "Command \"@cmd\" failed: $!";
93       $output = do { local $/ = undef; <$fh> };
94       close($fh);
95
96       $strict = ($? != 0 or not $output or $output !~ m/^false$/);
97     }
98
99     if ($strict and $name !~ m/^.+<.+\@.+>$/) {
100       $error = 1;
101     }
102
103     return $name;
104   }
105
106   while (<>) {
107     if (m/^Reviewed-by:(.*)$/) {
108       my @names = grep {
109         # Ignore empty entries
110         !/^$/
111       } map {
112         # Normalize whitespace
113         $_ =~ s/(^\s+|\s+$)//g;
114         $_ =~ s/\s+/ /g;
115
116         # Map names
117         $_ = map_username($_);
118
119         $_;
120       } split(m/,/, $1);
121
122       # Get unique names
123       my %saw;
124       @names = grep(!$saw{$_}++, @names);
125       undef %saw;
126
127       foreach (sort @names) {
128         print "Reviewed-by: $_\n";
129       }
130     } else {
131       print;
132     }
133   }
134
135   exit($error? 33 : 0);
136   ' "$msgfile"
137   then
138     :
139   else
140     [[ "$?" == 33 ]] && return 1
141     exit 1
142   fi
143
144   if ! grep -q '^Reviewed-by: ' "$msgfile"
145   then
146     echo 'Missing Reviewed-by: line' >&2
147     sleep 1
148     return 1
149   fi
150
151   return 0
152 }
153
154 run_editor() {
155   local filename="$1"
156   local editor=${EDITOR:-vi}
157   local args
158
159   case "$(basename "$editor")" in
160     vi* | *vim)
161       # Start edit mode at Reviewed-by: line
162       args='+/^Reviewed-by: +nohlsearch +startinsert!'
163     ;;
164     *)
165       args=
166     ;;
167   esac
168
169   $editor $args "$filename"
170 }
171
172 commit_editor() {
173   local msgfile="$1"
174
175   local tmpf=$(mktemp)
176   trap "rm -f $tmpf" EXIT
177
178   cp "$msgfile" "$tmpf"
179
180   while :
181   do
182     add_reviewed_by "$tmpf"
183
184     run_editor "$tmpf"
185
186     replace_users "$tmpf" && break
187   done
188
189   cp "$tmpf" "$msgfile"
190 }
191
192 copy_commit() {
193   local rev="$1" target_branch="$2"
194
195   echo "Copying commit $rev ..."
196
197   git cherry-pick -n "$rev"
198   GIT_EDITOR="$me --commit-editor \"\$@\"" git commit -c "$rev" -s
199 }
200
201 usage() {
202   echo "Usage: $me_plain [from..to] <target-branch>" >&2
203   echo "  If not passed from..to defaults to target-branch..HEAD" >&2
204   exit 1
205 }
206
207 main() {
208   local range target_branch
209
210   case "$#" in
211   1)
212     target_branch="$1"
213     range="$target_branch..$(git rev-parse HEAD)"
214   ;;
215   2)
216     range="$1"
217     target_branch="$2"
218     if [[ "$range" != *..* ]]; then
219       usage
220     fi
221   ;;
222   *)
223     usage
224   ;;
225   esac
226
227   git checkout "$target_branch"
228   local old_head=$(git rev-parse HEAD)
229
230   for rev in $(git rev-list --reverse "$range")
231   do
232     copy_commit "$rev"
233   done
234
235   git log "$old_head..$target_branch"
236 }
237
238 if [[ "$1" == --commit-editor ]]
239 then
240   shift
241   commit_editor "$@"
242 else
243   main "$@"
244 fi