home *** CD-ROM | disk | FTP | other *** search
/ Club Amiga de Montreal - CAM / CAM_CD_1.iso / files / 317a.lha / RCS / diff / diff.c < prev    next >
C/C++ Source or Header  |  1989-12-05  |  15KB  |  617 lines

  1. /* GNU DIFF main routine.
  2.    Copyright (C) 1988, 1989 Free Software Foundation, Inc.
  3.  
  4. This file is part of GNU DIFF.
  5.  
  6. GNU DIFF is free software; you can redistribute it and/or modify
  7. it under the terms of the GNU General Public License as published by
  8. the Free Software Foundation; either version 1, or (at your option)
  9. any later version.
  10.  
  11. GNU DIFF is distributed in the hope that it will be useful,
  12. but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14. GNU General Public License for more details.
  15.  
  16. You should have received a copy of the GNU General Public License
  17. along with GNU DIFF; see the file COPYING.  If not, write to
  18. the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */
  19.  
  20. /* GNU DIFF was written by Mike Haertel, David Hayes,
  21.    Richard Stallman and Len Tower.  */
  22.  
  23. #define GDIFF_MAIN
  24. #include "regex.h"
  25. #include "diff.h"
  26.  
  27.  
  28. /* Nonzero for -r: if comparing two directories,
  29.    compare their common subdirectories recursively.  */
  30.  
  31. int recursive;
  32.  
  33. /* For debugging: don't do discard_confusing_lines.  */
  34.  
  35. int no_discards;
  36.  
  37. void specify_style();
  38.  
  39. /* Return a string containing the command options with which diff was invoked.
  40.    Spaces appear between what were separate ARGV-elements.
  41.    There is a space at the beginning but none at the end.
  42.    If there were no options, the result is an empty string.
  43.  
  44.    Arguments: VECTOR, a vector containing separate ARGV-elements, and COUNT,
  45.    the length of that vector.  */
  46.  
  47. static char *
  48. option_list (vector, count)
  49.      char **vector;
  50.      int count;
  51. {
  52.   int i;
  53.   int length = 0;
  54.   char *result;
  55.  
  56.   for (i = 0; i < count; i++)
  57.     length += strlen (vector[i]) + 1;
  58.  
  59.   result = (char *) xmalloc (length + 1);
  60.   result[0] = 0;
  61.  
  62.   for (i = 0; i < count; i++)
  63.     {
  64.       strcat (result, " ");
  65.       strcat (result, vector[i]);
  66.     }
  67.  
  68.   return result;
  69. }
  70.  
  71. main (argc, argv)
  72.      int argc;
  73.      char *argv[];
  74. {
  75.   int val;
  76.   int c;
  77.   int prev = -1;
  78.  
  79.   extern int optind;
  80.   extern char *optarg;
  81.  
  82.   program = argv[0];
  83.  
  84.   /* Do our initializations. */
  85.   output_style = OUTPUT_NORMAL;
  86.   always_text_flag = FALSE;
  87.   ignore_space_change_flag = FALSE;
  88.   ignore_all_space_flag = FALSE;
  89.   length_varies = FALSE;
  90.   ignore_case_flag = FALSE;
  91.   ignore_blank_lines_flag = FALSE;
  92.   ignore_regexp = 0;
  93.   function_regexp = 0;
  94.   print_file_same_flag = FALSE;
  95.   entire_new_file_flag = FALSE;
  96.   context = -1;
  97.   line_end_char = '\n';
  98.   tab_align_flag = FALSE;
  99.   tab_expand_flag = FALSE;
  100.   recursive = FALSE;
  101.   paginate_flag = FALSE;
  102.   heuristic = FALSE;
  103.   dir_start_file = NULL;
  104.   msg_chain = NULL;
  105.   msg_chain_end = NULL;
  106.   no_discards = 0;
  107.  
  108.   /* Decode the options.  */
  109.  
  110.   while ((c = getopt (argc, argv, "0123456789abBcC:defF:hHiI:lnNprsS:tTw"))
  111.      != EOF)
  112.     {
  113.       switch (c)
  114.     {
  115.       /* All digits combine in decimal to specify the context-size.  */
  116.     case '1':
  117.     case '2':
  118.     case '3':
  119.     case '4':
  120.     case '5':
  121.     case '6':
  122.     case '7':
  123.     case '8':
  124.     case '9':
  125.     case '0':
  126.       if (context == -1)
  127.         context = 0;
  128.       /* If a context length has already been specified,
  129.          more digits allowed only if they follow right after the others.
  130.          Reject two separate runs of digits, or digits after -C.  */
  131.       else if (prev < '0' || prev > '9')
  132.         fatal ("context length specified twice");
  133.  
  134.       context = context * 10 + c - '0';
  135.       break;
  136.  
  137.     case 'a':
  138.       /* Treat all files as text files; never treat as binary.  */
  139.       always_text_flag = 1;
  140.       break;
  141.  
  142.     case 'b':
  143.       /* Ignore changes in amount of whitespace.  */
  144.       ignore_space_change_flag = 1;
  145.       length_varies = 1;
  146.       break;
  147.  
  148.     case 'B':
  149.       /* Ignore changes affecting only blank lines.  */
  150.       ignore_blank_lines_flag = 1;
  151.       break;
  152.  
  153.     case 'c':
  154.       /* Make context-style output.  */
  155.       specify_style (OUTPUT_CONTEXT);
  156.       break;
  157.  
  158.     case 'C':
  159.       if (context >= 0)
  160.         fatal ("context length specified twice");
  161.       {
  162.         char *p;
  163.         for (p = optarg; *p; p++)
  164.           if (*p < '0' || *p > '9')
  165.         fatal ("invalid context length argument (-C option)");
  166.       }
  167.       context = atoi (optarg);
  168.       break;
  169.  
  170.     case 'd':
  171.       /* Don't discard lines.  This makes things slower (sometimes much
  172.          slower) but will find a guaranteed minimal set of changes.  */
  173.       no_discards = 1;
  174.       break;
  175.  
  176.     case 'e':
  177.       /* Make output that is a valid `ed' script.  */
  178.       specify_style (OUTPUT_ED);
  179.       break;
  180.  
  181.     case 'f':
  182.       /* Make output that looks vaguely like an `ed' script
  183.          but has changes in the order they appear in the file.  */
  184.       specify_style (OUTPUT_FORWARD_ED);
  185.       break;
  186.  
  187.     case 'F':
  188.       /* Show, for each set of changes, the previous line that
  189.          matches the specified regexp.  Currently affects only
  190.          context-style output.  */
  191.       function_regexp = optarg;
  192.       break;
  193.  
  194.     case 'h':
  195.       /* Split the files into chunks of around 1500 lines
  196.          for faster processing.  Usually does not change the result.
  197.  
  198.          This currently has no effect.  */
  199.       break;
  200.  
  201.     case 'H':
  202.       /* Turn on heuristics that speed processing of large files
  203.          with a small density of changes.  */
  204.       heuristic = 1;
  205.       break;
  206.  
  207.     case 'i':
  208.       /* Ignore changes in case.  */
  209.       ignore_case_flag = 1;
  210.       break;
  211.  
  212.     case 'I':
  213.       /* Ignore changes affecting only lines that match the
  214.          specified regexp.  */
  215.       ignore_regexp = optarg;
  216.       break;
  217.  
  218.     case 'l':
  219.       /* Pass the output through `pr' to paginate it.  */
  220.       paginate_flag = 1;
  221.       break;
  222.  
  223.     case 'n':
  224.       /* Output RCS-style diffs, like `-f' except that each command
  225.          specifies the number of lines affected.  */
  226.       specify_style (OUTPUT_RCS);
  227.       break;
  228.  
  229.     case 'N':
  230.       /* When comparing directories, if a file appears only in one
  231.          directory, treat it as present but empty in the other.  */
  232.       entire_new_file_flag = 1;
  233.       break;
  234.  
  235.     case 'p':
  236.       /* Make context-style output and show name of last C function.  */
  237.       specify_style (OUTPUT_CONTEXT);
  238.       function_regexp = "^[_a-zA-Z]";
  239.       break;
  240.  
  241.     case 'r':
  242.       /* When comparing directories, 
  243.          recursively compare any subdirectories found.  */
  244.       recursive = 1;
  245.       break;
  246.  
  247.     case 's':
  248.       /* Print a message if the files are the same.  */
  249.       print_file_same_flag = 1;
  250.       break;
  251.  
  252.     case 'S':
  253.       /* When comparing directories, start with the specified
  254.          file name.  This is used for resuming an aborted comparison.  */
  255.       dir_start_file = optarg;
  256.       break;
  257.  
  258.     case 't':
  259.       /* Expand tabs to spaces in the output so that it preserves
  260.          the alignment of the input files.  */
  261.       tab_expand_flag = 1;
  262.       break;
  263.  
  264.     case 'T':
  265.       /* Use a tab in the output, rather than a space, before the
  266.          text of an input line, so as to keep the proper alignment
  267.          in the input line without changing the characters in it.  */
  268.       tab_align_flag = 1;
  269.       break;
  270.  
  271.     case 'w':
  272.       /* Ignore horizontal whitespace when comparing lines.  */
  273.       ignore_all_space_flag = 1;
  274.       length_varies = 1;
  275.       break;
  276.     }
  277.       prev = c;
  278.     }
  279.  
  280.   if (optind != argc - 2)
  281.     fatal ("requires two file names.  Usage: diff [-options] file1 file2");
  282.  
  283.   /*
  284.    * @@ need more complicated usage string for directory options??
  285.    * Note three liner at top of BSD documentation, and John Gilmore
  286.    * message in his public domain tar being used by GNU.  
  287.    */
  288.  
  289.   if (ignore_regexp)
  290.     {
  291.       char *val;
  292.       bzero (&ignore_regexp_compiled, sizeof ignore_regexp_compiled);
  293.       val = re_compile_pattern (ignore_regexp, strlen (ignore_regexp),
  294.                 &ignore_regexp_compiled);
  295.       if (val != 0)
  296.     error ("-I option: ", val);
  297.       ignore_regexp_compiled.fastmap = (char *) xmalloc (256);
  298.     }
  299.  
  300.   if (function_regexp)
  301.     {
  302.       char *val;
  303.       bzero (&function_regexp_compiled, sizeof function_regexp_compiled);
  304.       val = re_compile_pattern (function_regexp, strlen (function_regexp),
  305.                 &function_regexp_compiled);
  306.       if (val != 0)
  307.     error ("-F option: ", val);
  308.       function_regexp_compiled.fastmap = (char *) xmalloc (256);
  309.     }
  310.  
  311.   if (output_style != OUTPUT_CONTEXT)
  312.     context = 0;
  313.   else if (context == -1)
  314.     /* Default amount of context for -c.  */
  315.     context = 3;
  316.  
  317.   switch_string = option_list (argv + 1, optind - 1);
  318.  
  319.   val = compare_files (0, argv[optind], 0, argv[optind + 1], 0);
  320.  
  321.   /* Print any messages that were saved up for last.  */
  322.   print_message_queue ();
  323.  
  324.   exit (val);
  325. }
  326.  
  327. void specify_style (style)
  328.      enum output_style style;
  329. {
  330.   if (output_style != OUTPUT_NORMAL
  331.       && output_style != style)
  332.     error ("conflicting specifications of output style");
  333.   output_style = style;
  334. }
  335.  
  336. /* Compare two files (or dirs) with specified names
  337.    DIR0/NAME0 and DIR1/NAME1, at level DEPTH in directory recursion.
  338.    (if DIR0 is 0, then the name is just NAME0, etc.)
  339.    This is self-contained; it opens the files and closes them.
  340.  
  341.    Value is 0 if files are identical, 1 if different,
  342.    2 if there is a problem opening them.  */
  343.  
  344. int
  345. compare_files (dir0, name0, dir1, name1, depth)
  346.      char *dir0, *dir1;
  347.      char *name0, *name1;
  348.      int depth;
  349. {
  350.   struct file_data inf[2];
  351.   register int i;
  352.   int val;
  353.   int errorcount = 0;
  354.   int stat_result[2];
  355.  
  356.   /* If this is directory comparison, perhaps we have a file
  357.      that exists only in one of the directories.
  358.      If so, just print a message to that effect.  */
  359.  
  360.   if (! entire_new_file_flag && (name0 == 0 || name1 == 0))
  361.     {
  362.       char *name = name0 == 0 ? name1 : name0;
  363.       char *dir = name0 == 0 ? dir1 : dir0;
  364.       message ("Only in %s: %s\n", dir, name);
  365.       /* Return 1 so that diff_dirs will return 1 ("some files differ").  */
  366.       return 1;
  367.     }
  368.  
  369.   /* Mark any nonexistent file with -1 in the desc field.  */
  370.  
  371.   inf[0].desc = name0 == 0 ? -1 : 0;
  372.   inf[1].desc = name1 == 0 ? -1 : 0;
  373.  
  374.   /* Now record the full name of each file, including nonexistent ones.  */
  375.  
  376.   if (name0 == 0)
  377.     name0 = name1;
  378.   if (name1 == 0)
  379.     name1 = name0;
  380.  
  381.   inf[0].name = dir0 == 0 ? name0 : concat (dir0, "/", name0);
  382.   inf[1].name = dir1 == 0 ? name1 : concat (dir1, "/", name1);
  383.  
  384.   /* Stat the files.  Record whether they are directories.
  385.      Record in stat_result whether stat fails.  */
  386.  
  387.   for (i = 0; i <= 1; i++)
  388.     {
  389.       inf[i].stat.st_size = 0;
  390.       inf[i].stat.st_mtime = 0;
  391.       inf[i].dir_p = 0;
  392.       stat_result[i] = 0;
  393.  
  394.       if (inf[i].desc != -1
  395.       && strcmp (inf[i].name, "-"))
  396.     {
  397.       char *filename = inf[i].name;
  398.  
  399.       stat_result[i] = stat (filename, &inf[i].stat);
  400.       if (stat_result[i] < 0)
  401.         {
  402.           perror_with_name (filename);
  403.           errorcount = 1;
  404.         }
  405.       else
  406. #ifdef AMIGA
  407.         inf[i].dir_p = (inf[i].stat.st_type > 0);
  408. #else
  409.         inf[i].dir_p = (S_IFDIR == (inf[i].stat.st_mode & S_IFMT));
  410. #endif
  411.     }
  412.     }
  413.  
  414.   /* See if the two named files are actually the same physical file.
  415.      If so, we know they are identical without actually reading them.  */
  416.  
  417. #ifndef AMIGA
  418.   if (inf[0].stat.st_ino == inf[1].stat.st_ino
  419.       && inf[0].stat.st_dev == inf[1].stat.st_dev
  420.       && stat_result[0] == 0
  421.       && stat_result[1] == 0)
  422.     {
  423.       val = 0;
  424.       goto done;
  425.     }
  426. #endif
  427.  
  428.   if (name0 == 0)
  429.     inf[0].dir_p = inf[1].dir_p;
  430.   if (name1 == 0)
  431.     inf[1].dir_p = inf[0].dir_p;
  432.  
  433.   /* Open the files and record their descriptors.  */
  434.  
  435.   for (i = 0; i <= 1; i++)
  436.     {
  437.       if (inf[i].desc == -1)
  438.     ;
  439.       else if (!strcmp (inf[i].name, "-"))
  440.     {
  441.       inf[i].desc = fileno(stdin);
  442.       inf[i].name = "Standard Input";
  443. #ifdef AMIGA
  444.       inf[i].stat.st_type = -1;
  445. #endif
  446.     }
  447.       /* Don't bother opening if stat already failed.  */
  448.       else if (stat_result[i] == 0 && ! inf[i].dir_p)
  449.     {
  450.       char *filename = inf[i].name;
  451.  
  452.       inf[i].desc = open (filename, O_RDONLY, 0);
  453.       if (0 > inf[i].desc)
  454.         {
  455.           perror_with_name (filename);
  456.           errorcount = 1;
  457.         }
  458.     }
  459.     }
  460.  
  461.   if (errorcount)
  462.     {
  463.  
  464.       /* If either file should exist but fails to be opened, return 2.  */
  465.  
  466.       val = 2;
  467.  
  468.     }
  469.   else if (inf[0].dir_p && inf[1].dir_p)
  470.     {
  471.  
  472.       /* If both are directories, compare the files in them.  */
  473.  
  474.       if (depth > 0 && !recursive)
  475.     {
  476.       /* But don't compare dir contents one level down
  477.          unless -r was specified.  */
  478.       message ("Common subdirectories: %s and %s\n",
  479.            inf[0].name, inf[1].name);
  480.       val = 0;
  481.     }
  482.       else
  483.     {
  484.       val = diff_dirs (inf[0].name, inf[1].name, 
  485.                compare_files, depth, 0, 0);
  486.     }
  487.  
  488.     }
  489.   else if (depth == 0 && (inf[0].dir_p || inf[1].dir_p))
  490.     {
  491.  
  492.       /* If only one is a directory, and it was specified in the command line,
  493.      use the file in that dir whose basename matches the other file.  */
  494.  
  495.       int dir_arg = (inf[0].dir_p ? 0 : 1);
  496.       int fnm_arg = (inf[0].dir_p ? 1 : 0);
  497.       char *p = strrchr(inf[fnm_arg].name, '/');
  498.       char *filename = concat (inf[dir_arg].name,  "/",
  499.                    (p ? p+1 : inf[fnm_arg].name));
  500.  
  501. #ifdef AMIGA
  502.       inf[dir_arg].dir_p = (getfa(filename) == 1);
  503.       if (!inf[dir_arg].dir_p)
  504.         {
  505.         if (stat (filename,&inf[dir_arg].stat) < 0)
  506.       pfatal_with_name (filename);
  507.         inf[dir_arg].desc = open (filename, O_RDONLY, 0);
  508.         if (0 > inf[dir_arg].desc)
  509.           {
  510.             perror_with_name (filename);
  511.             val = 2;
  512.           }
  513. /*    free (inf[dir_arg].name); */
  514.     inf[dir_arg].name = filename;
  515.     val = diff_2_files (inf, depth);
  516.         }
  517.       else
  518.     {
  519.       error ("%s is a directory but %s is not",
  520.        inf[dir_arg].name, inf[fnm_arg].name);
  521.       val = 2;
  522.     }
  523. #else
  524.       inf[dir_arg].desc = open (filename, O_RDONLY, 0);
  525.  
  526.       if (0 > inf[dir_arg].desc)
  527.     {
  528.       perror_with_name (filename);
  529.       val = 2;
  530.     }
  531.       else
  532.     {
  533.       /* JF: patch from the net to check and make sure we can really free
  534.          this.  If it's from argv[], freeing it is a *really* bad idea */
  535.       if (0 != (dir_arg ? dir1 : dir0))
  536.         free (inf[dir_arg].name);
  537.       inf[dir_arg].name = filename;
  538.       if (fstat (inf[dir_arg].desc, &inf[dir_arg].stat) < 0)
  539.         pfatal_with_name (inf[dir_arg].name);
  540.  
  541.       inf[dir_arg].dir_p
  542.         = (S_IFDIR == (inf[dir_arg].stat.st_mode & S_IFMT));
  543.       if (inf[dir_arg].dir_p)
  544.         {
  545.           error ("%s is a directory but %s is not",
  546.              inf[dir_arg].name, inf[fnm_arg].name);
  547.           val = 1;
  548.         }
  549.       else
  550.         val = diff_2_files (inf, depth);
  551.     }
  552. #endif
  553.     }
  554.   else if (depth > 0 && (inf[0].dir_p || inf[1].dir_p))
  555.     {
  556.       /* Perhaps we have a subdirectory that exists only in one directory.
  557.      If so, just print a message to that effect.  */
  558.  
  559.       if (inf[0].desc == -1 || inf[1].desc == -1)
  560.     {
  561.       if (entire_new_file_flag && recursive)
  562.         val = diff_dirs (inf[0].name, inf[1].name, compare_files, depth,
  563.                  inf[0].desc == -1, inf[1].desc == -1);
  564.       else
  565.         {
  566.           char *dir = (inf[0].desc == -1) ? dir1 : dir0;
  567.           message ("Only in %s: %s\n", dir, name0);
  568.           val = 1;
  569.         }
  570.     }
  571.       else
  572.     {
  573.       /* We have a subdirectory in one directory
  574.          and a file in the other.  */
  575.  
  576.       if (inf[0].dir_p)
  577.         message ("%s is a directory but %s is not\n",
  578.              inf[0].name, inf[1].name);
  579.       else
  580.         message ("%s is a directory but %s is not\n",
  581.              inf[1].name, inf[0].name);
  582.       /* This is a difference.  */
  583.       val = 1;
  584.     }
  585.     }
  586.   else
  587.     {
  588.  
  589.       /* Both exist and both are ordinary files.  */
  590.  
  591.       val = diff_2_files (inf, depth);
  592.  
  593.     }
  594.  
  595.   /* Now the comparison has been done, if no error prevented it,
  596.      and VAL is the value this function will return.  */
  597.  
  598.   if (inf[0].desc > 0)
  599.     close (inf[0].desc);
  600.   if (inf[1].desc > 0)
  601.     close (inf[1].desc);
  602.  
  603.  done:
  604.   if (val == 0 && print_file_same_flag && !inf[0].dir_p)
  605.     message ("Files %s and %s are identical\n",
  606.          inf[0].name, inf[1].name);
  607.  
  608.   fflush (stdout);
  609.  
  610.   if (dir0 != 0)
  611.     free (inf[0].name);
  612.   if (dir1 != 0)
  613.     free (inf[1].name);
  614.  
  615.   return val;
  616. }
  617.