xref: /openbsd/gnu/usr.bin/cvs/src/rcscmds.c (revision 09467b48)
1 /*
2  * Copyright (c) 1992, Brian Berliner and Jeff Polk
3  * Copyright (c) 1989-1992, Brian Berliner
4  *
5  * You may distribute under the terms of the GNU General Public License as
6  * specified in the README file that comes with the CVS source distribution.
7  *
8  * The functions in this file provide an interface for performing
9  * operations directly on RCS files.
10  */
11 
12 #include "cvs.h"
13 #include <assert.h>
14 #include <stdio.h>
15 #include "diffrun.h"
16 
17 /* This file, rcs.h, and rcs.c, together sometimes known as the "RCS
18    library", are intended to define our interface to RCS files.
19 
20    Whether there will also be a version of RCS which uses this
21    library, or whether the library will be packaged for uses beyond
22    CVS or RCS (many people would like such a thing) is an open
23    question.  Some considerations:
24 
25    1.  An RCS library for CVS must have the capabilities of the
26    existing CVS code which accesses RCS files.  In particular, simple
27    approaches will often be slow.
28 
29    2.  An RCS library should not use code from the current RCS
30    (5.7 and its ancestors).  The code has many problems.  Too few
31    comments, too many layers of abstraction, too many global variables
32    (the correct number for a library is zero), too much intricately
33    interwoven functionality, and too many clever hacks.  Paul Eggert,
34    the current RCS maintainer, agrees.
35 
36    3.  More work needs to be done in terms of separating out the RCS
37    library from the rest of CVS (for example, cvs_output should be
38    replaced by a callback, and the declarations should be centralized
39    into rcs.h, and probably other such cleanups).
40 
41    4.  To be useful for RCS and perhaps for other uses, the library
42    may need features beyond those needed by CVS.
43 
44    5.  Any changes to the RCS file format *must* be compatible.  Many,
45    many tools (not just CVS and RCS) can at least import this format.
46    RCS and CVS must preserve the current ability to import/export it
47    (preferably improved--magic branches are currently a roadblock).
48    See doc/RCSFILES in the CVS distribution for documentation of this
49    file format.
50 
51    On a related note, see the comments at diff_exec, later in this file,
52    for more on the diff library.  */
53 
54 static void RCS_output_diff_options PROTO ((char *, char *, char *, char *));
55 
56 
57 /* Stuff to deal with passing arguments the way libdiff.a wants to deal
58    with them.  This is a crufty interface; there is no good reason for it
59    to resemble a command line rather than something closer to "struct
60    log_data" in log.c.  */
61 
62 /* First call call_diff_setup to setup any initial arguments.  The
63    argument will be parsed into whitespace separated words and added
64    to the global call_diff_argv list.
65 
66    Then, optionally, call call_diff_arg for each additional argument
67    that you'd like to pass to the diff library.
68 
69    Finally, call call_diff or call_diff3 to produce the diffs.  */
70 
71 static char **call_diff_argv;
72 static int call_diff_argc;
73 static int call_diff_argc_allocated;
74 
75 static void call_diff_add_arg PROTO ((const char *));
76 static void call_diff_setup PROTO ((const char *prog));
77 static int call_diff PROTO ((char *out));
78 static int call_diff3 PROTO ((char *out));
79 
80 static void call_diff_write_output PROTO((const char *, size_t));
81 static void call_diff_flush_output PROTO((void));
82 static void call_diff_write_stdout PROTO((const char *));
83 static void call_diff_error PROTO((const char *, const char *, const char *));
84 
85 /* VARARGS */
86 static void
87 call_diff_setup (prog)
88     const char *prog;
89 {
90     char *cp;
91     int i;
92     char *call_diff_prog;
93 
94     /* clean out any malloc'ed values from call_diff_argv */
95     for (i = 0; i < call_diff_argc; i++)
96     {
97 	if (call_diff_argv[i])
98 	{
99 	    free (call_diff_argv[i]);
100 	    call_diff_argv[i] = (char *) 0;
101 	}
102     }
103     call_diff_argc = 0;
104 
105     call_diff_prog = xstrdup (prog);
106 
107     /* put each word into call_diff_argv, allocating it as we go */
108     for (cp = strtok (call_diff_prog, " \t");
109 	 cp != NULL;
110 	 cp = strtok ((char *) NULL, " \t"))
111 	call_diff_add_arg (cp);
112     free (call_diff_prog);
113 }
114 
115 static void
116 call_diff_arg (s)
117     const char *s;
118 {
119     call_diff_add_arg (s);
120 }
121 
122 static void
123 call_diff_add_arg (s)
124     const char *s;
125 {
126     /* allocate more argv entries if we've run out */
127     if (call_diff_argc >= call_diff_argc_allocated)
128     {
129 	call_diff_argc_allocated += 50;
130 	call_diff_argv = (char **)
131 	    xrealloc ((char *) call_diff_argv,
132 		      call_diff_argc_allocated * sizeof (char **));
133     }
134 
135     if (s)
136 	call_diff_argv[call_diff_argc++] = xstrdup (s);
137     else
138 	/* Not post-incremented on purpose!  */
139 	call_diff_argv[call_diff_argc] = (char *) 0;
140 }
141 
142 /* Callback function for the diff library to write data to the output
143    file.  This is used when we are producing output to stdout.  */
144 
145 static void
146 call_diff_write_output (text, len)
147     const char *text;
148     size_t len;
149 {
150     if (len > 0)
151 	cvs_output (text, len);
152 }
153 
154 /* Call back function for the diff library to flush the output file.
155    This is used when we are producing output to stdout.  */
156 
157 static void
158 call_diff_flush_output ()
159 {
160     cvs_flushout ();
161 }
162 
163 /* Call back function for the diff library to write to stdout.  */
164 
165 static void
166 call_diff_write_stdout (text)
167     const char *text;
168 {
169     cvs_output (text, 0);
170 }
171 
172 /* Call back function for the diff library to write to stderr.  */
173 
174 static void
175 call_diff_error (format, a1, a2)
176     const char *format;
177     const char *a1;
178     const char *a2;
179 {
180     /* FIXME: Should we somehow indicate that this error is coming from
181        the diff library?  */
182     error (0, 0, format, a1, a2);
183 }
184 
185 /* This set of callback functions is used if we are sending the diff
186    to stdout.  */
187 
188 static struct diff_callbacks call_diff_stdout_callbacks =
189 {
190     call_diff_write_output,
191     call_diff_flush_output,
192     call_diff_write_stdout,
193     call_diff_error
194 };
195 
196 /* This set of callback functions is used if we are sending the diff
197    to a file.  */
198 
199 static struct diff_callbacks call_diff_file_callbacks =
200 {
201     (void (*) PROTO((const char *, size_t))) NULL,
202     (void (*) PROTO((void))) NULL,
203     call_diff_write_stdout,
204     call_diff_error
205 };
206 
207 static int
208 call_diff (out)
209     char *out;
210 {
211     if (out == RUN_TTY)
212 	return diff_run (call_diff_argc, call_diff_argv, NULL,
213 			 &call_diff_stdout_callbacks);
214     else
215 	return diff_run (call_diff_argc, call_diff_argv, out,
216 			 &call_diff_file_callbacks);
217 }
218 
219 static int
220 call_diff3 (out)
221     char *out;
222 {
223     if (out == RUN_TTY)
224 	return diff3_run (call_diff_argc, call_diff_argv, NULL,
225 			  &call_diff_stdout_callbacks);
226     else
227 	return diff3_run (call_diff_argc, call_diff_argv, out,
228 			  &call_diff_file_callbacks);
229 }
230 
231 
232 
233 /* Merge revisions REV1 and REV2. */
234 
235 int
236 RCS_merge(rcs, path, workfile, options, rev1, rev2)
237     RCSNode *rcs;
238     char *path;
239     char *workfile;
240     char *options;
241     char *rev1;
242     char *rev2;
243 {
244     char *xrev1, *xrev2;
245     char *tmp1, *tmp2;
246     char *diffout = NULL;
247     int retval;
248 
249     if (options != NULL && options[0] != '\0')
250       assert (options[0] == '-' && options[1] == 'k');
251 
252     cvs_output ("RCS file: ", 0);
253     cvs_output (rcs->path, 0);
254     cvs_output ("\n", 1);
255 
256     /* Calculate numeric revision numbers from rev1 and rev2 (may be
257        symbolic). */
258     xrev1 = RCS_gettag (rcs, rev1, 0, NULL);
259     xrev2 = RCS_gettag (rcs, rev2, 0, NULL);
260 
261     /* Check out chosen revisions.  The error message when RCS_checkout
262        fails is not very informative -- it is taken verbatim from RCS 5.7,
263        and relies on RCS_checkout saying something intelligent upon failure. */
264     cvs_output ("retrieving revision ", 0);
265     cvs_output (xrev1, 0);
266     cvs_output ("\n", 1);
267 
268     tmp1 = cvs_temp_name();
269     if (RCS_checkout (rcs, NULL, xrev1, rev1, options, tmp1,
270 		      (RCSCHECKOUTPROC)0, NULL))
271     {
272 	cvs_outerr ("rcsmerge: co failed\n", 0);
273 	error_exit();
274     }
275 
276     cvs_output ("retrieving revision ", 0);
277     cvs_output (xrev2, 0);
278     cvs_output ("\n", 1);
279 
280     tmp2 = cvs_temp_name();
281     if (RCS_checkout (rcs, NULL, xrev2, rev2, options, tmp2,
282 		      (RCSCHECKOUTPROC)0, NULL))
283     {
284 	cvs_outerr ("rcsmerge: co failed\n", 0);
285 	error_exit();
286     }
287 
288     /* Merge changes. */
289     cvs_output ("Merging differences between ", 0);
290     cvs_output (xrev1, 0);
291     cvs_output (" and ", 0);
292     cvs_output (xrev2, 0);
293     cvs_output (" into ", 0);
294     cvs_output (workfile, 0);
295     cvs_output ("\n", 1);
296 
297     /* Remember that the first word in the `call_diff_setup' string is used now
298        only for diagnostic messages -- CVS no longer forks to run diff3. */
299     diffout = cvs_temp_name();
300     call_diff_setup ("diff3");
301     call_diff_arg ("-E");
302     call_diff_arg ("-am");
303 
304     call_diff_arg ("-L");
305     call_diff_arg (workfile);
306     call_diff_arg ("-L");
307     call_diff_arg (xrev1);
308     call_diff_arg ("-L");
309     call_diff_arg (xrev2);
310 
311     call_diff_arg (workfile);
312     call_diff_arg (tmp1);
313     call_diff_arg (tmp2);
314 
315     retval = call_diff3 (diffout);
316 
317     if (retval == 1)
318 	cvs_outerr ("rcsmerge: warning: conflicts during merge\n", 0);
319     else if (retval == 2)
320 	error_exit();
321 
322     if (diffout)
323 	copy_file (diffout, workfile);
324 
325     /* Clean up. */
326     {
327 	int save_noexec = noexec;
328 	noexec = 0;
329 	if (unlink_file (tmp1) < 0)
330 	{
331 	    if (!existence_error (errno))
332 		error (0, errno, "cannot remove temp file %s", tmp1);
333 	}
334 	free (tmp1);
335 	if (unlink_file (tmp2) < 0)
336 	{
337 	    if (!existence_error (errno))
338 		error (0, errno, "cannot remove temp file %s", tmp2);
339 	}
340 	free (tmp2);
341 	if (diffout)
342 	{
343 	    if (unlink_file (diffout) < 0)
344 	    {
345 		if (!existence_error (errno))
346 		    error (0, errno, "cannot remove temp file %s", diffout);
347 	    }
348 	    free (diffout);
349 	}
350 	free (xrev1);
351 	free (xrev2);
352 	noexec = save_noexec;
353     }
354 
355     return retval;
356 }
357 
358 /* Diff revisions and/or files.  OPTS controls the format of the diff
359    (it contains options such as "-w -c", &c), or "" for the default.
360    OPTIONS controls keyword expansion, as a string starting with "-k",
361    or "" to use the default.  REV1 is the first revision to compare
362    against; it must be non-NULL.  If REV2 is non-NULL, compare REV1
363    and REV2; if REV2 is NULL compare REV1 with the file in the working
364    directory, whose name is WORKFILE.  LABEL1 and LABEL2 are default
365    file labels, and (if non-NULL) should be added as -L options
366    to diff.  Output goes to stdout.
367 
368    Return value is 0 for success, -1 for a failure which set errno,
369    or positive for a failure which printed a message on stderr.
370 
371    This used to exec rcsdiff, but now calls RCS_checkout and diff_exec.
372 
373    An issue is what timezone is used for the dates which appear in the
374    diff output.  rcsdiff uses the -z flag, which is not presently
375    processed by CVS diff, but I'm not sure exactly how hard to worry
376    about this--any such features are undocumented in the context of
377    CVS, and I'm not sure how important to users.  */
378 int
379 RCS_exec_rcsdiff (rcsfile, opts, options, rev1, rev2, label1, label2, workfile)
380     RCSNode *rcsfile;
381     char *opts;
382     char *options;
383     char *rev1;
384     char *rev2;
385     char *label1;
386     char *label2;
387     char *workfile;
388 {
389     char *tmpfile1;
390     char *tmpfile2;
391     char *use_file2;
392     int status, retval;
393 
394     tmpfile1 = cvs_temp_name ();
395     tmpfile2 = NULL;
396 
397     cvs_output ("\
398 ===================================================================\n\
399 RCS file: ", 0);
400     cvs_output (rcsfile->path, 0);
401     cvs_output ("\n", 1);
402 
403     /* Historically, `cvs diff' has expanded the $Name keyword to the
404        empty string when checking out revisions.  This is an accident,
405        but no one has considered the issue thoroughly enough to determine
406        what the best behavior is.  Passing NULL for the `nametag' argument
407        preserves the existing behavior. */
408 
409     cvs_output ("retrieving revision ", 0);
410     cvs_output (rev1, 0);
411     cvs_output ("\n", 1);
412     status = RCS_checkout (rcsfile, NULL, rev1, NULL, options, tmpfile1,
413 			   (RCSCHECKOUTPROC)0, NULL);
414     if (status > 0)
415     {
416 	retval = status;
417 	goto error_return;
418     }
419     else if (status < 0)
420     {
421 	error (0, errno,
422 	       "cannot check out revision %s of %s", rev1, rcsfile->path);
423 	retval = 1;
424 	goto error_return;
425     }
426 
427     if (rev2 == NULL)
428     {
429 	assert (workfile != NULL);
430 	use_file2 = workfile;
431     }
432     else
433     {
434 	tmpfile2 = cvs_temp_name ();
435 	cvs_output ("retrieving revision ", 0);
436 	cvs_output (rev2, 0);
437 	cvs_output ("\n", 1);
438 	status = RCS_checkout (rcsfile, NULL, rev2, NULL, options,
439 			       tmpfile2, (RCSCHECKOUTPROC)0, NULL);
440 	if (status > 0)
441 	{
442 	    retval = status;
443 	    goto error_return;
444 	}
445 	else if (status < 0)
446 	{
447 	    error (0, errno,
448 		   "cannot check out revision %s of %s", rev2, rcsfile->path);
449 	    return 1;
450 	}
451 	use_file2 = tmpfile2;
452     }
453 
454     RCS_output_diff_options (opts, rev1, rev2, workfile);
455     status = diff_execv (tmpfile1, use_file2, label1, label2, opts, RUN_TTY);
456     if (status >= 0)
457     {
458 	retval = status;
459 	goto error_return;
460     }
461     else if (status < 0)
462     {
463 	error (0, errno,
464 	       "cannot diff %s and %s", tmpfile1, use_file2);
465 	retval = 1;
466 	goto error_return;
467     }
468 
469  error_return:
470     {
471 	int save_noexec = noexec;
472 	noexec = 0;
473 	if (unlink_file (tmpfile1) < 0)
474 	{
475 	    if (!existence_error (errno))
476 		error (0, errno, "cannot remove temp file %s", tmpfile1);
477 	}
478 	noexec = save_noexec;
479     }
480     free (tmpfile1);
481     if (tmpfile2 != NULL)
482     {
483 	int save_noexec = noexec;
484 	noexec = 0;
485 	if (unlink_file (tmpfile2) < 0)
486 	{
487 	    if (!existence_error (errno))
488 		error (0, errno, "cannot remove temp file %s", tmpfile2);
489 	}
490 	noexec = save_noexec;
491 	free (tmpfile2);
492     }
493 
494     return retval;
495 }
496 
497 
498 /* Show differences between two files.  This is the start of a diff library.
499 
500    Some issues:
501 
502    * Should option parsing be part of the library or the caller?  The
503    former allows the library to add options without changing the callers,
504    but it causes various problems.  One is that something like --brief really
505    wants special handling in CVS, and probably the caller should retain
506    some flexibility in this area.  Another is online help (the library could
507    have some feature for providing help, but how does that interact with
508    the help provided by the caller directly?).  Another is that as things
509    stand currently, there is no separate namespace for diff options versus
510    "cvs diff" options like -l (that is, if the library adds an option which
511    conflicts with a CVS option, it is trouble).
512 
513    * This isn't required for a first-cut diff library, but if there
514    would be a way for the caller to specify the timestamps that appear
515    in the diffs (rather than the library getting them from the files),
516    that would clean up the kludgy utime() calls in patch.c.
517 
518    Show differences between FILE1 and FILE2.  Either one can be
519    DEVNULL to indicate a nonexistent file (same as an empty file
520    currently, I suspect, but that may be an issue in and of itself).
521    OPTIONS is a list of diff options, or "" if none.  At a minimum,
522    CVS expects that -c (update.c, patch.c) and -n (update.c) will be
523    supported.  Other options, like -u, --speed-large-files, &c, will
524    be specified if the user specified them.
525 
526    OUT is a filename to send the diffs to, or RUN_TTY to send them to
527    stdout.  Error messages go to stderr.  Return value is 0 for
528    success, -1 for a failure which set errno, 1 for success (and some
529    differences were found), or >1 for a failure which printed a
530    message on stderr.  */
531 
532 int
533 diff_exec (file1, file2, label1, label2, options, out)
534     char *file1;
535     char *file2;
536     char *label1;
537     char *label2;
538     char *options;
539     char *out;
540 {
541     char *args;
542 
543 #ifdef PRESERVE_PERMISSIONS_SUPPORT
544     /* If either file1 or file2 are special files, pretend they are
545        /dev/null.  Reason: suppose a file that represents a block
546        special device in one revision becomes a regular file.  CVS
547        must find the `difference' between these files, but a special
548        file contains no data useful for calculating this metric.  The
549        safe thing to do is to treat the special file as an empty file,
550        thus recording the regular file's full contents.  Doing so will
551        create extremely large deltas at the point of transition
552        between device files and regular files, but this is probably
553        very rare anyway.
554 
555        There may be ways around this, but I think they are fraught
556        with danger. -twp */
557 
558     if (preserve_perms &&
559 	strcmp (file1, DEVNULL) != 0 &&
560 	strcmp (file2, DEVNULL) != 0)
561     {
562 	struct stat sb1, sb2;
563 
564 	if (CVS_LSTAT (file1, &sb1) < 0)
565 	    error (1, errno, "cannot get file information for %s", file1);
566 	if (CVS_LSTAT (file2, &sb2) < 0)
567 	    error (1, errno, "cannot get file information for %s", file2);
568 
569 	if (!S_ISREG (sb1.st_mode) && !S_ISDIR (sb1.st_mode))
570 	    file1 = DEVNULL;
571 	if (!S_ISREG (sb2.st_mode) && !S_ISDIR (sb2.st_mode))
572 	    file2 = DEVNULL;
573     }
574 #endif
575 
576     args = xmalloc (strlen (options) + 10);
577     /* The first word in this string is used only for error reporting. */
578     sprintf (args, "diff %s", options);
579     call_diff_setup (args);
580     if (label1)
581 	call_diff_arg (label1);
582     if (label2)
583 	call_diff_arg (label2);
584     call_diff_arg (file1);
585     call_diff_arg (file2);
586     free (args);
587 
588     return call_diff (out);
589 }
590 
591 int
592 diff_execv (file1, file2, label1, label2, options, out)
593     char *file1;
594     char *file2;
595     char *label1;
596     char *label2;
597     char *options;
598     char *out;
599 {
600     char *args;
601 
602 #ifdef PRESERVE_PERMISSIONS_SUPPORT
603     /* Pretend that special files are /dev/null for purposes of making
604        diffs.  See comments in diff_exec. */
605 
606     if (preserve_perms &&
607 	strcmp (file1, DEVNULL) != 0 &&
608 	strcmp (file2, DEVNULL) != 0)
609     {
610 	struct stat sb1, sb2;
611 
612 	if (CVS_LSTAT (file1, &sb1) < 0)
613 	    error (1, errno, "cannot get file information for %s", file1);
614 	if (CVS_LSTAT (file2, &sb2) < 0)
615 	    error (1, errno, "cannot get file information for %s", file2);
616 
617 	if (!S_ISREG (sb1.st_mode) && !S_ISDIR (sb1.st_mode))
618 	    file1 = DEVNULL;
619 	if (!S_ISREG (sb2.st_mode) && !S_ISDIR (sb2.st_mode))
620 	    file2 = DEVNULL;
621     }
622 #endif
623 
624     args = xmalloc (strlen (options) + 10);
625     /* The first word in this string is used only for error reporting.  */
626     /* I guess we are pretty confident that options starts with a space.  */
627     sprintf (args, "diff%s", options);
628     call_diff_setup (args);
629     if (label1)
630 	call_diff_arg (label1);
631     if (label2)
632 	call_diff_arg (label2);
633     call_diff_arg (file1);
634     call_diff_arg (file2);
635     free (args);
636 
637     return call_diff (out);
638 }
639 
640 /* Print the options passed to DIFF, in the format used by rcsdiff.
641    The rcsdiff code that produces this output is extremely hairy, and
642    it is not clear how rcsdiff decides which options to print and
643    which not to print.  The code below reproduces every rcsdiff run
644    that I have seen. */
645 
646 static void
647 RCS_output_diff_options (opts, rev1, rev2, workfile)
648     char *opts;
649     char *rev1;
650     char *rev2;
651     char *workfile;
652 {
653     char *tmp;
654 
655     tmp = (char *) xmalloc (strlen (opts) + strlen (rev1) + 10);
656 
657     sprintf (tmp, "diff%s -r%s", opts, rev1);
658     cvs_output (tmp, 0);
659     free (tmp);
660 
661     if (rev2)
662     {
663 	cvs_output (" -r", 3);
664 	cvs_output (rev2, 0);
665     }
666     else
667     {
668 	assert (workfile != NULL);
669 	cvs_output (" ", 1);
670 	cvs_output (workfile, 0);
671     }
672     cvs_output ("\n", 1);
673 }
674