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