2 * Copyright (c) 1992, Brian Berliner and Jeff Polk
3 * Copyright (c) 1989-1992, Brian Berliner
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.3 kit.
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.
18 static char rcsid[] = "@(#)patch.c 1.50 92/04/10";
23 static SIGTYPE patch_cleanup (void);
24 static Dtype patch_dirproc (char *dir, char *repos, char *update_dir);
25 static int patch_fileproc (char *file, char *update_dir, char *repository,
26 List * entries, List * srcfiles);
27 static int patch_proc (int *pargc, char *argv[], char *xwhere,
28 char *mwhere, char *mfile, int shorten,
29 int local_specified, char *mname, char *msg);
31 static int patch_proc ();
32 static int patch_fileproc ();
33 static Dtype patch_dirproc ();
34 static SIGTYPE patch_cleanup ();
37 static int force_tag_match = 1;
38 static int patch_short = 0;
39 static int toptwo_diffs = 0;
41 static char *options = NULL;
42 static char *rev1 = NULL;
43 static char *rev2 = NULL;
44 static char *date1 = NULL;
45 static char *date2 = NULL;
46 static char tmpfile1[L_tmpnam+1], tmpfile2[L_tmpnam+1], tmpfile3[L_tmpnam+1];
47 static int unidiff = 0;
49 static char *patch_usage[] =
51 "Usage: %s %s [-Qflq] [-c|-u] [-s|-t] [-V %%d]\n",
52 " -r rev|-D date [-r rev2 | -D date2] modules...\n",
53 "\t-Q\tReally quiet.\n",
54 "\t-f\tForce a head revision match if tag/date not found.\n",
55 "\t-l\tLocal directory only, not recursive\n",
56 "\t-c\tContext diffs (default)\n",
57 "\t-u\tUnidiff format.\n",
58 "\t-s\tShort patch - one liner per file.\n",
59 "\t-t\tTop two diffs - last change made to the file.\n",
61 "\t-r rev\tRevision - symbolic or numeric.\n",
62 "\t-V vers\tUse RCS Version \"vers\" for keyword expansion.\n",
80 while ((c = gnu_getopt (argc, argv, "V:k:cuftsQqlRD:r:")) != -1)
106 if (rev2 != NULL || date2 != NULL)
108 "no more than two revisions/dates can be specified");
109 if (rev1 != NULL || date1 != NULL)
110 date2 = Make_Date (optarg);
112 date1 = Make_Date (optarg);
115 if (rev2 != NULL || date2 != NULL)
117 "no more than two revisions/dates can be specified");
118 if (rev1 != NULL || date1 != NULL)
126 options = RCS_check_kflag (optarg);
129 if (atoi (optarg) <= 0)
130 error (1, 0, "must specify a version number to -V");
133 options = xmalloc (strlen (optarg) + 1 + 2); /* for the -V */
134 (void) sprintf (options, "-V%s", optarg);
137 unidiff = 1; /* Unidiff */
139 case 'c': /* Context diff */
155 if (toptwo_diffs && patch_short)
156 error (1, 0, "-t and -s options are mutually exclusive");
157 if (toptwo_diffs && (date1 != NULL || date2 != NULL ||
158 rev1 != NULL || rev2 != NULL))
159 error (1, 0, "must not specify revisions/dates with -t option!");
161 if (!toptwo_diffs && (date1 == NULL && date2 == NULL &&
162 rev1 == NULL && rev2 == NULL))
163 error (1, 0, "must specify at least one revision/date!");
164 if (date1 != NULL && date2 != NULL)
165 if (RCS_datecmp (date1, date2) >= 0)
166 error (1, 0, "second date must come after first date!");
168 /* if options is NULL, make it a NULL string */
170 options = xstrdup ("");
172 /* clean up if we get a signal */
173 (void) SIG_register (SIGHUP, patch_cleanup);
174 (void) SIG_register (SIGINT, patch_cleanup);
175 (void) SIG_register (SIGQUIT, patch_cleanup);
176 (void) SIG_register (SIGPIPE, patch_cleanup);
177 (void) SIG_register (SIGTERM, patch_cleanup);
180 for (i = 0; i < argc; i++)
181 err += do_module (db, argv[i], PATCH, "Patching", patch_proc,
182 (char *) NULL, 0, 0, 0, (char *) NULL);
190 * callback proc for doing the real work of patching
193 static char where[PATH_MAX];
195 patch_proc (pargc, argv, xwhere, mwhere, mfile, shorten, local_specified,
209 char repository[PATH_MAX];
211 (void) sprintf (repository, "%s/%s", CVSroot, argv[0]);
212 (void) strcpy (where, argv[0]);
214 /* if mfile isn't null, we need to set up to do only part of the module */
220 /* if the portion of the module is a path, put the dir part on repos */
221 if ((cp = rindex (mfile, '/')) != NULL)
224 (void) strcat (repository, "/");
225 (void) strcat (repository, mfile);
226 (void) strcat (where, "/");
227 (void) strcat (where, mfile);
231 /* take care of the rest */
232 (void) sprintf (path, "%s/%s", repository, mfile);
235 /* directory means repository gets the dir tacked on */
236 (void) strcpy (repository, path);
237 (void) strcat (where, "/");
238 (void) strcat (where, mfile);
244 /* a file means muck argv */
245 for (i = 1; i < *pargc; i++)
247 argv[1] = xstrdup (mfile);
252 /* cd to the starting repository */
253 if (chdir (repository) < 0)
255 error (0, errno, "cannot chdir to %s", repository);
260 which = W_REPOS | W_ATTIC;
264 /* start the recursion processor */
265 err = start_recursion (patch_fileproc, (int (*) ()) NULL, patch_dirproc,
266 (int (*) ()) NULL, *pargc - 1, argv + 1, local,
267 which, 0, 1, where, 1);
273 * Called to examine a particular RCS file, as appropriate with the options
274 * that were set above.
278 patch_fileproc (file, update_dir, repository, entries, srcfiles)
285 char *vers_tag, *vers_head;
286 char rcsspace[PATH_MAX];
287 char *rcs = rcsspace;
290 FILE *fp1, *fp2, *fp3;
294 char file1[PATH_MAX], file2[PATH_MAX], strippath[PATH_MAX];
295 char line1[MAXLINELEN], line2[MAXLINELEN];
296 char *cp1, *cp2, *commap;
300 /* find the parsed rcs file */
301 p = findnode (srcfiles, file);
304 rcsfile = (RCSNode *) p->data;
305 if ((rcsfile->flags & VALID) && (rcsfile->flags & INATTIC))
308 (void) sprintf (rcs, "%s%s", file, RCSEXT);
310 /* if vers_head is NULL, may have been removed from the release */
311 if (isattic && rev2 == NULL && date2 == NULL)
314 vers_head = RCS_getversion (rcsfile, rev2, date2, force_tag_match);
318 if (vers_head == NULL)
322 date1 = xmalloc (50); /* plenty big :-) */
324 if (RCS_getrevtime (rcsfile, vers_head, date1, 1) == -1)
327 error (0, 0, "cannot find date in rcs file %s revision %s",
332 vers_tag = RCS_getversion (rcsfile, rev1, date1, force_tag_match);
334 if (vers_tag == NULL && (vers_head == NULL || isattic))
335 return (0); /* nothing known about specified revs */
337 if (vers_tag && vers_head && strcmp (vers_head, vers_tag) == 0)
338 return (0); /* not changed between releases */
342 (void) printf ("File ");
343 if (vers_tag == NULL)
344 (void) printf ("%s is new; current revision %s\n", rcs, vers_head);
345 else if (vers_head == NULL)
346 (void) printf ("%s is removed; not included in release %s\n",
347 rcs, rev2 ? rev2 : date2);
349 (void) printf ("%s changed from revision %s to %s\n",
350 rcs, vers_tag, vers_head);
353 if ((fp1 = fopen (tmpnam (tmpfile1), "w+")) != NULL)
355 if ((fp2 = fopen (tmpnam (tmpfile2), "w+")) != NULL)
357 if ((fp3 = fopen (tmpnam (tmpfile3), "w+")) != NULL)
359 if (fp1 == NULL || fp2 == NULL || fp3 == NULL)
361 error (0, 0, "cannot create temporary files");
365 if (vers_tag != NULL)
367 run_setup ("%s%s %s -p -q -r%s", Rcsbin, RCS_CO, options, vers_tag);
368 run_arg (rcsfile->path);
369 if ((retcode = run_exec (RUN_TTY, tmpfile1, RUN_TTY, RUN_NORMAL)) != 0)
372 error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
373 "co of revision %s in %s failed", vers_tag, rcs);
378 else if (toptwo_diffs)
383 if (vers_head != NULL)
385 run_setup ("%s%s %s -p -q -r%s", Rcsbin, RCS_CO, options, vers_head);
386 run_arg (rcsfile->path);
387 if ((retcode = run_exec (RUN_TTY, tmpfile2, RUN_TTY, RUN_NORMAL)) != 0)
390 error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
391 "co of revision %s in %s failed", vers_head, rcs);
396 run_setup ("%s -%c", DIFF, unidiff ? 'u' : 'c');
399 switch (run_exec (RUN_TTY, tmpfile3, RUN_TTY, RUN_NORMAL))
401 case -1: /* fork/wait failure */
402 error (1, errno, "fork for diff failed on %s", rcs);
404 case 0: /* nothing to do */
408 * The two revisions are really different, so read the first two
409 * lines of the diff output file, and munge them to include more
410 * reasonable file names that "patch" will understand.
412 fp = open_file (tmpfile3, "r");
413 if (fgets (line1, sizeof (line1), fp) == NULL ||
414 fgets (line2, sizeof (line2), fp) == NULL)
416 error (0, errno, "failed to read diff file header %s for %s",
424 if (strncmp (line1, "*** ", 4) != 0 ||
425 strncmp (line2, "--- ", 4) != 0 ||
426 (cp1 = index (line1, '\t')) == NULL ||
427 (cp2 = index (line2, '\t')) == NULL)
429 error (0, 0, "invalid diff header for %s", rcs);
437 if (strncmp (line1, "--- ", 4) != 0 ||
438 strncmp (line2, "+++ ", 4) != 0 ||
439 (cp1 = index (line1, '\t')) == NULL ||
440 (cp2 = index (line2, '\t')) == NULL)
442 error (0, 0, "invalid unidiff header for %s", rcs);
449 (void) sprintf (strippath, "%s/", CVSroot);
451 (void) strcpy (strippath, REPOS_STRIP);
452 if (strncmp (rcs, strippath, strlen (strippath)) == 0)
453 rcs += strlen (strippath);
454 commap = rindex (rcs, ',');
456 if (vers_tag != NULL)
458 (void) sprintf (file1, "%s%s%s:%s", update_dir,
459 update_dir[0] ? "/" : "", rcs, vers_tag);
463 (void) strcpy (file1, DEVNULL);
465 (void) sprintf (file2, "%s%s%s:%s", update_dir,
466 update_dir[0] ? "/" : "", rcs,
467 vers_head ? vers_head : "removed");
470 (void) printf ("diff -u %s %s\n", file1, file2);
471 (void) printf ("--- %s%s+++ ", file1, cp1);
475 (void) printf ("diff -c %s %s\n", file1, file2);
476 (void) printf ("*** %s%s--- ", file1, cp1);
479 if (update_dir[0] != '\0')
480 (void) printf ("%s/", update_dir);
481 (void) printf ("%s%s", rcs, cp2);
482 while (fgets (line1, sizeof (line1), fp) != NULL)
483 (void) printf ("%s", line1);
487 error (0, 0, "diff failed for %s", rcs);
490 (void) unlink_file (tmpfile1);
491 (void) unlink_file (tmpfile2);
492 (void) unlink_file (tmpfile3);
497 * Print a warm fuzzy message
501 patch_dirproc (dir, repos, update_dir)
507 error (0, 0, "Diffing %s", update_dir);
512 * Clean up temporary files
517 if (tmpfile1[0] != '\0')
518 (void) unlink_file (tmpfile1);
519 if (tmpfile2[0] != '\0')
520 (void) unlink_file (tmpfile2);
521 if (tmpfile3[0] != '\0')
522 (void) unlink_file (tmpfile3);