xref: /openbsd/gnu/usr.bin/cvs/diff/util.c (revision 73471bf0)
1 /* Support routines for GNU DIFF.
2    Copyright (C) 1988, 1989, 1992, 1993, 1994, 1997, 1998 Free Software Foundation, Inc.
3 
4 This file is part of GNU DIFF.
5 
6 GNU DIFF is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10 
11 GNU DIFF is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
15 
16 */
17 
18 #include "diff.h"
19 
20 #if __STDC__
21 #include <stdarg.h>
22 #else
23 #include <varargs.h>
24 #endif
25 
26 #ifndef strerror
27 extern char *strerror ();
28 #endif
29 
30 /* Queue up one-line messages to be printed at the end,
31    when -l is specified.  Each message is recorded with a `struct msg'.  */
32 
33 struct msg
34 {
35   struct msg *next;
36   char const *format;
37   char const *arg1;
38   char const *arg2;
39   char const *arg3;
40   char const *arg4;
41 };
42 
43 /* Head of the chain of queues messages.  */
44 
45 static struct msg *msg_chain;
46 
47 /* Tail of the chain of queues messages.  */
48 
49 static struct msg **msg_chain_end = &msg_chain;
50 
51 /* Use when a system call returns non-zero status.
52    TEXT should normally be the file name.  */
53 
54 void
55 perror_with_name (text)
56      char const *text;
57 {
58   int e = errno;
59 
60   if (callbacks && callbacks->error)
61     (*callbacks->error) ("%s: %s", text, strerror (e));
62   else
63     {
64       fprintf (stderr, "%s: ", diff_program_name);
65       errno = e;
66       perror (text);
67     }
68 }
69 
70 /* Use when a system call returns non-zero status and that is fatal.  */
71 
72 void
73 pfatal_with_name (text)
74      char const *text;
75 {
76   int e = errno;
77   print_message_queue ();
78   if (callbacks && callbacks->error)
79     (*callbacks->error) ("%s: %s", text, strerror (e));
80   else
81     {
82       fprintf (stderr, "%s: ", diff_program_name);
83       errno = e;
84       perror (text);
85     }
86   DIFF_ABORT (2);
87 }
88 
89 /* Print an error message from the format-string FORMAT
90    with args ARG1 and ARG2.  */
91 
92 void
93 diff_error (format, arg, arg1)
94      char const *format, *arg, *arg1;
95 {
96   if (callbacks && callbacks->error)
97     (*callbacks->error) (format, arg, arg1);
98   else
99     {
100       fprintf (stderr, "%s: ", diff_program_name);
101       fprintf (stderr, format, arg, arg1);
102       fprintf (stderr, "\n");
103     }
104 }
105 
106 /* Print an error message containing the string TEXT, then exit.  */
107 
108 void
109 fatal (m)
110      char const *m;
111 {
112   print_message_queue ();
113   diff_error ("%s", m, 0);
114   DIFF_ABORT (2);
115 }
116 
117 /* Like printf, except if -l in effect then save the message and print later.
118    This is used for things like "binary files differ" and "Only in ...".  */
119 
120 void
121 message (format, arg1, arg2)
122      char const *format, *arg1, *arg2;
123 {
124   message5 (format, arg1, arg2, 0, 0);
125 }
126 
127 void
128 message5 (format, arg1, arg2, arg3, arg4)
129      char const *format, *arg1, *arg2, *arg3, *arg4;
130 {
131   if (paginate_flag)
132     {
133       struct msg *new = (struct msg *) xmalloc (sizeof (struct msg));
134       new->format = format;
135       new->arg1 = concat (arg1, "", "");
136       new->arg2 = concat (arg2, "", "");
137       new->arg3 = arg3 ? concat (arg3, "", "") : 0;
138       new->arg4 = arg4 ? concat (arg4, "", "") : 0;
139       new->next = 0;
140       *msg_chain_end = new;
141       msg_chain_end = &new->next;
142     }
143   else
144     {
145       if (sdiff_help_sdiff)
146 	write_output (" ", 1);
147       printf_output (format, arg1, arg2, arg3, arg4);
148     }
149 }
150 
151 /* Output all the messages that were saved up by calls to `message'.  */
152 
153 void
154 print_message_queue ()
155 {
156   struct msg *m;
157 
158   for (m = msg_chain; m; m = m->next)
159     printf_output (m->format, m->arg1, m->arg2, m->arg3, m->arg4);
160 }
161 
162 /* Call before outputting the results of comparing files NAME0 and NAME1
163    to set up OUTFILE, the stdio stream for the output to go to.
164 
165    Usually, OUTFILE is just stdout.  But when -l was specified
166    we fork off a `pr' and make OUTFILE a pipe to it.
167    `pr' then outputs to our stdout.  */
168 
169 static char const *current_name0;
170 static char const *current_name1;
171 static int current_depth;
172 
173 static int output_in_progress = 0;
174 
175 void
176 setup_output (name0, name1, depth)
177      char const *name0, *name1;
178      int depth;
179 {
180   current_name0 = name0;
181   current_name1 = name1;
182   current_depth = depth;
183 }
184 
185 #if HAVE_FORK && defined (PR_PROGRAM)
186 static pid_t pr_pid;
187 #endif
188 
189 void
190 begin_output ()
191 {
192   char *name;
193 
194   if (output_in_progress)
195     return;
196   output_in_progress = 1;
197 
198   /* Construct the header of this piece of diff.  */
199   name = xmalloc (strlen (current_name0) + strlen (current_name1)
200 		  + strlen (switch_string) + 7);
201   /* Posix.2 section 4.17.6.1.1 specifies this format.  But there is a
202      bug in the first printing (IEEE Std 1003.2-1992 p 251 l 3304):
203      it says that we must print only the last component of the pathnames.
204      This requirement is silly and does not match historical practice.  */
205   sprintf (name, "diff%s %s %s", switch_string, current_name0, current_name1);
206 
207   if (paginate_flag && callbacks && callbacks->write_output)
208     fatal ("can't paginate when using library callbacks");
209 
210   if (paginate_flag)
211     {
212       /* Make OUTFILE a pipe to a subsidiary `pr'.  */
213 
214 #ifdef PR_PROGRAM
215 
216 # if HAVE_FORK
217       int pipes[2];
218 
219       if (pipe (pipes) != 0)
220 	pfatal_with_name ("pipe");
221 
222       fflush (stdout);
223 
224       pr_pid = vfork ();
225       if (pr_pid < 0)
226 	pfatal_with_name ("vfork");
227 
228       if (pr_pid == 0)
229 	{
230 	  close (pipes[1]);
231 	  if (pipes[0] != STDIN_FILENO)
232 	    {
233 	      if (dup2 (pipes[0], STDIN_FILENO) < 0)
234 		pfatal_with_name ("dup2");
235 	      close (pipes[0]);
236 	    }
237 
238 	  execl (PR_PROGRAM, PR_PROGRAM, "-f", "-h", name, (char *)NULL);
239 	  pfatal_with_name (PR_PROGRAM);
240 	}
241       else
242 	{
243 	  close (pipes[0]);
244 	  outfile = fdopen (pipes[1], "w");
245 	  if (!outfile)
246 	    pfatal_with_name ("fdopen");
247 	}
248 # else /* ! HAVE_FORK */
249       char *command = xmalloc (4 * strlen (name) + strlen (PR_PROGRAM) + 10);
250       char *p;
251       char const *a = name;
252       sprintf (command, "%s -f -h ", PR_PROGRAM);
253       p = command + strlen (command);
254       SYSTEM_QUOTE_ARG (p, a);
255       *p = 0;
256       outfile = popen (command, "w");
257       if (!outfile)
258 	pfatal_with_name (command);
259       free (command);
260 # endif /* ! HAVE_FORK */
261 #else
262       fatal ("This port does not support the --paginate option to diff.");
263 #endif
264     }
265   else
266     {
267 
268       /* If -l was not specified, output the diff straight to `stdout'.  */
269 
270       /* If handling multiple files (because scanning a directory),
271 	 print which files the following output is about.  */
272       if (current_depth > 0)
273 	printf_output ("%s\n", name);
274     }
275 
276   free (name);
277 
278   /* A special header is needed at the beginning of context output.  */
279   switch (output_style)
280     {
281     case OUTPUT_CONTEXT:
282       print_context_header (files, 0);
283       break;
284 
285     case OUTPUT_UNIFIED:
286       print_context_header (files, 1);
287       break;
288 
289     default:
290       break;
291     }
292 }
293 
294 /* Call after the end of output of diffs for one file.
295    If -l was given, close OUTFILE and get rid of the `pr' subfork.  */
296 
297 void
298 finish_output ()
299 {
300   if (paginate_flag && outfile != 0 && outfile != stdout)
301     {
302 #ifdef PR_PROGRAM
303       int wstatus;
304       if (ferror (outfile))
305 	fatal ("write error");
306 # if ! HAVE_FORK
307       wstatus = pclose (outfile);
308 # else /* HAVE_FORK */
309       if (fclose (outfile) != 0)
310 	pfatal_with_name ("write error");
311       if (waitpid (pr_pid, &wstatus, 0) < 0)
312 	pfatal_with_name ("waitpid");
313 # endif /* HAVE_FORK */
314       if (wstatus != 0)
315 	fatal ("subsidiary pr failed");
316 #else
317       fatal ("internal error in finish_output");
318 #endif
319     }
320 
321   output_in_progress = 0;
322 }
323 
324 /* Write something to the output file.  */
325 
326 void
327 write_output (text, len)
328      char const *text;
329      size_t len;
330 {
331   if (callbacks && callbacks->write_output)
332     (*callbacks->write_output) (text, len);
333   else if (len == 1)
334     putc (*text, outfile);
335   else
336     fwrite (text, sizeof (char), len, outfile);
337 }
338 
339 /* Printf something to the output file.  */
340 
341 #if __STDC__
342 #define VA_START(args, lastarg) va_start(args, lastarg)
343 #else /* ! __STDC__ */
344 #define VA_START(args, lastarg) va_start(args)
345 #endif /* __STDC__ */
346 
347 void
348 #if __STDC__
349 printf_output (const char *format, ...)
350 #else
351 printf_output (format, va_alist)
352      char const *format;
353      va_dcl
354 #endif
355 {
356   va_list args;
357 
358   VA_START (args, format);
359   if (callbacks && callbacks->write_output)
360     {
361       /* We implement our own limited printf-like functionality (%s, %d,
362 	 and %c only).  Callers who want something fancier can use
363 	 sprintf.  */
364       const char *p = format;
365       char *q;
366       char *str;
367       int num;
368       int ch;
369       char buf[100];
370 
371       while ((q = strchr (p, '%')) != NULL)
372 	{
373 	  static const char msg[] =
374 	    "\ninternal error: bad % in printf_output\n";
375 	  (*callbacks->write_output) (p, q - p);
376 
377 	  switch (q[1])
378 	    {
379 	    case 's':
380 	      str = va_arg (args, char *);
381 	      (*callbacks->write_output) (str, strlen (str));
382 	      break;
383 	    case 'd':
384 	      num = va_arg (args, int);
385 	      sprintf (buf, "%d", num);
386 	      (*callbacks->write_output) (buf, strlen (buf));
387 	      break;
388 	    case 'c':
389 	      ch = va_arg (args, int);
390 	      buf[0] = ch;
391 	      (*callbacks->write_output) (buf, 1);
392 	      break;
393 	    default:
394 	      (*callbacks->write_output) (msg, sizeof (msg) - 1);
395 	      /* Don't just keep going, because q + 1 might point to the
396 		 terminating '\0'.  */
397 	      goto out;
398 	    }
399 	  p = q + 2;
400 	}
401       (*callbacks->write_output) (p, strlen (p));
402     }
403   else
404     vfprintf (outfile, format, args);
405  out:
406   va_end (args);
407 }
408 
409 /* Flush the output file.  */
410 
411 void
412 flush_output ()
413 {
414   if (callbacks && callbacks->flush_output)
415     (*callbacks->flush_output) ();
416   else
417     fflush (outfile);
418 }
419 
420 /* Compare two lines (typically one from each input file)
421    according to the command line options.
422    For efficiency, this is invoked only when the lines do not match exactly
423    but an option like -i might cause us to ignore the difference.
424    Return nonzero if the lines differ.  */
425 
426 int
427 line_cmp (s1, s2)
428      char const *s1, *s2;
429 {
430   register unsigned char const *t1 = (unsigned char const *) s1;
431   register unsigned char const *t2 = (unsigned char const *) s2;
432 
433   while (1)
434     {
435       register unsigned char c1 = *t1++;
436       register unsigned char c2 = *t2++;
437 
438       /* Test for exact char equality first, since it's a common case.  */
439       if (c1 != c2)
440 	{
441 	  /* Ignore horizontal white space if -b or -w is specified.  */
442 
443 	  if (ignore_all_space_flag)
444 	    {
445 	      /* For -w, just skip past any white space.  */
446 	      while (ISSPACE (c1) && c1 != '\n') c1 = *t1++;
447 	      while (ISSPACE (c2) && c2 != '\n') c2 = *t2++;
448 	    }
449 	  else if (ignore_space_change_flag)
450 	    {
451 	      /* For -b, advance past any sequence of white space in line 1
452 		 and consider it just one Space, or nothing at all
453 		 if it is at the end of the line.  */
454 	      if (ISSPACE (c1))
455 		{
456 		  while (c1 != '\n')
457 		    {
458 		      c1 = *t1++;
459 		      if (! ISSPACE (c1))
460 			{
461 			  --t1;
462 			  c1 = ' ';
463 			  break;
464 			}
465 		    }
466 		}
467 
468 	      /* Likewise for line 2.  */
469 	      if (ISSPACE (c2))
470 		{
471 		  while (c2 != '\n')
472 		    {
473 		      c2 = *t2++;
474 		      if (! ISSPACE (c2))
475 			{
476 			  --t2;
477 			  c2 = ' ';
478 			  break;
479 			}
480 		    }
481 		}
482 
483 	      if (c1 != c2)
484 		{
485 		  /* If we went too far when doing the simple test
486 		     for equality, go back to the first non-white-space
487 		     character in both sides and try again.  */
488 		  if (c2 == ' ' && c1 != '\n'
489 		      && (unsigned char const *) s1 + 1 < t1
490 		      && ISSPACE(t1[-2]))
491 		    {
492 		      --t1;
493 		      continue;
494 		    }
495 		  if (c1 == ' ' && c2 != '\n'
496 		      && (unsigned char const *) s2 + 1 < t2
497 		      && ISSPACE(t2[-2]))
498 		    {
499 		      --t2;
500 		      continue;
501 		    }
502 		}
503 	    }
504 
505 	  /* Lowercase all letters if -i is specified.  */
506 
507 	  if (ignore_case_flag)
508 	    {
509 	      if (ISUPPER (c1))
510 		c1 = tolower (c1);
511 	      if (ISUPPER (c2))
512 		c2 = tolower (c2);
513 	    }
514 
515 	  if (c1 != c2)
516 	    break;
517 	}
518       if (c1 == '\n')
519 	return 0;
520     }
521 
522   return (1);
523 }
524 
525 /* Find the consecutive changes at the start of the script START.
526    Return the last link before the first gap.  */
527 
528 struct change *
529 find_change (start)
530      struct change *start;
531 {
532   return start;
533 }
534 
535 struct change *
536 find_reverse_change (start)
537      struct change *start;
538 {
539   return start;
540 }
541 
542 /* Divide SCRIPT into pieces by calling HUNKFUN and
543    print each piece with PRINTFUN.
544    Both functions take one arg, an edit script.
545 
546    HUNKFUN is called with the tail of the script
547    and returns the last link that belongs together with the start
548    of the tail.
549 
550    PRINTFUN takes a subscript which belongs together (with a null
551    link at the end) and prints it.  */
552 
553 void
554 print_script (script, hunkfun, printfun)
555      struct change *script;
556      struct change * (*hunkfun) PARAMS((struct change *));
557      void (*printfun) PARAMS((struct change *));
558 {
559   struct change *next = script;
560 
561   while (next)
562     {
563       struct change *this, *end;
564 
565       /* Find a set of changes that belong together.  */
566       this = next;
567       end = (*hunkfun) (next);
568 
569       /* Disconnect them from the rest of the changes,
570 	 making them a hunk, and remember the rest for next iteration.  */
571       next = end->link;
572       end->link = 0;
573 #ifdef DEBUG
574       debug_script (this);
575 #endif
576 
577       /* Print this hunk.  */
578       (*printfun) (this);
579 
580       /* Reconnect the script so it will all be freed properly.  */
581       end->link = next;
582     }
583 }
584 
585 /* Print the text of a single line LINE,
586    flagging it with the characters in LINE_FLAG (which say whether
587    the line is inserted, deleted, changed, etc.).  */
588 
589 void
590 print_1_line (line_flag, line)
591      char const *line_flag;
592      char const * const *line;
593 {
594   char const *text = line[0], *limit = line[1]; /* Help the compiler.  */
595   char const *flag_format = 0;
596 
597   /* If -T was specified, use a Tab between the line-flag and the text.
598      Otherwise use a Space (as Unix diff does).
599      Print neither space nor tab if line-flags are empty.  */
600 
601   if (line_flag && *line_flag)
602     {
603       flag_format = tab_align_flag ? "%s\t" : "%s ";
604       printf_output (flag_format, line_flag);
605     }
606 
607   output_1_line (text, limit, flag_format, line_flag);
608 
609   if ((!line_flag || line_flag[0]) && limit[-1] != '\n')
610     printf_output ("\n\\ No newline at end of file\n");
611 }
612 
613 /* Output a line from TEXT up to LIMIT.  Without -t, output verbatim.
614    With -t, expand white space characters to spaces, and if FLAG_FORMAT
615    is nonzero, output it with argument LINE_FLAG after every
616    internal carriage return, so that tab stops continue to line up.  */
617 
618 void
619 output_1_line (text, limit, flag_format, line_flag)
620      char const *text, *limit, *flag_format, *line_flag;
621 {
622   if (!tab_expand_flag)
623     write_output (text, limit - text);
624   else
625     {
626       register unsigned char c;
627       register char const *t = text;
628       register unsigned column = 0;
629       /* CC is used to avoid taking the address of the register
630          variable C.  */
631       char cc;
632 
633       while (t < limit)
634 	switch ((c = *t++))
635 	  {
636 	  case '\t':
637 	    {
638 	      unsigned spaces = TAB_WIDTH - column % TAB_WIDTH;
639 	      column += spaces;
640 	      do
641 		write_output (" ", 1);
642 	      while (--spaces);
643 	    }
644 	    break;
645 
646 	  case '\r':
647 	    write_output ("\r", 1);
648 	    if (flag_format && t < limit && *t != '\n')
649 	      printf_output (flag_format, line_flag);
650 	    column = 0;
651 	    break;
652 
653 	  case '\b':
654 	    if (column == 0)
655 	      continue;
656 	    column--;
657 	    write_output ("\b", 1);
658 	    break;
659 
660 	  default:
661 	    if (ISPRINT (c))
662 	      column++;
663 	    cc = c;
664 	    write_output (&cc, 1);
665 	    break;
666 	  }
667     }
668 }
669 
670 int
671 change_letter (inserts, deletes)
672      int inserts, deletes;
673 {
674   if (!inserts)
675     return 'd';
676   else if (!deletes)
677     return 'a';
678   else
679     return 'c';
680 }
681 
682 /* Translate an internal line number (an index into diff's table of lines)
683    into an actual line number in the input file.
684    The internal line number is LNUM.  FILE points to the data on the file.
685 
686    Internal line numbers count from 0 starting after the prefix.
687    Actual line numbers count from 1 within the entire file.  */
688 
689 int
690 translate_line_number (file, lnum)
691      struct file_data const *file;
692      int lnum;
693 {
694   return lnum + file->prefix_lines + 1;
695 }
696 
697 void
698 translate_range (file, a, b, aptr, bptr)
699      struct file_data const *file;
700      int a, b;
701      int *aptr, *bptr;
702 {
703   *aptr = translate_line_number (file, a - 1) + 1;
704   *bptr = translate_line_number (file, b + 1) - 1;
705 }
706 
707 /* Print a pair of line numbers with SEPCHAR, translated for file FILE.
708    If the two numbers are identical, print just one number.
709 
710    Args A and B are internal line numbers.
711    We print the translated (real) line numbers.  */
712 
713 void
714 print_number_range (sepchar, file, a, b)
715      int sepchar;
716      struct file_data *file;
717      int a, b;
718 {
719   int trans_a, trans_b;
720   translate_range (file, a, b, &trans_a, &trans_b);
721 
722   /* Note: we can have B < A in the case of a range of no lines.
723      In this case, we should print the line number before the range,
724      which is B.  */
725   if (trans_b > trans_a)
726     printf_output ("%d%c%d", trans_a, sepchar, trans_b);
727   else
728     printf_output ("%d", trans_b);
729 }
730 
731 /* Look at a hunk of edit script and report the range of lines in each file
732    that it applies to.  HUNK is the start of the hunk, which is a chain
733    of `struct change'.  The first and last line numbers of file 0 are stored in
734    *FIRST0 and *LAST0, and likewise for file 1 in *FIRST1 and *LAST1.
735    Note that these are internal line numbers that count from 0.
736 
737    If no lines from file 0 are deleted, then FIRST0 is LAST0+1.
738 
739    Also set *DELETES nonzero if any lines of file 0 are deleted
740    and set *INSERTS nonzero if any lines of file 1 are inserted.
741    If only ignorable lines are inserted or deleted, both are
742    set to 0.  */
743 
744 void
745 analyze_hunk (hunk, first0, last0, first1, last1, deletes, inserts)
746      struct change *hunk;
747      int *first0, *last0, *first1, *last1;
748      int *deletes, *inserts;
749 {
750   int l0, l1, show_from, show_to;
751   int i;
752   int trivial = ignore_blank_lines_flag || ignore_regexp_list;
753   struct change *next;
754 
755   show_from = show_to = 0;
756 
757   *first0 = hunk->line0;
758   *first1 = hunk->line1;
759 
760   next = hunk;
761   do
762     {
763       l0 = next->line0 + next->deleted - 1;
764       l1 = next->line1 + next->inserted - 1;
765       show_from += next->deleted;
766       show_to += next->inserted;
767 
768       for (i = next->line0; i <= l0 && trivial; i++)
769 	if (!ignore_blank_lines_flag || files[0].linbuf[i][0] != '\n')
770 	  {
771 	    struct regexp_list *r;
772 	    char const *line = files[0].linbuf[i];
773 	    int len = files[0].linbuf[i + 1] - line;
774 
775 	    for (r = ignore_regexp_list; r; r = r->next)
776 	      if (0 <= re_search (&r->buf, line, len, 0, len, 0))
777 		break;	/* Found a match.  Ignore this line.  */
778 	    /* If we got all the way through the regexp list without
779 	       finding a match, then it's nontrivial.  */
780 	    if (!r)
781 	      trivial = 0;
782 	  }
783 
784       for (i = next->line1; i <= l1 && trivial; i++)
785 	if (!ignore_blank_lines_flag || files[1].linbuf[i][0] != '\n')
786 	  {
787 	    struct regexp_list *r;
788 	    char const *line = files[1].linbuf[i];
789 	    int len = files[1].linbuf[i + 1] - line;
790 
791 	    for (r = ignore_regexp_list; r; r = r->next)
792 	      if (0 <= re_search (&r->buf, line, len, 0, len, 0))
793 		break;	/* Found a match.  Ignore this line.  */
794 	    /* If we got all the way through the regexp list without
795 	       finding a match, then it's nontrivial.  */
796 	    if (!r)
797 	      trivial = 0;
798 	  }
799     }
800   while ((next = next->link) != 0);
801 
802   *last0 = l0;
803   *last1 = l1;
804 
805   /* If all inserted or deleted lines are ignorable,
806      tell the caller to ignore this hunk.  */
807 
808   if (trivial)
809     show_from = show_to = 0;
810 
811   *deletes = show_from;
812   *inserts = show_to;
813 }
814 
815 /* Concatenate three strings, returning a newly malloc'd string.  */
816 
817 char *
818 concat (s1, s2, s3)
819      char const *s1, *s2, *s3;
820 {
821   size_t len = strlen (s1) + strlen (s2) + strlen (s3);
822   char *new = xmalloc (len + 1);
823   sprintf (new, "%s%s%s", s1, s2, s3);
824   return new;
825 }
826 
827 /* Yield the newly malloc'd pathname
828    of the file in DIR whose filename is FILE.  */
829 
830 char *
831 dir_file_pathname (dir, file)
832      char const *dir, *file;
833 {
834   char const *p = filename_lastdirchar (dir);
835   return concat (dir, "/" + (p && !p[1]), file);
836 }
837 
838 void
839 debug_script (sp)
840      struct change *sp;
841 {
842   fflush (stdout);
843   for (; sp; sp = sp->link)
844     fprintf (stderr, "%3d %3d delete %d insert %d\n",
845 	     sp->line0, sp->line1, sp->deleted, sp->inserted);
846   fflush (stderr);
847 }
848