]> icculus.org git repositories - divverent/nexuiz.git/blob - branch-manager
r7592 | div0 | 2009-09-02 02:38:13 -0400 (Wed, 02 Sep 2009) | 2 lines
[divverent/nexuiz.git] / branch-manager
1 #!/usr/bin/perl
2
3 use strict;
4 use warnings;
5
6 sub OutputOf($@)
7 {
8         my ($cmd, @args) = @_;
9         $cmd =~ s/#(\d)/"\$ARG$1"/g;
10         $ENV{"ARG$_"} = $args[$_-1]
11                 for 1..@args;
12
13         open my $fh, '-|', $cmd
14                 or die "popen $cmd: $!";
15
16         $ENV{"ARG$_"} = ''
17                 for 1..@args;
18
19         undef local $/;
20         my $ret = <$fh>;
21         close $fh;
22
23         return $ret;
24 }
25
26 sub StatusOf($@)
27 {
28         my ($cmd, @args) = @_;
29         $cmd =~ s/#(\d)/"\$ARG$1"/g;
30         $ENV{"ARG$_"} = $args[$_-1]
31                 for 1..@args;
32         my $ret = system $cmd;
33
34         $ENV{"ARG$_"} = ''
35                 for 1..@args;
36
37         return $ret;
38 }
39
40 my %conf = 
41 (
42         master => '',
43         revisions_applied => ''
44 );
45 my @revisions_applied = (); # array of [first, last] ranges
46
47 sub LoadSettings()
48 {
49         open my $fh, '<', ".patchsets"
50                 or return;
51         while(<$fh>)
52         {
53                 chomp;
54                 /^([^=]*?)\s*=\s*(.*?)$/s
55                         or next;
56                 die "Invalid config item: $1 (allowed: @{[sort keys %conf]})"
57                         if not exists $conf{$1};
58                 $conf{$1} = $2;
59         }
60
61         @revisions_applied = map { /^(\d+)-(\d+)$/s or die "Invalid revision spec $_"; [$1, $2]; } split /,/, $conf{revisions_applied};
62 }
63
64 sub WriteSettings()
65 {
66         $conf{revisions_applied} = join ',', map { "$_->[0]-$_->[1]" } @revisions_applied;
67
68         open my $fh, '>', ".patchsets"
69                 or die "writing settings: $!";
70         for(sort keys %conf)
71         {
72                 print $fh "$_ = $conf{$_}\n";
73         }
74 }
75
76 sub AddPatch($$)
77 {
78         my ($first, $last) = @_;
79         die "Invalid range" if $first > $last;
80
81         my @rev = sort { $a->[0] <=> $b->[0] } (@revisions_applied, [$first, $last]);
82
83         my $i = 0;
84         while($i < @rev - 1)
85         {
86                 my $a = $rev[$i][0];
87                 my $b = $rev[$i][1];
88                 my $c = $rev[$i+1][0];
89                 my $d = $rev[$i+1][1];
90
91                 if($b >= $c)
92                 {
93                         die "overlapping patch: $a-$b overlaps $c-$d";
94                 }
95                 if($b == $c - 1)
96                 {
97                         splice @rev, $i, 2, [$a, $d];
98                         next;
99                 }
100                 ++$i;
101         }
102
103         @revisions_applied = @rev;
104 }
105
106 sub RemovePatch($$)
107 {
108         my ($first, $last) = @_;
109         die "Invalid range" if $first > $last;
110
111         my @rev = sort { $a->[0] <=> $b->[0] } (@revisions_applied);
112
113         my $i = 0;
114         while($i < @rev)
115         {
116                 my $a = $rev[$i][0];
117                 my $b = $rev[$i][1];
118
119                 if($first >= $a && $last <= $b)
120                 {
121                         # this is the range
122                         my @replacement;
123
124                         if($first == $a && $last == $b)
125                         {
126                                 @replacement = ();
127                         }
128                         elsif($first == $a)
129                         {
130                                 @replacement = ([$last + 1, $b]);
131                         }
132                         elsif($last == $b)
133                         {
134                                 @replacement = ([$a, $first - 1]);
135                         }
136                         else
137                         {
138                                 @replacement = ([$a, $first - 1], [$last + 1, $b]);
139                         }
140                         splice @rev, $i, 1, @replacement;
141                         @revisions_applied = @rev;
142                         return;
143                 }
144         }
145
146         die "could not remove range: not in set";
147 }
148
149 sub GetUnappliedRanges($)
150 {
151         my ($lastrev) = @_;
152         my @unapplied = ();
153
154         my $cur = 0;
155         for(@revisions_applied)
156         {
157                 my ($a, $b) = @$_;
158                 if($a - 1 >= $cur + 1)
159                 {
160                         push @unapplied, [$cur + 1, $a - 1];
161                 }
162                 $cur = $b;
163         }
164         if($lastrev >= $cur + 1)
165         {
166                 push @unapplied, [$cur + 1, $lastrev];
167         }
168         return @unapplied;
169 }
170
171 sub GetMasterRev()
172 {
173         my $svninfo = OutputOf 'svn info #1', $conf{master};
174         $svninfo =~ /^Last Changed Rev: (\d+)$/m
175                 or die "could not get svn info";
176         return $1;
177 }
178
179 sub GetLog($$)
180 {
181         my ($first, $last) = @_;
182         my $log = OutputOf 'svn log -r#1:#2 #3', $first, $last, $conf{master};
183         $log =~ s/^-*$//gm;
184         $log =~ s/\n+/\n/gs;
185         $log =~ s/^\n//s;
186         $log =~ s/\n$//s;
187         return $log;
188 }
189
190 sub GetDiff($$)
191 {
192         my ($first, $last) = @_;
193         return OutputOf 'svn diff -r#1:#2 #3', $first-1, $last, $conf{master};
194 }
195
196 my ($cmd, @args) = @ARGV;
197 $cmd = 'help' if not defined $cmd;
198
199 if($cmd eq 'info')
200 {
201         LoadSettings();
202         for(@revisions_applied)
203         {
204                 my ($a, $b) = @$_;
205                 print "Applied: $a to $b\n";
206         }
207         for(GetUnappliedRanges(GetMasterRev()))
208         {
209                 my ($a, $b) = @$_;
210                 print "Unapplied: $a to $b\n";
211         }
212 }
213 elsif($cmd eq 'unmerged-diff')
214 {
215         LoadSettings();
216         my @autoadd = ();
217         for(GetUnappliedRanges(GetMasterRev()))
218         {
219                 my ($a, $b) = @$_;
220                 my $log = GetLog $a, $b;
221                 if($log eq '')
222                 {
223                         push @autoadd, [$a, $b];
224                         next;
225                 }
226                 $log =~ s/^/  /gm;
227                 print "Unapplied: $a to $b\n";
228                 print "$log\n";
229                 print GetDiff $a, $b;
230                 print "\n";
231         }
232         for(@autoadd)
233         {
234                 my ($a, $b) = @$_;
235                 print "Autofilled revision hole $a to $b\n";
236                 AddPatch $a, $b;
237         }
238         WriteSettings() if @autoadd;
239 }
240 elsif($cmd eq 'unmerged')
241 {
242         LoadSettings();
243         my @autoadd = ();
244         for(GetUnappliedRanges(GetMasterRev()))
245         {
246                 my ($a, $b) = @$_;
247                 my $log = GetLog $a, $b;
248                 if($log eq '')
249                 {
250                         push @autoadd, [$a, $b];
251                         next;
252                 }
253                 $log =~ s/^/  /gm;
254                 print "Unapplied: $a to $b\n";
255                 print "$log\n\n";
256         }
257         for(@autoadd)
258         {
259                 my ($a, $b) = @$_;
260                 print "Autofilled revision hole $a to $b\n";
261                 AddPatch $a, $b;
262         }
263         WriteSettings() if @autoadd;
264 }
265 elsif($cmd eq 'merge')
266 {
267         my ($first, $last, $force) = @args;
268         die "Usage: $0 merge first last [--force]"
269                 if not defined $first;
270         $last = $first if not defined $last;
271
272         die "Usage: $0 merge first last"
273                 if "$first$last" =~ /[^0-9]/;
274
275         die "There is an uncommitted merge"
276                 if -f '.commitmsg' and $force ne '--force';
277
278         LoadSettings();
279         AddPatch $first, $last;
280
281         StatusOf 'svn merge -r#1:#2 #3', ($first - 1), $last, $conf{master};
282         print "You may also want to run $0 unmerged to fill possible revision holes\n";
283         print "Make sure there are no conflicts, then run $0 commit\n";
284         print "To abort, use $0 revert\n";
285         
286         open my $fh, '>>', '.commitmsg'
287                 or die "open .commitmsg";
288         print $fh GetLog $first, $last;
289         print $fh "\n";
290         close $fh;
291
292         WriteSettings();
293 }
294 elsif($cmd eq 'undo')
295 {
296         my ($first, $last, $force) = @args;
297         die "Usage: $0 undo first last"
298                 if not defined $first;
299         $last = $first if not defined $last;
300
301         die "Usage: $0 merge first last"
302                 if "$first$last" =~ /[^0-9]/;
303
304         die "There is an uncommitted merge"
305                 if -f '.commitmsg' and $force ne '--force';
306
307         LoadSettings();
308         RemovePatch $first, $last;
309
310         print OutputOf 'svn merge -r#2:#1 #3', ($first - 1), $last, $conf{master};
311         print "Make sure there are no conflicts, then run $0 commit\n";
312         print "To abort, use $0 revert\n";
313
314         open my $fh, '>>', '.commitmsg'
315                 or die "open .commitmsg";
316         print $fh "undo the following merge:\n", GetLog $first, $last;
317         close $fh;
318
319         WriteSettings();
320 }
321 elsif($cmd eq 'commit')
322 {
323         system 'vim .commitmsg';
324         print "Hit Enter if OK to commit, Ctrl-C otherwise...\n";
325         my $ok = <STDIN>;
326         if(!system 'svn commit -F .commitmsg')
327         {
328                 unlink '.commitmsg';
329         }
330 }
331 elsif($cmd eq 'revert')
332 {
333         if(!system 'svn revert -R .')
334         {
335                 unlink '.commitmsg';
336         }
337 }
338 elsif($cmd eq 'init')
339 {
340         my ($master, $rev) = @args;
341         $conf{master} = $master;
342         @revisions_applied = [1, $rev];
343         WriteSettings();
344 }
345 else
346 {
347         print <<EOF;
348 Usage:
349   $0 init masterrepo rev
350   $0 info
351   $0 unmerged
352   $0 unmerged-diff
353   $0 merge rev1 [rev2] [--force]
354   $0 undo rev1 [rev2] [--force]
355   $0 commit
356   $0 revert
357 EOF
358 }