NetHack->aNetHack
[aNetHack.git] / DEVEL / hooksdir / NHsubst
blob1e19f6eb38f0601a2955e7d5f16cafabfae255d4
1 #!/usr/bin/perl
3 # NHsubst
4 # $NHDT-Date$
5 # git merge driver for substitutions (like RCS/CVS)
6 # driver line: .... %O %A %B %L
7 use strict;
9 my $debug = 0;
10 my $rawin = 0; # feed diff to stdin for testing (do NOT set $debug=1)
12 # We want TRACE open so we don't need to test $debug everywhere, but we skip
13 # this first block because it's expensive and dumpfile() hangs with $rawin.
14 my $sink = ($^O eq "MSWin32") ? "NUL" : "/dev/null";
15 my $dbgfile = ($^O eq "MSWin32") ? "$ENV{TEMP}.$$" : "/tmp/trace.$$";
16 open TRACE, ">>", $rawin?"/dev/tty":(($debug==0)? $sink : $dbgfile);
17 print TRACE "TEST TRACE\n";
18 if($debug){
19 print TRACE "START CLIENT ARGV:\n";
20 print TRACE "[0] $0\n";
21 my $x1;
22 for(my $x=0;$x<scalar @ARGV;$x++){
23 $x1 = $x+1;
24 print TRACE "[$x1] $ARGV[$x]\n";
26 print TRACE "ENV:\n";
27 foreach my $k (sort keys %ENV){
28 next unless ($k =~ m/^GIT_/);
29 print TRACE " $k => $ENV{$k}\n";
31 print TRACE "CWD: " . `pwd`;
32 &dumpfile($ARGV[0], "[0O]");
33 &dumpfile($ARGV[1], "[1A]");
34 &dumpfile($ARGV[2], "[2B]");
35 print TRACE "L=$ARGV[3]\n";
36 print TRACE "END\n";
39 my $mark_len = $ARGV[3];
40 $mark_len = 3 if($mark_len==0 && $rawin);
42 my $mark_start = '<' x $mark_len;
43 my $mark_middle = '=' x $mark_len;
44 my $mark_end = '>' x $mark_len;
46 my $PREFIX;
47 # pick up the prefix for substitutions in this repo
48 if($rawin){
49 $PREFIX = "TEST";
50 } else {
51 $PREFIX = `git config --local --get nethack.substprefix`;
52 chomp($PREFIX);
55 my @out;
56 my $cntout;
57 if($rawin){
58 @out = <STDIN>;
59 } else {
60 #system "git merge-file -p .... > temp
61 my $tags = "-L CURRENT -L ANCESTOR -L OTHER"; # XXX should "CURRENT" be "MINE"?
62 @out = `git merge-file -p $tags $ARGV[1] $ARGV[0] $ARGV[2]`;
63 #NB: we don't check the exit value because it's useless
64 print TRACE "MERGE-FILE START\n".join("",@out)."MERGE-FILE END\n";
67 ($cntout,@out) = &edit_merge(@out);
69 if($rawin){
70 print "COUNT: $cntout\n";
71 print @out;
72 } else {
73 # spit @out to $ARGV[1] (careful: what about EOL character?)
74 open OUT, ">$ARGV[1]" or die "Can't open $ARGV[1]";
75 print OUT @out;
76 close OUT;
78 print TRACE "WRITING START ($ARGV[1])\n".join("",@out)."WRITING END\n";
79 &dumpfile($ARGV[1], "READBACK");
81 print TRACE "COUNT: $cntout\n";
83 exit( ($cntout>0) ? 1 : 0);
85 #git merge-file [-L <current-name> [-L <base-name> [-L <other-name>]]]
86 # [--ours|--theirs|--union] [-p|--stdout] [-q|--quiet] [--marker-size=<n>]
87 # [--[no-]diff3] <current-file> <base-file> <other-file>
88 #The `merge.*.driver` variable's value is used to construct a command to run to merge ancestor's
89 # version (%O), current version (%A) and the other branches' version (%B). These three tokens are
90 # replaced with the names of temporary files that hold the contents of these versions when the
91 # command line is built. Additionally, %L will be replaced with the conflict marker size (see
92 # below).
94 # keep failing so we don't need to keep changing the setup while building this script
96 sub dumpfile {
97 my($file, $tag) = @_;
98 print TRACE "FILE $tag START\n";
99 print TRACE `hexdump -C $file`;
100 print TRACE "FILE END\n";
103 sub edit_merge {
104 my(@input) = @_;
105 # $::count is a bit ugly XXX
106 local $::count = 0; # we need the number of conflicts for exit()
107 my @out;
109 local $_;
110 while($_ = shift @input){
111 if(m/^$mark_start /){
112 print TRACE "FOUND A CONFLICT\n";
113 my @conflict;
114 push(@conflict, $_);
115 while($_ = shift @input){
116 push(@conflict, $_);
117 if(m/^$mark_end /){
118 last;
121 push(@out, &edit_conflict(@conflict));
122 } else {
123 push(@out, $_);
126 print TRACE "RETURN count=$::count\n";
127 return($::count, @out);
130 sub edit_conflict {
131 my(@in) = @_;
133 print TRACE "EDIT START: " . scalar(@in)."\n";
134 if($debug){
135 foreach my $x (@in){ my $xx = $x; chomp($xx); print TRACE "-$xx-\n"; }
137 print TRACE "EDIT END INPUT\n";
139 # one-line change - use as base case to develop the code
140 # ours ARGV[1] top-of-diff
141 # theirs ARGV[2] bottom-of-diff
142 # simple conflict:
143 # [0] <<<<<<< d1
144 # [1] $$PREFIX-Date: 1 ...
145 # [2] =======
146 # [3] $$PREFIX-Date: 3 ...
147 # [4] >>>>>>> d3
148 if(scalar(@in) == 5 && $in[2] =~ m/^$mark_middle/){
149 my $back = &merge_one_line_maybe($in[1],$in[3]); # (ours, theirs)
150 if(!defined $back){
151 $::count++; # leave the conflict
152 return @in;
153 } else {
154 return ($back);
156 # NOTREACHED
157 } else {
158 # XXX LATER
159 # Start at the top of both sections and work downwards. As long as the lines can be merged,
160 # push them out and keep going. If there are lines left, we will still have a conflict but
161 # we can try to make it smaller. Push out the start-conflict marker. Start at the
162 # bottom of both section and work upwards. As long as the lines can be merged, reverse push out
163 # the merged line and keep going. (We know there will be lines left at some point.) Push out
164 # remaining (middle) lines from OURS. Push out mark_middle. Push out remaining middle lines
165 # from THEIRS. Push out end-conflict marker. $::count++; return (@a,$b,@c,$d,@e,$f,@g)
166 # @a
167 # $b = <<<
168 # @c
169 # $d = ===
170 # @e
171 # $f = >>>
172 # @g
174 # not matched - return the unchanged conflict
175 $::count++;
176 return @in;
179 # XXX This is expensive. Add a quick check for "anything that looks like a subst var" and just
180 # declare the lines unmergeable if it fails.
181 sub merge_one_line_maybe {
182 my($ours, $theirs) = @_;
184 my $more = 1;
185 my $fail = 0;
186 my $out = '';
187 # TYPES:
188 # 0 no match
189 # 1 unexpanded var
190 # 2 expanded var
191 # 3 non-var text
192 my($ourstype, $theirtype);
193 my($oursvar, $theirvar);
194 my($oursval, $theirval);
196 while($more){
197 ($ourstype, $theirtype) = (0,0);
198 ($oursvar, $theirvar) = (undef, undef);
199 ($oursvar, $theirvar) = (undef, undef);
200 # unexpanded var
201 if($ours =~ m/\G\$$PREFIX-([A-Z][a-z]+)\$/gc){
202 $ourstype = 1;
203 $oursvar = $1;
205 if($theirs =~ m/\G\$$PREFIX-([A-Z][a-z]+)\$/gc){
206 $theirtype = 1;
207 $theirvar = $1;
209 # expanded var
210 unless($ourstype){
211 if($ours =~ m/\G\$$PREFIX-([A-Za-z]+):\s+(.*?)\s\$/gc){
212 $ourstype = 2;
213 $oursvar = $1;
214 $oursval = $2;
217 unless($theirtype){
218 if($theirs =~ m/\G\$$PREFIX-([A-Za-z]+):\s+(.*?)\s\$/gc){
219 $theirtype = 2;
220 $theirvar = $1;
221 $theirval = $2;
224 # non-var text
225 unless($ourstype){
226 if($ours =~ m/\G(\$?[^\x24]*)/gc){
227 $ourstype = 3;
228 $oursval = $1;
231 unless($theirtype){
232 if($theirs =~ m/\G(\$?[^\x24]*)/gc){
233 $theirtype = 3;
234 $theirval = $1;
237 print TRACE "MID: $ourstype/$oursval $theirtype/$theirval\n";
238 # are we done?
239 if(pos($ours)==length $ours && pos($theirs) == length $theirs){
240 $more = 0;
242 if($ourstype == 0 && $theirtype == 0){
243 die "NHsubst MERGE FAILED - aborted infinite loop\n";
246 # now see if ours and their match or can be resolved
247 # text
248 if($ourstype == 3 && $theirtype == 3){
249 #mismatch is \s vs \s\s - where is this coming from?
250 # HACK - hopefully temporary
251 if($oursval =~ m/^\s+$/ && $theirval =~ m/^\s+$/){
252 $out .= $oursval;
253 next;
255 if($oursval eq $theirval){
256 $out .= $oursval;
257 next;
259 return undef;
261 if($ourstype == 3 || $theirtype == 3){
262 return undef;
264 # XXX we could do better: on failure of one field, return 2 lines with the fields we _can_ fix
265 # substituted into those lines, leaving only the fail-to-match bits for the user to
266 # deal with. Later.
267 # vars (all 4 cases)
268 if($oursvar ne $theirvar){
269 return undef;
271 my $m = merge_one_var_maybe($oursvar, $oursval, $theirval);
272 if(! defined $m){
273 return undef;
275 $out .= $m;
277 return $out;
280 # return undef if we can't merge the values; $NAME: VALUE $ or $NAME$ (as appropriate) if we can.
281 sub merge_one_var_maybe {
282 my($varname, $oursval, $theirval) = @_;
283 print TRACE "MVM: -$varname-$oursval-$theirval-\n";
284 my $resolvedas;
286 no strict;
287 my $fn = "PREFIX::$varname";
288 if(defined &$fn){
289 $resolvedas = &$fn($PREFIX,$varname,$oursval, $theirval);
290 } else {
291 $resolvedas = undef; # can't resolve
295 if(!defined $resolvedas){
296 $::count++; # we have an externally visible conflict
297 return undef;
298 } else {
299 return $resolvedas;
301 # NOTREACHED
304 package PREFIX;
305 # Resolve the conflict of a single var's 2 values. Return undef to leave the conflict.
306 sub Date {
307 my($PREFIX, $varname, $mine, $theirs) = @_;
308 my $m = ($mine =~ m/(\d+)/)[0];
309 my $t = ($theirs =~ m/(\d+)/)[0];
310 return undef unless ($m>0) && ($t>0);
312 return "\$$PREFIX-$varname: " . (($m>$t)?$mine:$theirs) .' $';
315 #sub Header {
316 #sub Author {
318 sub Branch {
319 my($PREFIX, $varname, $mine, $theirs) = @_;
320 $mine =~ s/^\s+//; $mine =~ s/\s+$//;
321 $theirs =~ s/^\s+//; $theirs =~ s/\s+$//;
322 return "\$$PREFIX-$varname: $mine \$" if(length $mine);
323 return "\$$PREFIX-$varname: $theirs \$" if(length $theirs);
324 return "\$$PREFIX-$varname\$" if(length $theirs);
327 sub Revision {
328 my($PREFIX, $varname, $mine, $theirs) = @_;
329 my($m) = ($mine =~ m/1.(\d+)/);
330 my($t) = ($theirs =~ m/1.(\d+)/);
331 if($m > 0 && $t > 0){
332 my $q = ($m > $t) ? $m : $t;
333 return "\$$PREFIX-$varname: 1.$q \$";
335 if($m > 0){
336 return "\$$PREFIX-$varname: 1.$m \$";
338 if($t > 0){
339 return "\$$PREFIX-$varname: 1.$t \$";
341 return "\$$PREFIX-$varname\$";
343 __END__
345 TEST 1:
346 <<< d1
347 $TEST-Date: 1 $
349 $TEST-Date: 3 $
350 >>> d3
352 TEST 2:
353 nothing
354 at all
356 TEST 3:
357 <<< d1
358 a line
360 one line
361 two lines
362 >>> d3
364 TEST 4:
365 <<< d1
366 $TEST-Date: 1 $ yes
368 $TEST-Date: 1 $ no
369 >>> d3
371 TEST 5:
372 <<< d1
373 $TEST-Date: 3 $ yes
375 $TEST-Date: 1 $ yes
376 >>> d3
378 TEST 6:
379 <<< d1
380 $TEST-Date: 3 $ yes$TEST-Date: 4 $
382 $TEST-Date: 1 $ yes$TEST-Date: 5 $
383 >>> d3
385 TEST 7:
386 <<< d1
387 $TEST-Branch: mine $
389 $TEST-Branch: theirs $
390 >>> d3
392 TEST 8:
393 <<< d1
394 /* NetHack 3.6 objnam.c $TEST-Date$ $TEST-Branch$:$TEST-Revision$ */
396 /* NetHack 3.6 objnam.c $TEST-Date: 1426977394 2015/03/21 22:36:34 $ $TEST-Branch: master $:$TEST-Revision: 1.108 $ */
397 >>> d3