home *** CD-ROM | disk | FTP | other *** search
/ Linux Cubed Series 4: GNU Archives / Linux Cubed Series 4 - GNU Archives.iso / gnu / cvs-1.8 / cvs-1 / cvs-1.8.1 / src / diff.c < prev    next >
Encoding:
C/C++ Source or Header  |  1996-05-06  |  15.4 KB  |  624 lines

  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.  * Difference
  9.  * 
  10.  * Run diff against versions in the repository.  Options that are specified are
  11.  * passed on directly to "rcsdiff".
  12.  * 
  13.  * Without any file arguments, runs diff against all the currently modified
  14.  * files.
  15.  */
  16.  
  17. #include "cvs.h"
  18.  
  19. static Dtype diff_dirproc PROTO((char *dir, char *pos_repos, char *update_dir));
  20. static int diff_filesdoneproc PROTO((int err, char *repos, char *update_dir));
  21. static int diff_dirleaveproc PROTO((char *dir, int err, char *update_dir));
  22. static int diff_file_nodiff PROTO((char *file, char *repository, List *entries,
  23.                  RCSNode *rcs, Vers_TS *vers));
  24. static int diff_fileproc PROTO((struct file_info *finfo));
  25. static void diff_mark_errors PROTO((int err));
  26.  
  27. static char *diff_rev1, *diff_rev2;
  28. static char *diff_date1, *diff_date2;
  29. static char *use_rev1, *use_rev2;
  30.  
  31. #ifdef SERVER_SUPPORT
  32. /* Revision of the user file, if it is unchanged from something in the
  33.    repository and we want to use that fact.  */
  34. static char *user_file_rev;
  35. #endif
  36.  
  37. static char *options;
  38. static char opts[PATH_MAX];
  39. static int diff_errors;
  40. static int empty_files = 0;
  41.  
  42. static const char *const diff_usage[] =
  43. {
  44.     "Usage: %s %s [-lN] [rcsdiff-options]\n",
  45. #ifdef CVS_DIFFDATE
  46.     "    [[-r rev1 | -D date1] [-r rev2 | -D date2]] [files...] \n",
  47. #else
  48.     "    [-r rev1 [-r rev2]] [files...] \n",
  49. #endif
  50.     "\t-l\tLocal directory only, not recursive\n",
  51.     "\t-D d1\tDiff revision for date against working file.\n",
  52.     "\t-D d2\tDiff rev1/date1 against date2.\n",
  53.     "\t-N\tinclude diffs for added and removed files.\n",
  54.     "\t-r rev1\tDiff revision for rev1 against working file.\n",
  55.     "\t-r rev2\tDiff rev1/date1 against rev2.\n",
  56.     NULL
  57. };
  58.  
  59. int
  60. diff (argc, argv)
  61.     int argc;
  62.     char **argv;
  63. {
  64.     char tmp[50];
  65.     int c, err = 0;
  66.     int local = 0;
  67.     int which;
  68.  
  69.     if (argc == -1)
  70.     usage (diff_usage);
  71.  
  72.     /*
  73.      * Note that we catch all the valid arguments here, so that we can
  74.      * intercept the -r arguments for doing revision diffs; and -l/-R for a
  75.      * non-recursive/recursive diff.
  76.      */
  77. #ifdef SERVER_SUPPORT
  78.     /* Need to be able to do this command more than once (according to
  79.        the protocol spec, even if the current client doesn't use it).  */
  80.     opts[0] = '\0';
  81. #endif
  82.     optind = 1;
  83.     while ((c = getopt (argc, argv,
  84.            "abcdefhilnpqtuw0123456789BHNQRTC:D:F:I:L:V:k:r:")) != -1)
  85.     {
  86.     switch (c)
  87.     {
  88.         case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
  89.         case 'h': case 'i': case 'n': case 'p': case 't': case 'u':
  90.         case 'w': case '0': case '1': case '2': case '3': case '4':
  91.         case '5': case '6': case '7': case '8': case '9': case 'B':
  92.         case 'H': case 'T': case 'Q':
  93.         (void) sprintf (tmp, " -%c", (char) c);
  94.         (void) strcat (opts, tmp);
  95.         if (c == 'Q')
  96.         {
  97.             quiet = 1;
  98.             really_quiet = 1;
  99.             c = 'q';
  100.         }
  101.         break;
  102.         case 'C': case 'F': case 'I': case 'L': case 'V':
  103. #ifndef CVS_DIFFDATE
  104.         case 'D':
  105. #endif
  106.         (void) sprintf (tmp, " -%c%s", (char) c, optarg);
  107.         (void) strcat (opts, tmp);
  108.         break;
  109.         case 'R':
  110.         local = 0;
  111.         break;
  112.         case 'l':
  113.         local = 1;
  114.         break;
  115.         case 'q':
  116.         quiet = 1;
  117.         break;
  118.         case 'k':
  119.         if (options)
  120.             free (options);
  121.         options = RCS_check_kflag (optarg);
  122.         break;
  123.         case 'r':
  124.         if (diff_rev2 != NULL || diff_date2 != NULL)
  125.             error (1, 0,
  126.                "no more than two revisions/dates can be specified");
  127.         if (diff_rev1 != NULL || diff_date1 != NULL)
  128.             diff_rev2 = optarg;
  129.         else
  130.             diff_rev1 = optarg;
  131.         break;
  132. #ifdef CVS_DIFFDATE
  133.         case 'D':
  134.         if (diff_rev2 != NULL || diff_date2 != NULL)
  135.             error (1, 0,
  136.                "no more than two revisions/dates can be specified");
  137.         if (diff_rev1 != NULL || diff_date1 != NULL)
  138.             diff_date2 = Make_Date (optarg);
  139.         else
  140.             diff_date1 = Make_Date (optarg);
  141.         break;
  142. #endif
  143.         case 'N':
  144.         empty_files = 1;
  145.         break;
  146.         case '?':
  147.         default:
  148.         usage (diff_usage);
  149.         break;
  150.     }
  151.     }
  152.     argc -= optind;
  153.     argv += optind;
  154.  
  155.     /* make sure options is non-null */
  156.     if (!options)
  157.     options = xstrdup ("");
  158.  
  159. #ifdef CLIENT_SUPPORT
  160.     if (client_active) {
  161.     /* We're the client side.  Fire up the remote server.  */
  162.     start_server ();
  163.     
  164.     ign_setup ();
  165.  
  166.     if (local)
  167.         send_arg("-l");
  168.     if (empty_files)
  169.         send_arg("-N");
  170.     send_option_string (opts);
  171.     if (diff_rev1)
  172.         option_with_arg ("-r", diff_rev1);
  173.     if (diff_date1)
  174.         client_senddate (diff_date1);
  175.     if (diff_rev2)
  176.         option_with_arg ("-r", diff_rev2);
  177.     if (diff_date2)
  178.         client_senddate (diff_date2);
  179.  
  180.     send_file_names (argc, argv, SEND_EXPAND_WILD);
  181. #if 0
  182.     /* FIXME: We shouldn't have to send current files to diff two
  183.        revs, but it doesn't work yet and I haven't debugged it.
  184.        So send the files -- it's slower but it works.
  185.        gnu@cygnus.com Apr94 */
  186.     /* Send the current files unless diffing two revs from the archive */
  187.     if (diff_rev2 == NULL && diff_date2 == NULL)
  188. #endif
  189.     send_files (argc, argv, local, 0);
  190.  
  191.     send_to_server ("diff\012", 0);
  192.         err = get_responses_and_close ();
  193.     free (options);
  194.     return (err);
  195.     }
  196. #endif
  197.  
  198.     if (diff_rev1 != NULL)
  199.     tag_check_valid (diff_rev1, argc, argv, local, 0, "");
  200.     if (diff_rev2 != NULL)
  201.     tag_check_valid (diff_rev2, argc, argv, local, 0, "");
  202.  
  203.     which = W_LOCAL;
  204.     if (diff_rev2 != NULL || diff_date2 != NULL)
  205.     which |= W_REPOS | W_ATTIC;
  206.  
  207.     wrap_setup ();
  208.  
  209.     /* start the recursion processor */
  210.     err = start_recursion (diff_fileproc, diff_filesdoneproc, diff_dirproc,
  211.                diff_dirleaveproc, argc, argv, local,
  212.                which, 0, 1, (char *) NULL, 1, 0);
  213.  
  214.     /* clean up */
  215.     free (options);
  216.     return (err);
  217. }
  218.  
  219. /*
  220.  * Do a file diff
  221.  */
  222. /* ARGSUSED */
  223. static int
  224. diff_fileproc (finfo)
  225.     struct file_info *finfo;
  226. {
  227.     int status, err = 2;        /* 2 == trouble, like rcsdiff */
  228.     Vers_TS *vers;
  229.     enum {
  230.     DIFF_ERROR,
  231.     DIFF_ADDED,
  232.     DIFF_REMOVED,
  233.     DIFF_NEITHER
  234.     } empty_file = DIFF_NEITHER;
  235.     char tmp[L_tmpnam+1];
  236.     char *tocvsPath;
  237.     char fname[PATH_MAX];
  238.  
  239. #ifdef SERVER_SUPPORT
  240.     user_file_rev = 0;
  241. #endif
  242.     vers = Version_TS (finfo->repository, (char *) NULL, (char *) NULL, (char *) NULL,
  243.                finfo->file, 1, 0, finfo->entries, finfo->rcs);
  244.  
  245.     if (diff_rev2 != NULL || diff_date2 != NULL)
  246.     {
  247.     /* Skip all the following checks regarding the user file; we're
  248.        not using it.  */
  249.     }
  250.     else if (vers->vn_user == NULL)
  251.     {
  252.     error (0, 0, "I know nothing about %s", finfo->fullname);
  253.     freevers_ts (&vers);
  254.     diff_mark_errors (err);
  255.     return (err);
  256.     }
  257.     else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0')
  258.     {
  259.     if (empty_files)
  260.         empty_file = DIFF_ADDED;
  261.     else
  262.     {
  263.         error (0, 0, "%s is a new entry, no comparison available",
  264.            finfo->fullname);
  265.         freevers_ts (&vers);
  266.         diff_mark_errors (err);
  267.         return (err);
  268.     }
  269.     }
  270.     else if (vers->vn_user[0] == '-')
  271.     {
  272.     if (empty_files)
  273.         empty_file = DIFF_REMOVED;
  274.     else
  275.     {
  276.         error (0, 0, "%s was removed, no comparison available",
  277.            finfo->fullname);
  278.         freevers_ts (&vers);
  279.         diff_mark_errors (err);
  280.         return (err);
  281.     }
  282.     }
  283.     else
  284.     {
  285.     if (vers->vn_rcs == NULL && vers->srcfile == NULL)
  286.     {
  287.         error (0, 0, "cannot find revision control file for %s",
  288.            finfo->fullname);
  289.         freevers_ts (&vers);
  290.         diff_mark_errors (err);
  291.         return (err);
  292.     }
  293.     else
  294.     {
  295.         if (vers->ts_user == NULL)
  296.         {
  297.         error (0, 0, "cannot find %s", finfo->fullname);
  298.         freevers_ts (&vers);
  299.         diff_mark_errors (err);
  300.         return (err);
  301.         }
  302. #ifdef SERVER_SUPPORT
  303.         else if (!strcmp (vers->ts_user, vers->ts_rcs)) 
  304.         {
  305.         /* The user file matches some revision in the repository
  306.            Diff against the repository (for remote CVS, we might not
  307.            have a copy of the user file around).  */
  308.         user_file_rev = vers->vn_user;
  309.         }
  310. #endif
  311.     }
  312.     }
  313.  
  314.     if (empty_file == DIFF_NEITHER && diff_file_nodiff (finfo->file, finfo->repository, finfo->entries, finfo->rcs, vers))
  315.     {
  316.     freevers_ts (&vers);
  317.     return (0);
  318.     }
  319.  
  320.     /* FIXME: Check whether use_rev1 and use_rev2 are dead and deal
  321.        accordingly.  */
  322.  
  323.     /* Output an "Index:" line for patch to use */
  324.     (void) fflush (stdout);
  325.     (void) printf ("Index: %s\n", finfo->fullname);
  326.     (void) fflush (stdout);
  327.  
  328.     tocvsPath = wrap_tocvs_process_file(finfo->file);
  329.     if (tocvsPath)
  330.     {
  331.     /* Backup the current version of the file to CVS/,,filename */
  332.     sprintf(fname,"%s/%s%s",CVSADM, CVSPREFIX, finfo->file);
  333.     if (unlink_file_dir (fname) < 0)
  334.         if (! existence_error (errno))
  335.         error (1, errno, "cannot remove %s", finfo->file);
  336.     rename_file (finfo->file, fname);
  337.     /* Copy the wrapped file to the current directory then go to work */
  338.     copy_file (tocvsPath, finfo->file);
  339.     }
  340.  
  341.     if (empty_file == DIFF_ADDED || empty_file == DIFF_REMOVED)
  342.     {
  343.     /* This is file, not fullname, because it is the "Index:" line which
  344.        is supposed to contain the directory.  */
  345.     (void) printf ("===================================================================\nRCS file: %s\n",
  346.                finfo->file);
  347.     (void) printf ("diff -N %s\n", finfo->file);
  348.  
  349.     if (empty_file == DIFF_ADDED)
  350.     {
  351.         run_setup ("%s %s %s %s", DIFF, opts, DEVNULL, finfo->file);
  352.     }
  353.     else
  354.     {
  355.         int retcode;
  356.  
  357.         /*
  358.          * FIXME: Should be setting use_rev1 using the logic in
  359.          * diff_file_nodiff, and using that revision.  This code
  360.          * is broken for "cvs diff -N -r foo".
  361.          */
  362.         retcode = RCS_checkout (vers->srcfile->path, NULL, vers->vn_rcs,
  363.                                 *options ? options : vers->options, tmpnam (tmp),
  364.                                 0, 0);
  365.         if (retcode == -1)
  366.         {
  367.         (void) unlink (tmp);
  368.         error (1, errno, "fork failed during checkout of %s",
  369.                vers->srcfile->path);
  370.         }
  371.         /* FIXME: what if retcode > 0?  */
  372.  
  373.         run_setup ("%s %s %s %s", DIFF, opts, tmp, DEVNULL);
  374.     }
  375.     }
  376.     else
  377.     {
  378.     if (use_rev2)
  379.     {
  380.         run_setup ("%s%s -x,v/ %s %s -r%s -r%s", Rcsbin, RCS_DIFF,
  381.                opts, *options ? options : vers->options,
  382.                use_rev1, use_rev2);
  383.     }
  384.     else
  385.     {
  386.         run_setup ("%s%s -x,v/ %s %s -r%s", Rcsbin, RCS_DIFF, opts,
  387.                *options ? options : vers->options, use_rev1);
  388.     }
  389.     run_arg (vers->srcfile->path);
  390.     }
  391.  
  392.     switch ((status = run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
  393.     RUN_REALLY|RUN_COMBINED)))
  394.     {
  395.     case -1:            /* fork failed */
  396.         error (1, errno, "fork failed during rcsdiff of %s",
  397.            vers->srcfile->path);
  398.     case 0:                /* everything ok */
  399.         err = 0;
  400.         break;
  401.     default:            /* other error */
  402.         err = status;
  403.         break;
  404.     }
  405.  
  406.     if (tocvsPath)
  407.     {
  408.     if (unlink_file_dir (finfo->file) < 0)
  409.         if (! existence_error (errno))
  410.         error (1, errno, "cannot remove %s", finfo->file);
  411.  
  412.     rename_file (fname,finfo->file);
  413.     if (unlink_file (tocvsPath) < 0)
  414.         error (1, errno, "cannot remove %s", finfo->file);
  415.     }
  416.  
  417.     if (empty_file == DIFF_REMOVED)
  418.     (void) unlink (tmp);
  419.  
  420.     (void) fflush (stdout);
  421.     freevers_ts (&vers);
  422.     diff_mark_errors (err);
  423.     return (err);
  424. }
  425.  
  426. /*
  427.  * Remember the exit status for each file.
  428.  */
  429. static void
  430. diff_mark_errors (err)
  431.     int err;
  432. {
  433.     if (err > diff_errors)
  434.     diff_errors = err;
  435. }
  436.  
  437. /*
  438.  * Print a warm fuzzy message when we enter a dir
  439.  *
  440.  * Don't try to diff directories that don't exist! -- DW
  441.  */
  442. /* ARGSUSED */
  443. static Dtype
  444. diff_dirproc (dir, pos_repos, update_dir)
  445.     char *dir;
  446.     char *pos_repos;
  447.     char *update_dir;
  448. {
  449.     /* XXX - check for dirs we don't want to process??? */
  450.  
  451.     /* YES ... for instance dirs that don't exist!!! -- DW */
  452.     if (!isdir (dir) )
  453.       return (R_SKIP_ALL);
  454.   
  455.     if (!quiet)
  456.     error (0, 0, "Diffing %s", update_dir);
  457.     return (R_PROCESS);
  458. }
  459.  
  460. /*
  461.  * Concoct the proper exit status - done with files
  462.  */
  463. /* ARGSUSED */
  464. static int
  465. diff_filesdoneproc (err, repos, update_dir)
  466.     int err;
  467.     char *repos;
  468.     char *update_dir;
  469. {
  470.     return (diff_errors);
  471. }
  472.  
  473. /*
  474.  * Concoct the proper exit status - leaving directories
  475.  */
  476. /* ARGSUSED */
  477. static int
  478. diff_dirleaveproc (dir, err, update_dir)
  479.     char *dir;
  480.     int err;
  481.     char *update_dir;
  482. {
  483.     return (diff_errors);
  484. }
  485.  
  486. /*
  487.  * verify that a file is different 0=same 1=different
  488.  */
  489. static int
  490. diff_file_nodiff (file, repository, entries, rcs, vers)
  491.     char *file;
  492.     char *repository;
  493.     List *entries;
  494.     RCSNode *rcs;
  495.     Vers_TS *vers;
  496. {
  497.     Vers_TS *xvers;
  498.     char tmp[L_tmpnam+1];
  499.     int retcode;
  500.  
  501.     /* free up any old use_rev* variables and reset 'em */
  502.     if (use_rev1)
  503.     free (use_rev1);
  504.     if (use_rev2)
  505.     free (use_rev2);
  506.     use_rev1 = use_rev2 = (char *) NULL;
  507.  
  508.     if (diff_rev1 || diff_date1)
  509.     {
  510.     /* special handling for TAG_HEAD */
  511.     if (diff_rev1 && strcmp (diff_rev1, TAG_HEAD) == 0)
  512.         use_rev1 = xstrdup (vers->vn_rcs);
  513.     else
  514.     {
  515.         xvers = Version_TS (repository, (char *) NULL, diff_rev1,
  516.                 diff_date1, file, 1, 0, entries, rcs);
  517.         if (xvers->vn_rcs == NULL)
  518.         {
  519.         /* Don't gripe if it doesn't exist, just ignore! */
  520.         if (! isfile (file))
  521.                   /* null statement */ ;
  522.         else if (diff_rev1)
  523.                     error (0, 0, "tag %s is not in file %s", diff_rev1, file);
  524.         else
  525.             error (0, 0, "no revision for date %s in file %s",
  526.                diff_date1, file);
  527.  
  528.         freevers_ts (&xvers);
  529.         return (1);
  530.         }
  531.         use_rev1 = xstrdup (xvers->vn_rcs);
  532.         freevers_ts (&xvers);
  533.     }
  534.     }
  535.     if (diff_rev2 || diff_date2)
  536.     {
  537.     /* special handling for TAG_HEAD */
  538.     if (diff_rev2 && strcmp (diff_rev2, TAG_HEAD) == 0)
  539.         use_rev2 = xstrdup (vers->vn_rcs);
  540.     else
  541.     {
  542.         xvers = Version_TS (repository, (char *) NULL, diff_rev2,
  543.                 diff_date2, file, 1, 0, entries, rcs);
  544.         if (xvers->vn_rcs == NULL)
  545.         {
  546.         /* Don't gripe if it doesn't exist, just ignore! */
  547.         if (! isfile (file))
  548.                   /* null statement */ ;
  549.         else if (diff_rev1)
  550.             error (0, 0, "tag %s is not in file %s", diff_rev2, file);
  551.         else
  552.             error (0, 0, "no revision for date %s in file %s",
  553.                diff_date2, file);
  554.  
  555.         freevers_ts (&xvers);
  556.         return (1);
  557.         }
  558.         use_rev2 = xstrdup (xvers->vn_rcs);
  559.         freevers_ts (&xvers);
  560.     }
  561.  
  562.     /* now, see if we really need to do the diff */
  563.     if (use_rev1 && use_rev2) {
  564.         return (strcmp (use_rev1, use_rev2) == 0);
  565.     } else {
  566.         error(0, 0, "No HEAD revision for file %s", file);
  567.         return (1);
  568.     }
  569.     }
  570. #ifdef SERVER_SUPPORT
  571.     if (user_file_rev) 
  572.     {
  573.         /* drop user_file_rev into first unused use_rev */
  574.         if (!use_rev1) 
  575.       use_rev1 = xstrdup (user_file_rev);
  576.     else if (!use_rev2)
  577.       use_rev2 = xstrdup (user_file_rev);
  578.     /* and if not, it wasn't needed anyhow */
  579.     user_file_rev = 0;
  580.     }
  581.  
  582.     /* now, see if we really need to do the diff */
  583.     if (use_rev1 && use_rev2) 
  584.     {
  585.     return (strcmp (use_rev1, use_rev2) == 0);
  586.     }
  587. #endif /* SERVER_SUPPORT */
  588.     if (use_rev1 == NULL || strcmp (use_rev1, vers->vn_user) == 0)
  589.     {
  590.     if (strcmp (vers->ts_rcs, vers->ts_user) == 0 &&
  591.         (!(*options) || strcmp (options, vers->options) == 0))
  592.     {
  593.         return (1);
  594.     }
  595.     if (use_rev1 == NULL)
  596.         use_rev1 = xstrdup (vers->vn_user);
  597.     }
  598.  
  599.     /*
  600.      * with 0 or 1 -r option specified, run a quick diff to see if we
  601.      * should bother with it at all.
  602.      */
  603.     retcode = RCS_checkout (vers->srcfile->path, NULL, use_rev1,
  604.                             *options ? options : vers->options, tmpnam (tmp), 0, 0);
  605.     switch (retcode)
  606.     {
  607.     case 0:                /* everything ok */
  608.         if (xcmp (file, tmp) == 0)
  609.         {
  610.         (void) unlink (tmp);
  611.         return (1);
  612.         }
  613.         break;
  614.     case -1:            /* fork failed */
  615.         (void) unlink (tmp);
  616.         error (1, errno, "fork failed during checkout of %s",
  617.            vers->srcfile->path);
  618.     default:
  619.         break;
  620.     }
  621.     (void) unlink (tmp);
  622.     return (0);
  623. }
  624.