/contrib/cvs/src/diff.c

https://bitbucket.org/freebsd/freebsd-head/ · C · 1178 lines · 853 code · 89 blank · 236 comment · 285 complexity · beb7b1984df956e56f3e50f1e42cf293 MD5 · raw file

  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. * Difference
  14. *
  15. * Run diff against versions in the repository. Options that are specified are
  16. * passed on directly to "rcsdiff".
  17. *
  18. * Without any file arguments, runs diff against all the currently modified
  19. * files.
  20. *
  21. * $FreeBSD$
  22. */
  23. #include <assert.h>
  24. #include "cvs.h"
  25. enum diff_file
  26. {
  27. DIFF_ERROR,
  28. DIFF_ADDED,
  29. DIFF_REMOVED,
  30. DIFF_DIFFERENT,
  31. DIFF_SAME
  32. };
  33. static Dtype diff_dirproc PROTO ((void *callerdat, const char *dir,
  34. const char *pos_repos,
  35. const char *update_dir,
  36. List *entries));
  37. static int diff_filesdoneproc PROTO ((void *callerdat, int err,
  38. const char *repos,
  39. const char *update_dir,
  40. List *entries));
  41. static int diff_dirleaveproc PROTO ((void *callerdat, const char *dir,
  42. int err, const char *update_dir,
  43. List *entries));
  44. static enum diff_file diff_file_nodiff PROTO(( struct file_info *finfo,
  45. Vers_TS *vers,
  46. enum diff_file,
  47. char **rev1_cache ));
  48. static int diff_fileproc PROTO ((void *callerdat, struct file_info *finfo));
  49. static void diff_mark_errors PROTO((int err));
  50. /* Global variables. Would be cleaner if we just put this stuff in a
  51. struct like log.c does. */
  52. /* Command line tags, from -r option. Points into argv. */
  53. static char *diff_rev1, *diff_rev2;
  54. /* Command line dates, from -D option. Malloc'd. */
  55. static char *diff_date1, *diff_date2;
  56. static char *diff_join1, *diff_join2;
  57. static char *use_rev1, *use_rev2;
  58. static int have_rev1_label, have_rev2_label;
  59. /* Revision of the user file, if it is unchanged from something in the
  60. repository and we want to use that fact. */
  61. static char *user_file_rev;
  62. static char *options;
  63. static char **diff_argv;
  64. static int diff_argc;
  65. static size_t diff_arg_allocated;
  66. static int diff_errors;
  67. static int empty_files = 0;
  68. static const char *const diff_usage[] =
  69. {
  70. "Usage: %s %s [-lR] [-k kopt] [format_options]\n",
  71. " [[-r rev1 | -D date1] [-r rev2 | -D date2]] [files...] \n",
  72. "\t-l\tLocal directory only, not recursive\n",
  73. "\t-R\tProcess directories recursively.\n",
  74. "\t-k kopt\tSpecify keyword expansion mode.\n",
  75. "\t-D d1\tDiff revision for date against working file.\n",
  76. "\t-D d2\tDiff rev1/date1 against date2.\n",
  77. "\t-r rev1\tDiff revision for rev1 against working file.\n",
  78. "\t-r rev2\tDiff rev1/date1 against rev2.\n",
  79. "\nformat_options:\n",
  80. " -i --ignore-case Consider upper- and lower-case to be the same.\n",
  81. " -w --ignore-all-space Ignore all white space.\n",
  82. " -b --ignore-space-change Ignore changes in the amount of white space.\n",
  83. " -B --ignore-blank-lines Ignore changes whose lines are all blank.\n",
  84. " -I RE --ignore-matching-lines=RE Ignore changes whose lines all match RE.\n",
  85. " --binary Read and write data in binary mode.\n",
  86. " -a --text Treat all files as text.\n\n",
  87. " -c -C NUM --context[=NUM] Output NUM (default 2) lines of copied context.\n",
  88. " -u -U NUM --unified[=NUM] Output NUM (default 2) lines of unified context.\n",
  89. " -NUM Use NUM context lines.\n",
  90. " -L LABEL --label LABEL Use LABEL instead of file name.\n",
  91. " -p --show-c-function Show which C function each change is in.\n",
  92. " -F RE --show-function-line=RE Show the most recent line matching RE.\n",
  93. " --brief Output only whether files differ.\n",
  94. " -e --ed Output an ed script.\n",
  95. " -f --forward-ed Output something like an ed script in forward order.\n",
  96. " -n --rcs Output an RCS format diff.\n",
  97. " -y --side-by-side Output in two columns.\n",
  98. " -W NUM --width=NUM Output at most NUM (default 130) characters per line.\n",
  99. " --left-column Output only the left column of common lines.\n",
  100. " --suppress-common-lines Do not output common lines.\n",
  101. " --ifdef=NAME Output merged file to show `#ifdef NAME' diffs.\n",
  102. " --GTYPE-group-format=GFMT Similar, but format GTYPE input groups with GFMT.\n",
  103. " --line-format=LFMT Similar, but format all input lines with LFMT.\n",
  104. " --LTYPE-line-format=LFMT Similar, but format LTYPE input lines with LFMT.\n",
  105. " LTYPE is `old', `new', or `unchanged'. GTYPE is LTYPE or `changed'.\n",
  106. " GFMT may contain:\n",
  107. " %%< lines from FILE1\n",
  108. " %%> lines from FILE2\n",
  109. " %%= lines common to FILE1 and FILE2\n",
  110. " %%[-][WIDTH][.[PREC]]{doxX}LETTER printf-style spec for LETTER\n",
  111. " LETTERs are as follows for new group, lower case for old group:\n",
  112. " F first line number\n",
  113. " L last line number\n",
  114. " N number of lines = L-F+1\n",
  115. " E F-1\n",
  116. " M L+1\n",
  117. " LFMT may contain:\n",
  118. " %%L contents of line\n",
  119. " %%l contents of line, excluding any trailing newline\n",
  120. " %%[-][WIDTH][.[PREC]]{doxX}n printf-style spec for input line number\n",
  121. " Either GFMT or LFMT may contain:\n",
  122. " %%%% %%\n",
  123. " %%c'C' the single character C\n",
  124. " %%c'\\OOO' the character with octal code OOO\n\n",
  125. " -t --expand-tabs Expand tabs to spaces in output.\n",
  126. " -T --initial-tab Make tabs line up by prepending a tab.\n\n",
  127. " -N --new-file Treat absent files as empty.\n",
  128. " -s --report-identical-files Report when two files are the same.\n",
  129. " --horizon-lines=NUM Keep NUM lines of the common prefix and suffix.\n",
  130. " -d --minimal Try hard to find a smaller set of changes.\n",
  131. " -H --speed-large-files Assume large files and many scattered small changes.\n",
  132. "\n(Specify the --help global option for a list of other help options)\n",
  133. NULL
  134. };
  135. /* I copied this array directly out of diff.c in diffutils 2.7, after
  136. removing the following entries, none of which seem relevant to use
  137. with CVS:
  138. --help
  139. --version (-v)
  140. --recursive (-r)
  141. --unidirectional-new-file (-P)
  142. --starting-file (-S)
  143. --exclude (-x)
  144. --exclude-from (-X)
  145. --sdiff-merge-assist
  146. --paginate (-l) (doesn't work with library callbacks)
  147. I changed the options which take optional arguments (--context and
  148. --unified) to return a number rather than a letter, so that the
  149. optional argument could be handled more easily. I changed the
  150. --brief and --ifdef options to return numbers, since -q and -D mean
  151. something else to cvs diff.
  152. The numbers 129- that appear in the fourth element of some entries
  153. tell the big switch in `diff' how to process those options. -- Ian
  154. The following options, which diff lists as "An alias, no longer
  155. recommended" have been removed: --file-label --entire-new-file
  156. --ascii --print. */
  157. static struct option const longopts[] =
  158. {
  159. {"ignore-blank-lines", 0, 0, 'B'},
  160. {"context", 2, 0, 143},
  161. {"ifdef", 1, 0, 131},
  162. {"show-function-line", 1, 0, 'F'},
  163. {"speed-large-files", 0, 0, 'H'},
  164. {"ignore-matching-lines", 1, 0, 'I'},
  165. {"label", 1, 0, 'L'},
  166. {"new-file", 0, 0, 'N'},
  167. {"initial-tab", 0, 0, 'T'},
  168. {"width", 1, 0, 'W'},
  169. {"text", 0, 0, 'a'},
  170. {"ignore-space-change", 0, 0, 'b'},
  171. {"minimal", 0, 0, 'd'},
  172. {"ed", 0, 0, 'e'},
  173. {"forward-ed", 0, 0, 'f'},
  174. {"ignore-case", 0, 0, 'i'},
  175. {"rcs", 0, 0, 'n'},
  176. {"show-c-function", 0, 0, 'p'},
  177. /* This is a potentially very useful option, except the output is so
  178. silly. It would be much better for it to look like "cvs rdiff -s"
  179. which displays all the same info, minus quite a few lines of
  180. extraneous garbage. */
  181. {"brief", 0, 0, 145},
  182. {"report-identical-files", 0, 0, 's'},
  183. {"expand-tabs", 0, 0, 't'},
  184. {"ignore-all-space", 0, 0, 'w'},
  185. {"side-by-side", 0, 0, 'y'},
  186. {"unified", 2, 0, 146},
  187. {"left-column", 0, 0, 129},
  188. {"suppress-common-lines", 0, 0, 130},
  189. {"old-line-format", 1, 0, 132},
  190. {"new-line-format", 1, 0, 133},
  191. {"unchanged-line-format", 1, 0, 134},
  192. {"line-format", 1, 0, 135},
  193. {"old-group-format", 1, 0, 136},
  194. {"new-group-format", 1, 0, 137},
  195. {"unchanged-group-format", 1, 0, 138},
  196. {"changed-group-format", 1, 0, 139},
  197. {"horizon-lines", 1, 0, 140},
  198. {"binary", 0, 0, 142},
  199. {0, 0, 0, 0}
  200. };
  201. /* Add one of OPT or LONGOPT, and ARGUMENT, when present, to global DIFF_ARGV.
  202. *
  203. * INPUTS
  204. * opt A character option representation.
  205. * longopt A long option name.
  206. * argument Optional option argument.
  207. *
  208. * GLOBALS
  209. * diff_argc The number of arguments in DIFF_ARGV.
  210. * diff_argv Array of argument strings.
  211. * diff_arg_allocated Allocated length of DIFF_ARGV.
  212. *
  213. * NOTES
  214. * Behavior when both OPT & LONGOPT are provided is undefined.
  215. *
  216. * RETURNS
  217. * Nothing.
  218. */
  219. static void
  220. add_diff_args (char opt, const char *longopt, const char *argument)
  221. {
  222. char *tmp;
  223. /* Add opt or longopt to diff_arv. */
  224. assert (opt || (longopt && *longopt));
  225. assert (!(opt && (longopt && *longopt)));
  226. if (opt)
  227. {
  228. tmp = xmalloc (3);
  229. sprintf (tmp, "-%c", opt);
  230. }
  231. else
  232. {
  233. tmp = xmalloc (3 + strlen (longopt));
  234. sprintf (tmp, "--%s", longopt);
  235. }
  236. run_add_arg_p (&diff_argc, &diff_arg_allocated, &diff_argv, tmp);
  237. free (tmp);
  238. /* When present, add ARGUMENT to DIFF_ARGV. */
  239. if (argument)
  240. run_add_arg_p (&diff_argc, &diff_arg_allocated, &diff_argv, argument);
  241. }
  242. /* CVS 1.9 and similar versions seemed to have pretty weird handling
  243. of -y and -T. In the cases where it called rcsdiff,
  244. they would have the meanings mentioned below. In the cases where it
  245. called diff, they would have the meanings mentioned in "longopts".
  246. Noone seems to have missed them, so I think the right thing to do is
  247. just to remove the options altogether (which I have done).
  248. In the case of -z and -q, "cvs diff" did not accept them even back
  249. when we called rcsdiff (at least, it hasn't accepted them
  250. recently).
  251. In comparing rcsdiff to the new CVS implementation, I noticed that
  252. the following rcsdiff flags are not handled by CVS diff:
  253. -y: perform diff even when the requested revisions are the
  254. same revision number
  255. -q: run quietly
  256. -T: preserve modification time on the RCS file
  257. -z: specify timezone for use in file labels
  258. I think these are not really relevant. -y is undocumented even in
  259. RCS 5.7, and seems like a minor change at best. According to RCS
  260. documentation, -T only applies when a RCS file has been modified
  261. because of lock changes; doesn't CVS sidestep RCS's entire lock
  262. structure? -z seems to be unsupported by CVS diff, and has a
  263. different meaning as a global option anyway. (Adding it could be
  264. a feature, but if it is left out for now, it should not break
  265. anything.) For the purposes of producing output, CVS diff appears
  266. mostly to ignore -q. Maybe this should be fixed, but I think it's
  267. a larger issue than the changes included here. */
  268. int
  269. diff (argc, argv)
  270. int argc;
  271. char **argv;
  272. {
  273. int c, err = 0;
  274. int local = 0;
  275. int which;
  276. int option_index;
  277. if (argc == -1)
  278. usage (diff_usage);
  279. have_rev1_label = have_rev2_label = 0;
  280. /*
  281. * Note that we catch all the valid arguments here, so that we can
  282. * intercept the -r arguments for doing revision diffs; and -l/-R for a
  283. * non-recursive/recursive diff.
  284. */
  285. /* Clean out our global variables (multiroot can call us multiple
  286. times and the server can too, if the client sends several
  287. diff commands). */
  288. if (diff_argc)
  289. {
  290. run_arg_free_p (diff_argc, diff_argv);
  291. diff_argc = 0;
  292. }
  293. diff_rev1 = NULL;
  294. diff_rev2 = NULL;
  295. diff_date1 = NULL;
  296. diff_date2 = NULL;
  297. diff_join1 = NULL;
  298. diff_join2 = NULL;
  299. optind = 0;
  300. /* FIXME: This should really be allocating an argv to be passed to diff
  301. * later rather than strcatting onto the opts variable. We have some
  302. * handling routines that can already handle most of the argc/argv
  303. * maintenance for us and currently, if anyone were to attempt to pass a
  304. * quoted string in here, it would be split on spaces and tabs on its way
  305. * to diff.
  306. */
  307. while ((c = getopt_long (argc, argv,
  308. "+abcdefhilnpstuwy0123456789BHNRTC:D:F:I:L:U:W:k:r:j:",
  309. longopts, &option_index)) != -1)
  310. {
  311. switch (c)
  312. {
  313. case 'y':
  314. add_diff_args (0, "side-by-side", NULL);
  315. break;
  316. case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
  317. case 'h': case 'i': case 'n': case 'p': case 's': case 't':
  318. case 'u': case 'w':
  319. case '0': case '1': case '2': case '3': case '4': case '5':
  320. case '6': case '7': case '8': case '9':
  321. case 'B': case 'H': case 'T':
  322. add_diff_args (c, NULL, NULL);
  323. break;
  324. case 'L':
  325. if (have_rev1_label++)
  326. if (have_rev2_label++)
  327. {
  328. error (0, 0, "extra -L arguments ignored");
  329. break;
  330. }
  331. /* Fall through. */
  332. case 'C': case 'F': case 'I': case 'U': case 'W':
  333. add_diff_args (c, NULL, optarg);
  334. break;
  335. case 129: case 130: case 131: case 132: case 133: case 134:
  336. case 135: case 136: case 137: case 138: case 139: case 140:
  337. case 141: case 142: case 143: case 145: case 146:
  338. add_diff_args (0, longopts[option_index].name,
  339. longopts[option_index].has_arg ? optarg : NULL);
  340. break;
  341. case 'R':
  342. local = 0;
  343. break;
  344. case 'l':
  345. local = 1;
  346. break;
  347. case 'k':
  348. if (options)
  349. free (options);
  350. options = RCS_check_kflag (optarg);
  351. break;
  352. case 'j':
  353. {
  354. char *ptr;
  355. char *cpy = strdup(optarg);
  356. if ((ptr = strchr(optarg, ':')) != NULL)
  357. *ptr++ = 0;
  358. if (diff_rev2 != NULL || diff_date2 != NULL)
  359. error (1, 0,
  360. "no more than two revisions/dates can be specified");
  361. if (diff_rev1 != NULL || diff_date1 != NULL) {
  362. diff_join2 = cpy;
  363. diff_rev2 = optarg;
  364. diff_date2 = ptr ? Make_Date(ptr) : NULL;
  365. } else {
  366. diff_join1 = cpy;
  367. diff_rev1 = optarg;
  368. diff_date1 = ptr ? Make_Date(ptr) : NULL;
  369. }
  370. }
  371. break;
  372. case 'r':
  373. if (diff_rev2 != NULL || diff_date2 != NULL)
  374. error (1, 0,
  375. "no more than two revisions/dates can be specified");
  376. if (diff_rev1 != NULL || diff_date1 != NULL)
  377. diff_rev2 = optarg;
  378. else
  379. diff_rev1 = optarg;
  380. break;
  381. case 'D':
  382. if (diff_rev2 != NULL || diff_date2 != NULL)
  383. error (1, 0,
  384. "no more than two revisions/dates can be specified");
  385. if (diff_rev1 != NULL || diff_date1 != NULL)
  386. diff_date2 = Make_Date (optarg);
  387. else
  388. diff_date1 = Make_Date (optarg);
  389. break;
  390. case 'N':
  391. empty_files = 1;
  392. break;
  393. case '?':
  394. default:
  395. usage (diff_usage);
  396. break;
  397. }
  398. }
  399. argc -= optind;
  400. argv += optind;
  401. /* make sure options is non-null */
  402. if (!options)
  403. options = xstrdup ("");
  404. #ifdef CLIENT_SUPPORT
  405. if (current_parsed_root->isremote) {
  406. /* We're the client side. Fire up the remote server. */
  407. start_server ();
  408. ign_setup ();
  409. if (local)
  410. send_arg("-l");
  411. if (empty_files)
  412. send_arg("-N");
  413. send_options (diff_argc, diff_argv);
  414. if (options[0] != '\0')
  415. send_arg (options);
  416. if (diff_join1)
  417. option_with_arg ("-j", diff_join1);
  418. else if (diff_rev1)
  419. option_with_arg ("-r", diff_rev1);
  420. else if (diff_date1)
  421. client_senddate (diff_date1);
  422. if (diff_join2)
  423. option_with_arg ("-j", diff_join2);
  424. else if (diff_rev2)
  425. option_with_arg ("-r", diff_rev2);
  426. else if (diff_date2)
  427. client_senddate (diff_date2);
  428. send_arg ("--");
  429. /* Send the current files unless diffing two revs from the archive */
  430. if (diff_rev2 == NULL && diff_date2 == NULL)
  431. send_files (argc, argv, local, 0, 0);
  432. else
  433. send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
  434. send_file_names (argc, argv, SEND_EXPAND_WILD);
  435. send_to_server ("diff\012", 0);
  436. err = get_responses_and_close ();
  437. } else
  438. #endif
  439. { /* FreeBSD addition - warning idention not changed til matching-} */
  440. if (diff_rev1 != NULL)
  441. tag_check_valid (diff_rev1, argc, argv, local, 0, "");
  442. if (diff_rev2 != NULL)
  443. tag_check_valid (diff_rev2, argc, argv, local, 0, "");
  444. which = W_LOCAL;
  445. if (diff_rev1 != NULL || diff_date1 != NULL)
  446. which |= W_REPOS | W_ATTIC;
  447. wrap_setup ();
  448. /* start the recursion processor */
  449. err = start_recursion (diff_fileproc, diff_filesdoneproc, diff_dirproc,
  450. diff_dirleaveproc, NULL, argc, argv, local,
  451. which, 0, CVS_LOCK_READ, (char *) NULL, 1,
  452. (char *) NULL);
  453. } /* FreeBSD addition */
  454. /* clean up */
  455. free (options);
  456. options = NULL;
  457. if (diff_date1 != NULL)
  458. free (diff_date1);
  459. if (diff_date2 != NULL)
  460. free (diff_date2);
  461. if (diff_join1 != NULL)
  462. free (diff_join1);
  463. if (diff_join2 != NULL)
  464. free (diff_join2);
  465. return (err);
  466. }
  467. /*
  468. * Do a file diff
  469. */
  470. /* ARGSUSED */
  471. static int
  472. diff_fileproc (callerdat, finfo)
  473. void *callerdat;
  474. struct file_info *finfo;
  475. {
  476. int status, err = 2; /* 2 == trouble, like rcsdiff */
  477. Vers_TS *vers;
  478. enum diff_file empty_file = DIFF_DIFFERENT;
  479. char *tmp = NULL;
  480. char *tocvsPath = NULL;
  481. char *fname = NULL;
  482. char *label1;
  483. char *label2;
  484. char *rev1_cache = NULL;
  485. user_file_rev = 0;
  486. vers = Version_TS (finfo, NULL, NULL, NULL, 1, 0);
  487. if (diff_rev2 != NULL || diff_date2 != NULL)
  488. {
  489. /* Skip all the following checks regarding the user file; we're
  490. not using it. */
  491. }
  492. else if (vers->vn_user == NULL)
  493. {
  494. /* The file does not exist in the working directory. */
  495. if ((diff_rev1 != NULL || diff_date1 != NULL)
  496. && vers->srcfile != NULL)
  497. {
  498. /* The file does exist in the repository. */
  499. if (empty_files)
  500. empty_file = DIFF_REMOVED;
  501. else
  502. {
  503. int exists;
  504. exists = 0;
  505. /* special handling for TAG_HEAD */
  506. if (diff_rev1 && strcmp (diff_rev1, TAG_HEAD) == 0)
  507. {
  508. char *head =
  509. (vers->vn_rcs == NULL
  510. ? NULL
  511. : RCS_branch_head (vers->srcfile, vers->vn_rcs));
  512. exists = head != NULL && !RCS_isdead(vers->srcfile, head);
  513. if (head != NULL)
  514. free (head);
  515. }
  516. else
  517. {
  518. Vers_TS *xvers;
  519. xvers = Version_TS (finfo, NULL, diff_rev1, diff_date1,
  520. 1, 0);
  521. exists = xvers->vn_rcs != NULL && !RCS_isdead(xvers->srcfile, xvers->vn_rcs);
  522. freevers_ts (&xvers);
  523. }
  524. if (exists)
  525. error (0, 0,
  526. "%s no longer exists, no comparison available",
  527. finfo->fullname);
  528. goto out;
  529. }
  530. }
  531. else
  532. {
  533. error (0, 0, "I know nothing about %s", finfo->fullname);
  534. goto out;
  535. }
  536. }
  537. else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0')
  538. {
  539. /* The file was added locally. */
  540. int exists = 0;
  541. if (vers->srcfile != NULL)
  542. {
  543. /* The file does exist in the repository. */
  544. if ((diff_rev1 != NULL || diff_date1 != NULL))
  545. {
  546. /* special handling for TAG_HEAD */
  547. if (diff_rev1 && strcmp (diff_rev1, TAG_HEAD) == 0)
  548. {
  549. char *head =
  550. (vers->vn_rcs == NULL
  551. ? NULL
  552. : RCS_branch_head (vers->srcfile, vers->vn_rcs));
  553. exists = head != NULL && !RCS_isdead(vers->srcfile, head);
  554. if (head != NULL)
  555. free (head);
  556. }
  557. else
  558. {
  559. Vers_TS *xvers;
  560. xvers = Version_TS (finfo, NULL, diff_rev1, diff_date1,
  561. 1, 0);
  562. exists = xvers->vn_rcs != NULL
  563. && !RCS_isdead (xvers->srcfile, xvers->vn_rcs);
  564. freevers_ts (&xvers);
  565. }
  566. }
  567. else
  568. {
  569. /* The file was added locally, but an RCS archive exists. Our
  570. * base revision must be dead.
  571. */
  572. /* No need to set, exists = 0, here. That's the default. */
  573. }
  574. }
  575. if (!exists)
  576. {
  577. /* If we got here, then either the RCS archive does not exist or
  578. * the relevant revision is dead.
  579. */
  580. if (empty_files)
  581. empty_file = DIFF_ADDED;
  582. else
  583. {
  584. error (0, 0, "%s is a new entry, no comparison available",
  585. finfo->fullname);
  586. goto out;
  587. }
  588. }
  589. }
  590. else if (vers->vn_user[0] == '-')
  591. {
  592. if (empty_files)
  593. empty_file = DIFF_REMOVED;
  594. else
  595. {
  596. error (0, 0, "%s was removed, no comparison available",
  597. finfo->fullname);
  598. goto out;
  599. }
  600. }
  601. else
  602. {
  603. if (vers->vn_rcs == NULL && vers->srcfile == NULL)
  604. {
  605. error (0, 0, "cannot find revision control file for %s",
  606. finfo->fullname);
  607. goto out;
  608. }
  609. else
  610. {
  611. if (vers->ts_user == NULL)
  612. {
  613. error (0, 0, "cannot find %s", finfo->fullname);
  614. goto out;
  615. }
  616. else if (!strcmp (vers->ts_user, vers->ts_rcs))
  617. {
  618. /* The user file matches some revision in the repository
  619. Diff against the repository (for remote CVS, we might not
  620. have a copy of the user file around). */
  621. user_file_rev = vers->vn_user;
  622. }
  623. }
  624. }
  625. empty_file = diff_file_nodiff( finfo, vers, empty_file, &rev1_cache );
  626. if( empty_file == DIFF_SAME )
  627. {
  628. /* In the server case, would be nice to send a "Checked-in"
  629. response, so that the client can rewrite its timestamp.
  630. server_checked_in by itself isn't the right thing (it
  631. needs a server_register), but I'm not sure what is.
  632. It isn't clear to me how "cvs status" handles this (that
  633. is, for a client which sends Modified not Is-modified to
  634. "cvs status"), but it does. */
  635. err = 0;
  636. goto out;
  637. }
  638. else if( empty_file == DIFF_ERROR )
  639. goto out;
  640. /* Output an "Index:" line for patch to use */
  641. cvs_output ("Index: ", 0);
  642. cvs_output (finfo->fullname, 0);
  643. cvs_output ("\n", 1);
  644. tocvsPath = wrap_tocvs_process_file(finfo->file);
  645. if( tocvsPath != NULL )
  646. {
  647. /* Backup the current version of the file to CVS/,,filename */
  648. fname = xmalloc (strlen (finfo->file)
  649. + sizeof CVSADM
  650. + sizeof CVSPREFIX
  651. + 10);
  652. sprintf(fname,"%s/%s%s",CVSADM, CVSPREFIX, finfo->file);
  653. if (unlink_file_dir (fname) < 0)
  654. if (! existence_error (errno))
  655. error (1, errno, "cannot remove %s", fname);
  656. rename_file (finfo->file, fname);
  657. /* Copy the wrapped file to the current directory then go to work */
  658. copy_file (tocvsPath, finfo->file);
  659. }
  660. /* Set up file labels appropriate for compatibility with the Larry Wall
  661. * implementation of patch if the user didn't specify. This is irrelevant
  662. * according to the POSIX.2 specification.
  663. */
  664. label1 = NULL;
  665. label2 = NULL;
  666. if (!have_rev1_label)
  667. {
  668. if (empty_file == DIFF_ADDED)
  669. label1 =
  670. make_file_label (DEVNULL, NULL, NULL);
  671. else
  672. label1 =
  673. make_file_label (finfo->fullname, use_rev1,
  674. vers ? vers->srcfile : NULL);
  675. }
  676. if (!have_rev2_label)
  677. {
  678. if (empty_file == DIFF_REMOVED)
  679. label2 =
  680. make_file_label (DEVNULL, NULL, NULL);
  681. else
  682. label2 =
  683. make_file_label (finfo->fullname, use_rev2,
  684. vers ? vers->srcfile : NULL);
  685. }
  686. if (empty_file == DIFF_ADDED || empty_file == DIFF_REMOVED)
  687. {
  688. /* This is fullname, not file, possibly despite the POSIX.2
  689. * specification, because that's the way all the Larry Wall
  690. * implementations of patch (are there other implementations?) want
  691. * things and the POSIX.2 spec appears to leave room for this.
  692. */
  693. cvs_output ("\
  694. ===================================================================\n\
  695. RCS file: ", 0);
  696. cvs_output (finfo->fullname, 0);
  697. cvs_output ("\n", 1);
  698. cvs_output ("diff -N ", 0);
  699. cvs_output (finfo->fullname, 0);
  700. cvs_output ("\n", 1);
  701. if (empty_file == DIFF_ADDED)
  702. {
  703. if (use_rev2 == NULL)
  704. status = diff_exec (DEVNULL, finfo->file, label1, label2,
  705. diff_argc, diff_argv, RUN_TTY);
  706. else
  707. {
  708. int retcode;
  709. tmp = cvs_temp_name ();
  710. retcode = RCS_checkout (vers->srcfile, (char *) NULL,
  711. use_rev2, (char *) NULL,
  712. (*options
  713. ? options
  714. : vers->options),
  715. tmp, (RCSCHECKOUTPROC) NULL,
  716. (void *) NULL);
  717. if( retcode != 0 )
  718. goto out;
  719. status = diff_exec (DEVNULL, tmp, label1, label2,
  720. diff_argc, diff_argv, RUN_TTY);
  721. }
  722. }
  723. else
  724. {
  725. int retcode;
  726. tmp = cvs_temp_name ();
  727. retcode = RCS_checkout (vers->srcfile, (char *) NULL,
  728. use_rev1, (char *) NULL,
  729. *options ? options : vers->options,
  730. tmp, (RCSCHECKOUTPROC) NULL,
  731. (void *) NULL);
  732. if (retcode != 0)
  733. goto out;
  734. status = diff_exec (tmp, DEVNULL, label1, label2,
  735. diff_argc, diff_argv, RUN_TTY);
  736. }
  737. }
  738. else
  739. {
  740. status = RCS_exec_rcsdiff (vers->srcfile, diff_argc, diff_argv,
  741. *options ? options : vers->options,
  742. use_rev1, rev1_cache, use_rev2,
  743. label1, label2, finfo->file);
  744. }
  745. if (label1) free (label1);
  746. if (label2) free (label2);
  747. switch (status)
  748. {
  749. case -1: /* fork failed */
  750. error (1, errno, "fork failed while diffing %s",
  751. vers->srcfile->path);
  752. case 0: /* everything ok */
  753. err = 0;
  754. break;
  755. default: /* other error */
  756. err = status;
  757. break;
  758. }
  759. out:
  760. if( tocvsPath != NULL )
  761. {
  762. if (unlink_file_dir (finfo->file) < 0)
  763. if (! existence_error (errno))
  764. error (1, errno, "cannot remove %s", finfo->file);
  765. rename_file (fname, finfo->file);
  766. if (unlink_file (tocvsPath) < 0)
  767. error (1, errno, "cannot remove %s", tocvsPath);
  768. free (fname);
  769. }
  770. /* Call CVS_UNLINK() rather than unlink_file() below to avoid the check
  771. * for noexec.
  772. */
  773. if( tmp != NULL )
  774. {
  775. if (CVS_UNLINK(tmp) < 0)
  776. error (0, errno, "cannot remove %s", tmp);
  777. free (tmp);
  778. }
  779. if( rev1_cache != NULL )
  780. {
  781. if( CVS_UNLINK( rev1_cache ) < 0 )
  782. error( 0, errno, "cannot remove %s", rev1_cache );
  783. free( rev1_cache );
  784. }
  785. freevers_ts (&vers);
  786. diff_mark_errors (err);
  787. return err;
  788. }
  789. /*
  790. * Remember the exit status for each file.
  791. */
  792. static void
  793. diff_mark_errors (err)
  794. int err;
  795. {
  796. if (err > diff_errors)
  797. diff_errors = err;
  798. }
  799. /*
  800. * Print a warm fuzzy message when we enter a dir
  801. *
  802. * Don't try to diff directories that don't exist! -- DW
  803. */
  804. /* ARGSUSED */
  805. static Dtype
  806. diff_dirproc (callerdat, dir, pos_repos, update_dir, entries)
  807. void *callerdat;
  808. const char *dir;
  809. const char *pos_repos;
  810. const char *update_dir;
  811. List *entries;
  812. {
  813. /* XXX - check for dirs we don't want to process??? */
  814. /* YES ... for instance dirs that don't exist!!! -- DW */
  815. if (!isdir (dir))
  816. return (R_SKIP_ALL);
  817. if (!quiet)
  818. error (0, 0, "Diffing %s", update_dir);
  819. return (R_PROCESS);
  820. }
  821. /*
  822. * Concoct the proper exit status - done with files
  823. */
  824. /* ARGSUSED */
  825. static int
  826. diff_filesdoneproc (callerdat, err, repos, update_dir, entries)
  827. void *callerdat;
  828. int err;
  829. const char *repos;
  830. const char *update_dir;
  831. List *entries;
  832. {
  833. return (diff_errors);
  834. }
  835. /*
  836. * Concoct the proper exit status - leaving directories
  837. */
  838. /* ARGSUSED */
  839. static int
  840. diff_dirleaveproc (callerdat, dir, err, update_dir, entries)
  841. void *callerdat;
  842. const char *dir;
  843. int err;
  844. const char *update_dir;
  845. List *entries;
  846. {
  847. return (diff_errors);
  848. }
  849. /*
  850. * verify that a file is different
  851. */
  852. static enum diff_file
  853. diff_file_nodiff( finfo, vers, empty_file, rev1_cache )
  854. struct file_info *finfo;
  855. Vers_TS *vers;
  856. enum diff_file empty_file;
  857. char **rev1_cache; /* Cache the content of rev1 if we have to look
  858. * it up.
  859. */
  860. {
  861. Vers_TS *xvers;
  862. int retcode;
  863. /* free up any old use_rev* variables and reset 'em */
  864. if (use_rev1)
  865. free (use_rev1);
  866. if (use_rev2)
  867. free (use_rev2);
  868. use_rev1 = use_rev2 = (char *) NULL;
  869. if (diff_rev1 || diff_date1)
  870. {
  871. /* special handling for TAG_HEAD */
  872. if (diff_rev1 && strcmp (diff_rev1, TAG_HEAD) == 0)
  873. {
  874. if (vers->vn_rcs != NULL && vers->srcfile != NULL)
  875. use_rev1 = RCS_branch_head (vers->srcfile, vers->vn_rcs);
  876. }
  877. else
  878. {
  879. xvers = Version_TS (finfo, NULL, diff_rev1, diff_date1, 1, 0);
  880. if (xvers->vn_rcs != NULL)
  881. use_rev1 = xstrdup (xvers->vn_rcs);
  882. freevers_ts (&xvers);
  883. }
  884. }
  885. if (diff_rev2 || diff_date2)
  886. {
  887. /* special handling for TAG_HEAD */
  888. if (diff_rev2 && strcmp (diff_rev2, TAG_HEAD) == 0)
  889. {
  890. if (vers->vn_rcs != NULL && vers->srcfile != NULL)
  891. use_rev2 = RCS_branch_head (vers->srcfile, vers->vn_rcs);
  892. }
  893. else
  894. {
  895. xvers = Version_TS (finfo, NULL, diff_rev2, diff_date2, 1, 0);
  896. if (xvers->vn_rcs != NULL)
  897. use_rev2 = xstrdup (xvers->vn_rcs);
  898. freevers_ts (&xvers);
  899. }
  900. if( use_rev1 == NULL || RCS_isdead( vers->srcfile, use_rev1 ) )
  901. {
  902. /* The first revision does not exist. If EMPTY_FILES is
  903. true, treat this as an added file. Otherwise, warn
  904. about the missing tag. */
  905. if( use_rev2 == NULL || RCS_isdead( vers->srcfile, use_rev2 ) )
  906. /* At least in the case where DIFF_REV1 and DIFF_REV2
  907. * are both numeric (and non-existant (NULL), as opposed to
  908. * dead?), we should be returning some kind of error (see
  909. * basicb-8a0 in testsuite). The symbolic case may be more
  910. * complicated.
  911. */
  912. return DIFF_SAME;
  913. if( empty_files )
  914. return DIFF_ADDED;
  915. if( use_rev1 != NULL )
  916. {
  917. if (diff_rev1)
  918. {
  919. error( 0, 0,
  920. "Tag %s refers to a dead (removed) revision in file `%s'.",
  921. diff_rev1, finfo->fullname );
  922. }
  923. else
  924. {
  925. error( 0, 0,
  926. "Date %s refers to a dead (removed) revision in file `%s'.",
  927. diff_date1, finfo->fullname );
  928. }
  929. error( 0, 0,
  930. "No comparison available. Pass `-N' to `%s diff'?",
  931. program_name );
  932. }
  933. else if (diff_rev1)
  934. error (0, 0, "tag %s is not in file %s", diff_rev1,
  935. finfo->fullname);
  936. else
  937. error (0, 0, "no revision for date %s in file %s",
  938. diff_date1, finfo->fullname);
  939. return DIFF_ERROR;
  940. }
  941. assert( use_rev1 != NULL );
  942. if( use_rev2 == NULL || RCS_isdead( vers->srcfile, use_rev2 ) )
  943. {
  944. /* The second revision does not exist. If EMPTY_FILES is
  945. true, treat this as a removed file. Otherwise warn
  946. about the missing tag. */
  947. if (empty_files)
  948. return DIFF_REMOVED;
  949. if( use_rev2 != NULL )
  950. {
  951. if (diff_rev2)
  952. {
  953. error( 0, 0,
  954. "Tag %s refers to a dead (removed) revision in file `%s'.",
  955. diff_rev2, finfo->fullname );
  956. }
  957. else
  958. {
  959. error( 0, 0,
  960. "Date %s refers to a dead (removed) revision in file `%s'.",
  961. diff_date2, finfo->fullname );
  962. }
  963. error( 0, 0,
  964. "No comparison available. Pass `-N' to `%s diff'?",
  965. program_name );
  966. }
  967. else if (diff_rev2)
  968. error (0, 0, "tag %s is not in file %s", diff_rev2,
  969. finfo->fullname);
  970. else
  971. error (0, 0, "no revision for date %s in file %s",
  972. diff_date2, finfo->fullname);
  973. return DIFF_ERROR;
  974. }
  975. /* Now, see if we really need to do the diff. We can't assume that the
  976. * files are different when the revs are.
  977. */
  978. assert( use_rev2 != NULL );
  979. if( strcmp (use_rev1, use_rev2) == 0 )
  980. return DIFF_SAME;
  981. /* else fall through and do the diff */
  982. }
  983. /* If we had a r1/d1 & r2/d2, then at this point we must have a C3P0...
  984. * err... ok, then both rev1 & rev2 must have resolved to an existing,
  985. * live version due to if statement we just closed.
  986. */
  987. assert (!(diff_rev2 || diff_date2) || (use_rev1 && use_rev2));
  988. if ((diff_rev1 || diff_date1) &&
  989. (use_rev1 == NULL || RCS_isdead (vers->srcfile, use_rev1)))
  990. {
  991. /* The first revision does not exist, and no second revision
  992. was given. */
  993. if (empty_files)
  994. {
  995. if (empty_file == DIFF_REMOVED)
  996. return DIFF_SAME;
  997. if( user_file_rev && use_rev2 == NULL )
  998. use_rev2 = xstrdup( user_file_rev );
  999. return DIFF_ADDED;
  1000. }
  1001. if( use_rev1 != NULL )
  1002. {
  1003. if (diff_rev1)
  1004. {
  1005. error( 0, 0,
  1006. "Tag %s refers to a dead (removed) revision in file `%s'.",
  1007. diff_rev1, finfo->fullname );
  1008. }
  1009. else
  1010. {
  1011. error( 0, 0,
  1012. "Date %s refers to a dead (removed) revision in file `%s'.",
  1013. diff_date1, finfo->fullname );
  1014. }
  1015. error( 0, 0,
  1016. "No comparison available. Pass `-N' to `%s diff'?",
  1017. program_name );
  1018. }
  1019. else if ( diff_rev1 )
  1020. error( 0, 0, "tag %s is not in file %s", diff_rev1,
  1021. finfo->fullname );
  1022. else
  1023. error( 0, 0, "no revision for date %s in file %s",
  1024. diff_date1, finfo->fullname );
  1025. return DIFF_ERROR;
  1026. }
  1027. assert( !diff_rev1 || use_rev1 );
  1028. if (user_file_rev)
  1029. {
  1030. /* drop user_file_rev into first unused use_rev */
  1031. if (!use_rev1)
  1032. use_rev1 = xstrdup (user_file_rev);
  1033. else if (!use_rev2)
  1034. use_rev2 = xstrdup (user_file_rev);
  1035. /* and if not, it wasn't needed anyhow */
  1036. user_file_rev = NULL;
  1037. }
  1038. /* Now, see if we really need to do the diff. We can't assume that the
  1039. * files are different when the revs are.
  1040. */
  1041. if( use_rev1 && use_rev2)
  1042. {
  1043. if (strcmp (use_rev1, use_rev2) == 0)
  1044. return DIFF_SAME;
  1045. /* Fall through and do the diff. */
  1046. }
  1047. /* Don't want to do the timestamp check with both use_rev1 & use_rev2 set.
  1048. * The timestamp check is just for the default case of diffing the
  1049. * workspace file against its base revision.
  1050. */
  1051. else if( use_rev1 == NULL
  1052. || ( vers->vn_user != NULL
  1053. && strcmp( use_rev1, vers->vn_user ) == 0 ) )
  1054. {
  1055. if (empty_file == DIFF_DIFFERENT
  1056. && vers->ts_user != NULL
  1057. && strcmp (vers->ts_rcs, vers->ts_user) == 0
  1058. && (!(*options) || strcmp (options, vers->options) == 0))
  1059. {
  1060. return DIFF_SAME;
  1061. }
  1062. if (use_rev1 == NULL
  1063. && (vers->vn_user[0] != '0' || vers->vn_user[1] != '\0'))
  1064. {
  1065. if (vers->vn_user[0] == '-')
  1066. use_rev1 = xstrdup (vers->vn_user + 1);
  1067. else
  1068. use_rev1 = xstrdup (vers->vn_user);
  1069. }
  1070. }
  1071. /* If we already know that the file is being added or removed,
  1072. then we don't want to do an actual file comparison here. */
  1073. if (empty_file != DIFF_DIFFERENT)
  1074. return empty_file;
  1075. /*
  1076. * Run a quick cmp to see if we should bother with a full diff.
  1077. */
  1078. retcode = RCS_cmp_file( vers->srcfile, use_rev1, rev1_cache,
  1079. use_rev2, *options ? options : vers->options,
  1080. finfo->file );
  1081. return retcode == 0 ? DIFF_SAME : DIFF_DIFFERENT;
  1082. }