xref: /openbsd/gnu/usr.bin/cvs/src/rcscmds.c (revision 846414fd)
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
call_diff_setup(prog)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
call_diff_arg(s)116 call_diff_arg (s)
117     const char *s;
118 {
119     call_diff_add_arg (s);
120 }
121 
122 static void
call_diff_add_arg(s)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
call_diff_write_output(text,len)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
call_diff_flush_output()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
call_diff_write_stdout(text)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
call_diff_error(format,a1,a2)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
call_diff(out)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
call_diff3(out)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
RCS_merge(rcs,path,workfile,options,rev1,rev2)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
RCS_exec_rcsdiff(rcsfile,opts,options,rev1,rev2,label1,label2,workfile)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     status = RCS_checkout (rcsfile, NULL, rev1, NULL, options, tmpfile1,
410 			   (RCSCHECKOUTPROC)0, NULL);
411     if (status > 0)
412     {
413 	retval = status;
414 	goto error_return;
415     }
416     else if (status < 0)
417     {
418 	error (0, errno,
419 	       "cannot check out revision %s of %s", rev1, rcsfile->path);
420 	retval = 1;
421 	goto error_return;
422     }
423 
424     if (rev2 == NULL)
425     {
426 	assert (workfile != NULL);
427 	use_file2 = workfile;
428     }
429     else
430     {
431 	tmpfile2 = cvs_temp_name ();
432 	status = RCS_checkout (rcsfile, NULL, rev2, NULL, options,
433 			       tmpfile2, (RCSCHECKOUTPROC)0, NULL);
434 	if (status > 0)
435 	{
436 	    retval = status;
437 	    goto error_return;
438 	}
439 	else if (status < 0)
440 	{
441 	    error (0, errno,
442 		   "cannot check out revision %s of %s", rev2, rcsfile->path);
443 	    return 1;
444 	}
445 	use_file2 = tmpfile2;
446     }
447 
448     RCS_output_diff_options (opts, rev1, rev2, workfile);
449     status = diff_execv (tmpfile1, use_file2, label1, label2, opts, RUN_TTY);
450     if (status >= 0)
451     {
452 	retval = status;
453 	goto error_return;
454     }
455     else if (status < 0)
456     {
457 	error (0, errno,
458 	       "cannot diff %s and %s", tmpfile1, use_file2);
459 	retval = 1;
460 	goto error_return;
461     }
462 
463  error_return:
464     {
465 	int save_noexec = noexec;
466 	noexec = 0;
467 	if (unlink_file (tmpfile1) < 0)
468 	{
469 	    if (!existence_error (errno))
470 		error (0, errno, "cannot remove temp file %s", tmpfile1);
471 	}
472 	noexec = save_noexec;
473     }
474     free (tmpfile1);
475     if (tmpfile2 != NULL)
476     {
477 	int save_noexec = noexec;
478 	noexec = 0;
479 	if (unlink_file (tmpfile2) < 0)
480 	{
481 	    if (!existence_error (errno))
482 		error (0, errno, "cannot remove temp file %s", tmpfile2);
483 	}
484 	noexec = save_noexec;
485 	free (tmpfile2);
486     }
487 
488     return retval;
489 }
490 
491 
492 /* Show differences between two files.  This is the start of a diff library.
493 
494    Some issues:
495 
496    * Should option parsing be part of the library or the caller?  The
497    former allows the library to add options without changing the callers,
498    but it causes various problems.  One is that something like --brief really
499    wants special handling in CVS, and probably the caller should retain
500    some flexibility in this area.  Another is online help (the library could
501    have some feature for providing help, but how does that interact with
502    the help provided by the caller directly?).  Another is that as things
503    stand currently, there is no separate namespace for diff options versus
504    "cvs diff" options like -l (that is, if the library adds an option which
505    conflicts with a CVS option, it is trouble).
506 
507    * This isn't required for a first-cut diff library, but if there
508    would be a way for the caller to specify the timestamps that appear
509    in the diffs (rather than the library getting them from the files),
510    that would clean up the kludgy utime() calls in patch.c.
511 
512    Show differences between FILE1 and FILE2.  Either one can be
513    DEVNULL to indicate a nonexistent file (same as an empty file
514    currently, I suspect, but that may be an issue in and of itself).
515    OPTIONS is a list of diff options, or "" if none.  At a minimum,
516    CVS expects that -c (update.c, patch.c) and -n (update.c) will be
517    supported.  Other options, like -u, --speed-large-files, &c, will
518    be specified if the user specified them.
519 
520    OUT is a filename to send the diffs to, or RUN_TTY to send them to
521    stdout.  Error messages go to stderr.  Return value is 0 for
522    success, -1 for a failure which set errno, 1 for success (and some
523    differences were found), or >1 for a failure which printed a
524    message on stderr.  */
525 
526 int
diff_exec(file1,file2,label1,label2,options,out)527 diff_exec (file1, file2, label1, label2, options, out)
528     char *file1;
529     char *file2;
530     char *label1;
531     char *label2;
532     char *options;
533     char *out;
534 {
535     char *args;
536 
537 #ifdef PRESERVE_PERMISSIONS_SUPPORT
538     /* If either file1 or file2 are special files, pretend they are
539        /dev/null.  Reason: suppose a file that represents a block
540        special device in one revision becomes a regular file.  CVS
541        must find the `difference' between these files, but a special
542        file contains no data useful for calculating this metric.  The
543        safe thing to do is to treat the special file as an empty file,
544        thus recording the regular file's full contents.  Doing so will
545        create extremely large deltas at the point of transition
546        between device files and regular files, but this is probably
547        very rare anyway.
548 
549        There may be ways around this, but I think they are fraught
550        with danger. -twp */
551 
552     if (preserve_perms &&
553 	strcmp (file1, DEVNULL) != 0 &&
554 	strcmp (file2, DEVNULL) != 0)
555     {
556 	struct stat sb1, sb2;
557 
558 	if (CVS_LSTAT (file1, &sb1) < 0)
559 	    error (1, errno, "cannot get file information for %s", file1);
560 	if (CVS_LSTAT (file2, &sb2) < 0)
561 	    error (1, errno, "cannot get file information for %s", file2);
562 
563 	if (!S_ISREG (sb1.st_mode) && !S_ISDIR (sb1.st_mode))
564 	    file1 = DEVNULL;
565 	if (!S_ISREG (sb2.st_mode) && !S_ISDIR (sb2.st_mode))
566 	    file2 = DEVNULL;
567     }
568 #endif
569 
570     args = xmalloc (strlen (options) + 10);
571     /* The first word in this string is used only for error reporting. */
572     sprintf (args, "diff %s", options);
573     call_diff_setup (args);
574     if (label1)
575 	call_diff_arg (label1);
576     if (label2)
577 	call_diff_arg (label2);
578     call_diff_arg (file1);
579     call_diff_arg (file2);
580     free (args);
581 
582     return call_diff (out);
583 }
584 
585 int
diff_execv(file1,file2,label1,label2,options,out)586 diff_execv (file1, file2, label1, label2, options, out)
587     char *file1;
588     char *file2;
589     char *label1;
590     char *label2;
591     char *options;
592     char *out;
593 {
594     char *args;
595 
596 #ifdef PRESERVE_PERMISSIONS_SUPPORT
597     /* Pretend that special files are /dev/null for purposes of making
598        diffs.  See comments in diff_exec. */
599 
600     if (preserve_perms &&
601 	strcmp (file1, DEVNULL) != 0 &&
602 	strcmp (file2, DEVNULL) != 0)
603     {
604 	struct stat sb1, sb2;
605 
606 	if (CVS_LSTAT (file1, &sb1) < 0)
607 	    error (1, errno, "cannot get file information for %s", file1);
608 	if (CVS_LSTAT (file2, &sb2) < 0)
609 	    error (1, errno, "cannot get file information for %s", file2);
610 
611 	if (!S_ISREG (sb1.st_mode) && !S_ISDIR (sb1.st_mode))
612 	    file1 = DEVNULL;
613 	if (!S_ISREG (sb2.st_mode) && !S_ISDIR (sb2.st_mode))
614 	    file2 = DEVNULL;
615     }
616 #endif
617 
618     args = xmalloc (strlen (options) + 10);
619     /* The first word in this string is used only for error reporting.  */
620     /* I guess we are pretty confident that options starts with a space.  */
621     sprintf (args, "diff%s", options);
622     call_diff_setup (args);
623     if (label1)
624 	call_diff_arg (label1);
625     if (label2)
626 	call_diff_arg (label2);
627     call_diff_arg (file1);
628     call_diff_arg (file2);
629     free (args);
630 
631     return call_diff (out);
632 }
633 
634 /* Print the options passed to DIFF, in the format used by rcsdiff.
635    The rcsdiff code that produces this output is extremely hairy, and
636    it is not clear how rcsdiff decides which options to print and
637    which not to print.  The code below reproduces every rcsdiff run
638    that I have seen. */
639 
640 static void
RCS_output_diff_options(opts,rev1,rev2,workfile)641 RCS_output_diff_options (opts, rev1, rev2, workfile)
642     char *opts;
643     char *rev1;
644     char *rev2;
645     char *workfile;
646 {
647     char *tmp;
648 
649     tmp = (char *) xmalloc (strlen (opts) + strlen (rev1) + 10);
650 
651     sprintf (tmp, "diff%s -r%s", opts, rev1);
652     cvs_output (tmp, 0);
653     free (tmp);
654 
655     if (rev2)
656     {
657 	cvs_output (" -r", 3);
658 	cvs_output (rev2, 0);
659     }
660     else
661     {
662 	assert (workfile != NULL);
663 	cvs_output (" ", 1);
664 	cvs_output (workfile, 0);
665     }
666     cvs_output ("\n", 1);
667 }
668