xref: /openbsd/gnu/usr.bin/cvs/src/patch.c (revision ae5e8a9b)
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 1.4 kit.
7  *
8  * Patch
9  *
10  * Create a Larry Wall format "patch" file between a previous release and the
11  * current head of a module, or between two releases.  Can specify the
12  * release as either a date or a revision number.
13  */
14 
15 #include "cvs.h"
16 #include "getline.h"
17 
18 static RETSIGTYPE patch_cleanup PROTO((void));
19 static Dtype patch_dirproc PROTO ((void *callerdat, char *dir,
20 				   char *repos, char *update_dir,
21 				   List *entries));
22 static int patch_fileproc PROTO ((void *callerdat, struct file_info *finfo));
23 static int patch_proc PROTO((int *pargc, char **argv, char *xwhere,
24 		       char *mwhere, char *mfile, int shorten,
25 		       int local_specified, char *mname, char *msg));
26 
27 static int force_tag_match = 1;
28 static int patch_short = 0;
29 static int toptwo_diffs = 0;
30 static int local = 0;
31 static char *options = NULL;
32 static char *rev1 = NULL;
33 static int rev1_validated = 0;
34 static char *rev2 = NULL;
35 static int rev2_validated = 0;
36 static char *date1 = NULL;
37 static char *date2 = NULL;
38 static char *tmpfile1 = NULL;
39 static char *tmpfile2 = NULL;
40 static char *tmpfile3 = NULL;
41 static int unidiff = 0;
42 
43 static const char *const patch_usage[] =
44 {
45     "Usage: %s %s [-fl] [-c|-u] [-s|-t] [-V %%d]\n",
46     "    -r rev|-D date [-r rev2 | -D date2] modules...\n",
47     "\t-f\tForce a head revision match if tag/date not found.\n",
48     "\t-l\tLocal directory only, not recursive\n",
49     "\t-c\tContext diffs (default)\n",
50     "\t-u\tUnidiff format.\n",
51     "\t-s\tShort patch - one liner per file.\n",
52     "\t-t\tTop two diffs - last change made to the file.\n",
53     "\t-D date\tDate.\n",
54     "\t-r rev\tRevision - symbolic or numeric.\n",
55     "\t-V vers\tUse RCS Version \"vers\" for keyword expansion.\n",
56     NULL
57 };
58 
59 int
60 patch (argc, argv)
61     int argc;
62     char **argv;
63 {
64     register int i;
65     int c;
66     int err = 0;
67     DBM *db;
68 
69     if (argc == -1)
70 	usage (patch_usage);
71 
72     optind = 1;
73     while ((c = getopt (argc, argv, "V:k:cuftsQqlRD:r:")) != -1)
74     {
75 	switch (c)
76 	{
77 	    case 'Q':
78 	    case 'q':
79 #ifdef SERVER_SUPPORT
80 		/* The CVS 1.5 client sends these options (in addition to
81 		   Global_option requests), so we must ignore them.  */
82 		if (!server_active)
83 #endif
84 		    error (1, 0,
85 			   "-q or -Q must be specified before \"%s\"",
86 			   command_name);
87 		break;
88 	    case 'f':
89 		force_tag_match = 0;
90 		break;
91 	    case 'l':
92 		local = 1;
93 		break;
94 	    case 'R':
95 		local = 0;
96 		break;
97 	    case 't':
98 		toptwo_diffs = 1;
99 		break;
100 	    case 's':
101 		patch_short = 1;
102 		break;
103 	    case 'D':
104 		if (rev2 != NULL || date2 != NULL)
105 		    error (1, 0,
106 		       "no more than two revisions/dates can be specified");
107 		if (rev1 != NULL || date1 != NULL)
108 		    date2 = Make_Date (optarg);
109 		else
110 		    date1 = Make_Date (optarg);
111 		break;
112 	    case 'r':
113 		if (rev2 != NULL || date2 != NULL)
114 		    error (1, 0,
115 		       "no more than two revisions/dates can be specified");
116 		if (rev1 != NULL || date1 != NULL)
117 		    rev2 = optarg;
118 		else
119 		    rev1 = optarg;
120 		break;
121 	    case 'k':
122 		if (options)
123 		    free (options);
124 		options = RCS_check_kflag (optarg);
125 		break;
126 	    case 'V':
127 		if (atoi (optarg) <= 0)
128 		    error (1, 0, "must specify a version number to -V");
129 		if (options)
130 		    free (options);
131 		options = xmalloc (strlen (optarg) + 1 + 2);	/* for the -V */
132 		(void) sprintf (options, "-V%s", optarg);
133 		break;
134 	    case 'u':
135 		unidiff = 1;		/* Unidiff */
136 		break;
137 	    case 'c':			/* Context diff */
138 		unidiff = 0;
139 		break;
140 	    case '?':
141 	    default:
142 		usage (patch_usage);
143 		break;
144 	}
145     }
146     argc -= optind;
147     argv += optind;
148 
149     /* Sanity checks */
150     if (argc < 1)
151 	usage (patch_usage);
152 
153     if (toptwo_diffs && patch_short)
154 	error (1, 0, "-t and -s options are mutually exclusive");
155     if (toptwo_diffs && (date1 != NULL || date2 != NULL ||
156 			 rev1 != NULL || rev2 != NULL))
157 	error (1, 0, "must not specify revisions/dates with -t option!");
158 
159     if (!toptwo_diffs && (date1 == NULL && date2 == NULL &&
160 			  rev1 == NULL && rev2 == NULL))
161 	error (1, 0, "must specify at least one revision/date!");
162     if (date1 != NULL && date2 != NULL)
163 	if (RCS_datecmp (date1, date2) >= 0)
164 	    error (1, 0, "second date must come after first date!");
165 
166     /* if options is NULL, make it a NULL string */
167     if (options == NULL)
168 	options = xstrdup ("");
169 
170 #ifdef CLIENT_SUPPORT
171     if (client_active)
172     {
173 	/* We're the client side.  Fire up the remote server.  */
174 	start_server ();
175 
176 	ign_setup ();
177 
178 	if (local)
179 	    send_arg("-l");
180 	if (!force_tag_match)
181 	    send_arg("-f");
182 	if (toptwo_diffs)
183 	    send_arg("-t");
184 	if (patch_short)
185 	    send_arg("-s");
186 	if (unidiff)
187 	    send_arg("-u");
188 
189 	if (rev1)
190 	    option_with_arg ("-r", rev1);
191 	if (date1)
192 	    client_senddate (date1);
193 	if (rev2)
194 	    option_with_arg ("-r", rev2);
195 	if (date2)
196 	    client_senddate (date2);
197 	if (options[0] != '\0')
198 	    send_arg (options);
199 
200 	{
201 	    int i;
202 	    for (i = 0; i < argc; ++i)
203 		send_arg (argv[i]);
204 	}
205 
206 	send_to_server ("rdiff\012", 0);
207         return get_responses_and_close ();
208     }
209 #endif
210 
211     /* clean up if we get a signal */
212 #ifdef SIGHUP
213     (void) SIG_register (SIGHUP, patch_cleanup);
214 #endif
215 #ifdef SIGINT
216     (void) SIG_register (SIGINT, patch_cleanup);
217 #endif
218 #ifdef SIGQUIT
219     (void) SIG_register (SIGQUIT, patch_cleanup);
220 #endif
221 #ifdef SIGPIPE
222     (void) SIG_register (SIGPIPE, patch_cleanup);
223 #endif
224 #ifdef SIGTERM
225     (void) SIG_register (SIGTERM, patch_cleanup);
226 #endif
227 
228     db = open_module ();
229     for (i = 0; i < argc; i++)
230 	err += do_module (db, argv[i], PATCH, "Patching", patch_proc,
231 			  (char *) NULL, 0, 0, 0, (char *) NULL);
232     close_module (db);
233     free (options);
234     patch_cleanup ();
235     return (err);
236 }
237 
238 /*
239  * callback proc for doing the real work of patching
240  */
241 /* ARGSUSED */
242 static char where[PATH_MAX];
243 static int
244 patch_proc (pargc, argv, xwhere, mwhere, mfile, shorten, local_specified,
245 	    mname, msg)
246     int *pargc;
247     char **argv;
248     char *xwhere;
249     char *mwhere;
250     char *mfile;
251     int shorten;
252     int local_specified;
253     char *mname;
254     char *msg;
255 {
256     int err = 0;
257     int which;
258     char repository[PATH_MAX];
259 
260     (void) sprintf (repository, "%s/%s", CVSroot_directory, argv[0]);
261     (void) strcpy (where, argv[0]);
262 
263     /* if mfile isn't null, we need to set up to do only part of the module */
264     if (mfile != NULL)
265     {
266 	char *cp;
267 	char path[PATH_MAX];
268 
269 	/* if the portion of the module is a path, put the dir part on repos */
270 	if ((cp = strrchr (mfile, '/')) != NULL)
271 	{
272 	    *cp = '\0';
273 	    (void) strcat (repository, "/");
274 	    (void) strcat (repository, mfile);
275 	    (void) strcat (where, "/");
276 	    (void) strcat (where, mfile);
277 	    mfile = cp + 1;
278 	}
279 
280 	/* take care of the rest */
281 	(void) sprintf (path, "%s/%s", repository, mfile);
282 	if (isdir (path))
283 	{
284 	    /* directory means repository gets the dir tacked on */
285 	    (void) strcpy (repository, path);
286 	    (void) strcat (where, "/");
287 	    (void) strcat (where, mfile);
288 	}
289 	else
290 	{
291 	    int i;
292 
293 	    /* a file means muck argv */
294 	    for (i = 1; i < *pargc; i++)
295 		free (argv[i]);
296 	    argv[1] = xstrdup (mfile);
297 	    (*pargc) = 2;
298 	}
299     }
300 
301     /* cd to the starting repository */
302     if ( CVS_CHDIR (repository) < 0)
303     {
304 	error (0, errno, "cannot chdir to %s", repository);
305 	return (1);
306     }
307 
308     if (force_tag_match)
309 	which = W_REPOS | W_ATTIC;
310     else
311 	which = W_REPOS;
312 
313     if (rev1 != NULL && !rev1_validated)
314     {
315 	tag_check_valid (rev1, *pargc - 1, argv + 1, local, 0, NULL);
316 	rev1_validated = 1;
317     }
318     if (rev2 != NULL && !rev2_validated)
319     {
320 	tag_check_valid (rev2, *pargc - 1, argv + 1, local, 0, NULL);
321 	rev2_validated = 1;
322     }
323 
324     /* start the recursion processor */
325     err = start_recursion (patch_fileproc, (FILESDONEPROC) NULL, patch_dirproc,
326 			   (DIRLEAVEPROC) NULL, NULL,
327 			   *pargc - 1, argv + 1, local,
328 			   which, 0, 1, where, 1);
329 
330     return (err);
331 }
332 
333 /*
334  * Called to examine a particular RCS file, as appropriate with the options
335  * that were set above.
336  */
337 /* ARGSUSED */
338 static int
339 patch_fileproc (callerdat, finfo)
340     void *callerdat;
341     struct file_info *finfo;
342 {
343     struct utimbuf t;
344     char *vers_tag, *vers_head;
345     char rcsspace[1][PATH_MAX];
346     char *rcs = rcsspace[0];
347     RCSNode *rcsfile;
348     FILE *fp1, *fp2, *fp3;
349     int ret = 0;
350     int isattic = 0;
351     int retcode = 0;
352     char file1[PATH_MAX], file2[PATH_MAX], strippath[PATH_MAX];
353     char *line1, *line2;
354     size_t line1_chars_allocated;
355     size_t line2_chars_allocated;
356     char *cp1, *cp2;
357     FILE *fp;
358 
359     line1 = NULL;
360     line1_chars_allocated = 0;
361     line2 = NULL;
362     line2_chars_allocated = 0;
363 
364     /* find the parsed rcs file */
365     if ((rcsfile = finfo->rcs) == NULL)
366 	return (1);
367     if ((rcsfile->flags & VALID) && (rcsfile->flags & INATTIC))
368 	isattic = 1;
369 
370     (void) sprintf (rcs, "%s%s", finfo->file, RCSEXT);
371 
372     /* if vers_head is NULL, may have been removed from the release */
373     if (isattic && rev2 == NULL && date2 == NULL)
374 	vers_head = NULL;
375     else
376     {
377 	vers_head = RCS_getversion (rcsfile, rev2, date2, force_tag_match,
378 				    (int *) NULL);
379 	if (vers_head != NULL && RCS_isdead (rcsfile, vers_head))
380 	{
381 	    free (vers_head);
382 	    vers_head = NULL;
383 	}
384     }
385 
386     if (toptwo_diffs)
387     {
388 	if (vers_head == NULL)
389 	    return (1);
390 
391 	if (!date1)
392 	    date1 = xmalloc (50);	/* plenty big :-) */
393 	*date1 = '\0';
394 	if (RCS_getrevtime (rcsfile, vers_head, date1, 1) == -1)
395 	{
396 	    if (!really_quiet)
397 		error (0, 0, "cannot find date in rcs file %s revision %s",
398 		       rcs, vers_head);
399 	    return (1);
400 	}
401     }
402     vers_tag = RCS_getversion (rcsfile, rev1, date1, force_tag_match,
403 			       (int *) NULL);
404     if (vers_tag != NULL && RCS_isdead (rcsfile, vers_tag))
405     {
406         free (vers_tag);
407 	vers_tag = NULL;
408     }
409 
410     if (vers_tag == NULL && vers_head == NULL)
411 	return (0);			/* nothing known about specified revs */
412 
413     if (vers_tag && vers_head && strcmp (vers_head, vers_tag) == 0)
414 	return (0);			/* not changed between releases */
415 
416     if (patch_short)
417     {
418 	(void) printf ("File %s ", finfo->fullname);
419 	if (vers_tag == NULL)
420 	    (void) printf ("is new; current revision %s\n", vers_head);
421 	else if (vers_head == NULL)
422 	{
423 	    (void) printf ("is removed; not included in ");
424 	    if (rev2 != NULL)
425 		(void) printf ("release tag %s", rev2);
426 	    else if (date2 != NULL)
427 		(void) printf ("release date %s", date2);
428 	    else
429 		(void) printf ("current release");
430 	    (void) printf ("\n");
431 	}
432 	else
433 	    (void) printf ("changed from revision %s to %s\n",
434 			   vers_tag, vers_head);
435 	return (0);
436     }
437     tmpfile1 = cvs_temp_name ();
438     if ((fp1 = CVS_FOPEN (tmpfile1, "w+")) != NULL)
439 	(void) fclose (fp1);
440     tmpfile2 = cvs_temp_name ();
441     if ((fp2 = CVS_FOPEN (tmpfile2, "w+")) != NULL)
442 	(void) fclose (fp2);
443     tmpfile3 = cvs_temp_name ();
444     if ((fp3 = CVS_FOPEN (tmpfile3, "w+")) != NULL)
445 	(void) fclose (fp3);
446     if (fp1 == NULL || fp2 == NULL || fp3 == NULL)
447     {
448 	/* FIXME: should be printing a proper error message, with errno-based
449 	   message, and the filename which we could not create.  */
450 	error (0, 0, "cannot create temporary files");
451 	ret = 1;
452 	goto out;
453     }
454     if (vers_tag != NULL)
455     {
456 	retcode = RCS_checkout (rcsfile, (char *) NULL, vers_tag,
457 				rev1, options, tmpfile1);
458 	if (retcode != 0)
459 	{
460 	    if (!really_quiet)
461 		error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
462 		       "co of revision %s in %s failed", vers_tag, rcs);
463 	    ret = 1;
464 	    goto out;
465 	}
466 	memset ((char *) &t, 0, sizeof (t));
467 	if ((t.actime = t.modtime = RCS_getrevtime (rcsfile, vers_tag,
468 						    (char *) 0, 0)) != -1)
469 		(void) utime (tmpfile1, &t);
470     }
471     else if (toptwo_diffs)
472     {
473 	ret = 1;
474 	goto out;
475     }
476     if (vers_head != NULL)
477     {
478 	retcode = RCS_checkout (rcsfile, (char *) NULL, vers_head,
479 				rev2, options, tmpfile2);
480 	if (retcode != 0)
481 	{
482 	    if (!really_quiet)
483 		error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
484 		       "co of revision %s in %s failed", vers_head, rcs);
485 	    ret = 1;
486 	    goto out;
487 	}
488 	if ((t.actime = t.modtime = RCS_getrevtime (rcsfile, vers_head,
489 						    (char *) 0, 0)) != -1)
490 		(void) utime (tmpfile2, &t);
491     }
492     run_setup ("%s -%c", DIFF, unidiff ? 'u' : 'c');
493     run_arg (tmpfile1);
494     run_arg (tmpfile2);
495 
496     switch (run_exec (RUN_TTY, tmpfile3, RUN_TTY, RUN_REALLY))
497     {
498 	case -1:			/* fork/wait failure */
499 	    error (1, errno, "fork for diff failed on %s", rcs);
500 	    break;
501 	case 0:				/* nothing to do */
502 	    break;
503 	case 1:
504 	    /*
505 	     * The two revisions are really different, so read the first two
506 	     * lines of the diff output file, and munge them to include more
507 	     * reasonable file names that "patch" will understand.
508 	     */
509 
510 	    /* Output an "Index:" line for patch to use */
511 	    (void) fflush (stdout);
512 	    (void) printf ("Index: %s\n", finfo->fullname);
513 	    (void) fflush (stdout);
514 
515 	    fp = open_file (tmpfile3, "r");
516 	    if (getline (&line1, &line1_chars_allocated, fp) < 0 ||
517 		getline (&line2, &line2_chars_allocated, fp) < 0)
518 	    {
519 		error (0, errno, "failed to read diff file header %s for %s",
520 		       tmpfile3, rcs);
521 		ret = 1;
522 		(void) fclose (fp);
523 		goto out;
524 	    }
525 	    if (!unidiff)
526 	    {
527 		if (strncmp (line1, "*** ", 4) != 0 ||
528 		    strncmp (line2, "--- ", 4) != 0 ||
529 		    (cp1 = strchr (line1, '\t')) == NULL ||
530 		    (cp2 = strchr (line2, '\t')) == NULL)
531 		{
532 		    error (0, 0, "invalid diff header for %s", rcs);
533 		    ret = 1;
534 		    (void) fclose (fp);
535 		    goto out;
536 		}
537 	    }
538 	    else
539 	    {
540 		if (strncmp (line1, "--- ", 4) != 0 ||
541 		    strncmp (line2, "+++ ", 4) != 0 ||
542 		    (cp1 = strchr (line1, '\t')) == NULL ||
543 		    (cp2 = strchr  (line2, '\t')) == NULL)
544 		{
545 		    error (0, 0, "invalid unidiff header for %s", rcs);
546 		    ret = 1;
547 		    (void) fclose (fp);
548 		    goto out;
549 		}
550 	    }
551 	    if (CVSroot_directory != NULL)
552 		(void) sprintf (strippath, "%s/", CVSroot_directory);
553 	    else
554 		(void) strcpy (strippath, REPOS_STRIP);
555 	    if (strncmp (rcs, strippath, strlen (strippath)) == 0)
556 		rcs += strlen (strippath);
557 	    if (vers_tag != NULL)
558 	    {
559 		(void) sprintf (file1, "%s:%s", finfo->fullname, vers_tag);
560 	    }
561 	    else
562 	    {
563 		(void) strcpy (file1, DEVNULL);
564 	    }
565 	    (void) sprintf (file2, "%s:%s", finfo->fullname,
566 			    vers_head ? vers_head : "removed");
567 
568 	    /* Note that this prints "diff" not DIFF.  The format of a diff
569 	       does not depend on the name of the program which happens to
570 	       have produced it.  */
571 	    if (unidiff)
572 	    {
573 		(void) printf ("diff -u %s %s\n", file1, file2);
574 		(void) printf ("--- %s%s+++ ", file1, cp1);
575 	    }
576 	    else
577 	    {
578 		(void) printf ("diff -c %s %s\n", file1, file2);
579 		(void) printf ("*** %s%s--- ", file1, cp1);
580 	    }
581 
582 	    (void) printf ("%s%s", finfo->fullname, cp2);
583 	    /* spew the rest of the diff out */
584 	    while (getline (&line1, &line1_chars_allocated, fp) >= 0)
585 		(void) fputs (line1, stdout);
586 	    (void) fclose (fp);
587 	    break;
588 	default:
589 	    error (0, 0, "diff failed for %s", finfo->fullname);
590     }
591   out:
592     if (line1)
593         free (line1);
594     if (line2)
595         free (line2);
596     /* FIXME: should be checking for errors.  */
597     (void) CVS_UNLINK (tmpfile1);
598     (void) CVS_UNLINK (tmpfile2);
599     (void) CVS_UNLINK (tmpfile3);
600     free (tmpfile1);
601     free (tmpfile2);
602     free (tmpfile3);
603     tmpfile1 = tmpfile2 = tmpfile3 = NULL;
604     return (ret);
605 }
606 
607 /*
608  * Print a warm fuzzy message
609  */
610 /* ARGSUSED */
611 static Dtype
612 patch_dirproc (callerdat, dir, repos, update_dir, entries)
613     void *callerdat;
614     char *dir;
615     char *repos;
616     char *update_dir;
617     List *entries;
618 {
619     if (!quiet)
620 	error (0, 0, "Diffing %s", update_dir);
621     return (R_PROCESS);
622 }
623 
624 /*
625  * Clean up temporary files
626  */
627 static RETSIGTYPE
628 patch_cleanup ()
629 {
630     if (tmpfile1 != NULL)
631     {
632 	(void) unlink_file (tmpfile1);
633 	free (tmpfile1);
634     }
635     if (tmpfile2 != NULL)
636     {
637 	(void) unlink_file (tmpfile2);
638 	free (tmpfile2);
639     }
640     if (tmpfile3 != NULL)
641     {
642 	(void) unlink_file (tmpfile3);
643 	free (tmpfile3);
644     }
645     tmpfile1 = tmpfile2 = tmpfile3 = NULL;
646 }
647