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