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