(root)/
diffutils-3.10/
src/
context.c
       1  /* Context-format output routines for GNU DIFF.
       2  
       3     Copyright (C) 1988-1989, 1991-1995, 1998, 2001-2002, 2004, 2006, 2009-2013,
       4     2015-2023 Free Software Foundation, Inc.
       5  
       6     This file is part of GNU DIFF.
       7  
       8     This program is free software: you can redistribute it and/or modify
       9     it under the terms of the GNU General Public License as published by
      10     the Free Software Foundation, either version 3 of the License, or
      11     (at your option) any later version.
      12  
      13     This program is distributed in the hope that it will be useful,
      14     but WITHOUT ANY WARRANTY; without even the implied warranty of
      15     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      16     GNU General Public License for more details.
      17  
      18     You should have received a copy of the GNU General Public License
      19     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
      20  
      21  #include "diff.h"
      22  #include "c-ctype.h"
      23  #include <stat-time.h>
      24  #include <strftime.h>
      25  
      26  static char const *find_function (char const * const *, lin);
      27  static struct change *find_hunk (struct change *);
      28  static void mark_ignorable (struct change *);
      29  static void pr_context_hunk (struct change *);
      30  static void pr_unidiff_hunk (struct change *);
      31  
      32  /* Last place find_function started searching from.  */
      33  static lin find_function_last_search;
      34  
      35  /* The value find_function returned when it started searching there.  */
      36  static lin find_function_last_match;
      37  
      38  /* Print a label for a context diff, with a file name and date or a label.  */
      39  
      40  static void
      41  print_context_label (char const *mark,
      42                       struct file_data *inf,
      43                       char const *name,
      44                       char const *label)
      45  {
      46    set_color_context (HEADER_CONTEXT);
      47    if (label)
      48      fprintf (outfile, "%s %s", mark, label);
      49    else
      50      {
      51        char buf[MAX (INT_STRLEN_BOUND (int) + 32,
      52                      INT_STRLEN_BOUND (time_t) + 11)];
      53        struct tm const *tm = localtime (&inf->stat.st_mtime);
      54        int nsec = get_stat_mtime_ns (&inf->stat);
      55        if (! (tm && nstrftime (buf, sizeof buf, time_format, tm, localtz, nsec)))
      56          {
      57            verify (TYPE_IS_INTEGER (time_t));
      58            if (LONG_MIN <= TYPE_MINIMUM (time_t)
      59                && TYPE_MAXIMUM (time_t) <= LONG_MAX)
      60              {
      61                long int sec = inf->stat.st_mtime;
      62                sprintf (buf, "%ld.%.9d", sec, nsec);
      63              }
      64            else if (TYPE_MAXIMUM (time_t) <= INTMAX_MAX)
      65              {
      66                intmax_t sec = inf->stat.st_mtime;
      67                sprintf (buf, "%"PRIdMAX".%.9d", sec, nsec);
      68              }
      69            else
      70              {
      71                uintmax_t sec = inf->stat.st_mtime;
      72                sprintf (buf, "%"PRIuMAX".%.9d", sec, nsec);
      73              }
      74          }
      75        fprintf (outfile, "%s %s\t%s", mark, name, buf);
      76      }
      77    set_color_context (RESET_CONTEXT);
      78    putc ('\n', outfile);
      79  }
      80  
      81  /* Print a header for a context diff, with the file names and dates.  */
      82  
      83  void
      84  print_context_header (struct file_data inf[], char const *const *names, bool unidiff)
      85  {
      86    if (unidiff)
      87      {
      88        print_context_label ("---", &inf[0], names[0], file_label[0]);
      89        print_context_label ("+++", &inf[1], names[1], file_label[1]);
      90      }
      91    else
      92      {
      93        print_context_label ("***", &inf[0], names[0], file_label[0]);
      94        print_context_label ("---", &inf[1], names[1], file_label[1]);
      95      }
      96  }
      97  
      98  /* Print an edit script in context format.  */
      99  
     100  void
     101  print_context_script (struct change *script, bool unidiff)
     102  {
     103    if (ignore_blank_lines || ignore_regexp.fastmap)
     104      mark_ignorable (script);
     105    else
     106      {
     107        struct change *e;
     108        for (e = script; e; e = e->link)
     109          e->ignore = false;
     110      }
     111  
     112    find_function_last_search = - files[0].prefix_lines;
     113    find_function_last_match = LIN_MAX;
     114  
     115    if (unidiff)
     116      print_script (script, find_hunk, pr_unidiff_hunk);
     117    else
     118      print_script (script, find_hunk, pr_context_hunk);
     119  }
     120  
     121  /* Print a pair of line numbers with a comma, translated for file FILE.
     122     If the second number is not greater, use the first in place of it.
     123  
     124     Args A and B are internal line numbers.
     125     We print the translated (real) line numbers.  */
     126  
     127  static void
     128  print_context_number_range (struct file_data const *file, lin a, lin b)
     129  {
     130    lin trans_a, trans_b;
     131    translate_range (file, a, b, &trans_a, &trans_b);
     132  
     133    /* We can have B <= A in the case of a range of no lines.
     134       In this case, we should print the line number before the range,
     135       which is B.
     136  
     137       POSIX 1003.1-2001 requires two line numbers separated by a comma
     138       even if the line numbers are the same.  However, this does not
     139       match existing practice and is surely an error in the
     140       specification.  */
     141  
     142    if (trans_b <= trans_a)
     143      fprintf (outfile, "%"pI"d", trans_b);
     144    else
     145      fprintf (outfile, "%"pI"d,%"pI"d", trans_a, trans_b);
     146  }
     147  
     148  /* Print FUNCTION in a context header.  */
     149  static void
     150  print_context_function (FILE *out, char const *function)
     151  {
     152    int i, j;
     153    putc (' ', out);
     154    for (i = 0; c_isspace ((unsigned char) function[i]) && function[i] != '\n'; i++)
     155      continue;
     156    for (j = i; j < i + 40 && function[j] != '\n'; j++)
     157      continue;
     158    while (i < j && c_isspace ((unsigned char) function[j - 1]))
     159      j--;
     160    fwrite (function + i, sizeof (char), j - i, out);
     161  }
     162  
     163  /* Print a portion of an edit script in context format.
     164     HUNK is the beginning of the portion to be printed.
     165     The end is marked by a 'link' that has been nulled out.
     166  
     167     Prints out lines from both files, and precedes each
     168     line with the appropriate flag-character.  */
     169  
     170  static void
     171  pr_context_hunk (struct change *hunk)
     172  {
     173    lin first0, last0, first1, last1, i;
     174    char const *prefix;
     175    char const *function;
     176    FILE *out;
     177  
     178    /* Determine range of line numbers involved in each file.  */
     179  
     180    enum changes changes = analyze_hunk (hunk, &first0, &last0, &first1, &last1);
     181    if (! changes)
     182      return;
     183  
     184    /* Include a context's width before and after.  */
     185  
     186    i = - files[0].prefix_lines;
     187    first0 = MAX (first0 - context, i);
     188    first1 = MAX (first1 - context, i);
     189    if (last0 < files[0].valid_lines - context)
     190      last0 += context;
     191    else
     192      last0 = files[0].valid_lines - 1;
     193    if (last1 < files[1].valid_lines - context)
     194      last1 += context;
     195    else
     196      last1 = files[1].valid_lines - 1;
     197  
     198    /* If desired, find the preceding function definition line in file 0.  */
     199    function = nullptr;
     200    if (function_regexp.fastmap)
     201      function = find_function (files[0].linbuf, first0);
     202  
     203    begin_output ();
     204    out = outfile;
     205  
     206    fputs ("***************", out);
     207  
     208    if (function)
     209      print_context_function (out, function);
     210  
     211    putc ('\n', out);
     212    set_color_context (LINE_NUMBER_CONTEXT);
     213    fputs ("*** ", out);
     214    print_context_number_range (&files[0], first0, last0);
     215    fputs (" ****", out);
     216    set_color_context (RESET_CONTEXT);
     217    putc ('\n', out);
     218  
     219    if (changes & OLD)
     220      {
     221        struct change *next = hunk;
     222  
     223        for (i = first0; i <= last0; i++)
     224          {
     225            set_color_context (DELETE_CONTEXT);
     226  
     227            /* Skip past changes that apply (in file 0)
     228               only to lines before line I.  */
     229  
     230            while (next && next->line0 + next->deleted <= i)
     231              next = next->link;
     232  
     233            /* Compute the marking for line I.  */
     234  
     235            prefix = " ";
     236            if (next && next->line0 <= i)
     237              {
     238                /* The change NEXT covers this line.
     239                   If lines were inserted here in file 1, this is "changed".
     240                   Otherwise it is "deleted".  */
     241                prefix = (next->inserted > 0 ? "!" : "-");
     242              }
     243            print_1_line_nl (prefix, &files[0].linbuf[i], true);
     244            set_color_context (RESET_CONTEXT);
     245            if (files[0].linbuf[i + 1][-1] == '\n')
     246              putc ('\n', out);
     247          }
     248      }
     249  
     250    set_color_context (LINE_NUMBER_CONTEXT);
     251    fputs ("--- ", out);
     252    print_context_number_range (&files[1], first1, last1);
     253    fputs (" ----", out);
     254    set_color_context (RESET_CONTEXT);
     255    putc ('\n', out);
     256  
     257    if (changes & NEW)
     258      {
     259        struct change *next = hunk;
     260  
     261        for (i = first1; i <= last1; i++)
     262          {
     263            set_color_context (ADD_CONTEXT);
     264  
     265            /* Skip past changes that apply (in file 1)
     266               only to lines before line I.  */
     267  
     268            while (next && next->line1 + next->inserted <= i)
     269              next = next->link;
     270  
     271            /* Compute the marking for line I.  */
     272  
     273            prefix = " ";
     274            if (next && next->line1 <= i)
     275              {
     276                /* The change NEXT covers this line.
     277                   If lines were deleted here in file 0, this is "changed".
     278                   Otherwise it is "inserted".  */
     279                prefix = (next->deleted > 0 ? "!" : "+");
     280              }
     281            print_1_line_nl (prefix, &files[1].linbuf[i], true);
     282            set_color_context (RESET_CONTEXT);
     283            if (files[1].linbuf[i + 1][-1] == '\n')
     284              putc ('\n', out);
     285          }
     286      }
     287  }
     288  
     289  /* Print a pair of line numbers with a comma, translated for file FILE.
     290     If the second number is smaller, use the first in place of it.
     291     If the numbers are equal, print just one number.
     292  
     293     Args A and B are internal line numbers.
     294     We print the translated (real) line numbers.  */
     295  
     296  static void
     297  print_unidiff_number_range (struct file_data const *file, lin a, lin b)
     298  {
     299    lin trans_a, trans_b;
     300    translate_range (file, a, b, &trans_a, &trans_b);
     301  
     302    /* We can have B < A in the case of a range of no lines.
     303       In this case, we print the line number before the range,
     304       which is B.  It would be more logical to print A, but
     305       'patch' expects B in order to detect diffs against empty files.  */
     306    if (trans_b <= trans_a)
     307      fprintf (outfile, trans_b < trans_a ? "%"pI"d,0" : "%"pI"d", trans_b);
     308    else
     309      fprintf (outfile, "%"pI"d,%"pI"d", trans_a, trans_b - trans_a + 1);
     310  }
     311  
     312  /* Print a portion of an edit script in unidiff format.
     313     HUNK is the beginning of the portion to be printed.
     314     The end is marked by a 'link' that has been nulled out.
     315  
     316     Prints out lines from both files, and precedes each
     317     line with the appropriate flag-character.  */
     318  
     319  static void
     320  pr_unidiff_hunk (struct change *hunk)
     321  {
     322    lin first0, last0, first1, last1;
     323    lin i, j, k;
     324    struct change *next;
     325    char const *function;
     326    FILE *out;
     327  
     328    /* Determine range of line numbers involved in each file.  */
     329  
     330    if (! analyze_hunk (hunk, &first0, &last0, &first1, &last1))
     331      return;
     332  
     333    /* Include a context's width before and after.  */
     334  
     335    i = - files[0].prefix_lines;
     336    first0 = MAX (first0 - context, i);
     337    first1 = MAX (first1 - context, i);
     338    if (last0 < files[0].valid_lines - context)
     339      last0 += context;
     340    else
     341      last0 = files[0].valid_lines - 1;
     342    if (last1 < files[1].valid_lines - context)
     343      last1 += context;
     344    else
     345      last1 = files[1].valid_lines - 1;
     346  
     347    /* If desired, find the preceding function definition line in file 0.  */
     348    function = nullptr;
     349    if (function_regexp.fastmap)
     350      function = find_function (files[0].linbuf, first0);
     351  
     352    begin_output ();
     353    out = outfile;
     354  
     355    set_color_context (LINE_NUMBER_CONTEXT);
     356    fputs ("@@ -", out);
     357    print_unidiff_number_range (&files[0], first0, last0);
     358    fputs (" +", out);
     359    print_unidiff_number_range (&files[1], first1, last1);
     360    fputs (" @@", out);
     361    set_color_context (RESET_CONTEXT);
     362  
     363    if (function)
     364      print_context_function (out, function);
     365  
     366    putc ('\n', out);
     367  
     368    next = hunk;
     369    i = first0;
     370    j = first1;
     371  
     372    while (i <= last0 || j <= last1)
     373      {
     374  
     375        /* If the line isn't a difference, output the context from file 0. */
     376  
     377        if (!next || i < next->line0)
     378          {
     379            char const *const *line = &files[0].linbuf[i++];
     380            if (! (suppress_blank_empty && **line == '\n'))
     381              putc (initial_tab ? '\t' : ' ', out);
     382            print_1_line (nullptr, line);
     383            j++;
     384          }
     385        else
     386          {
     387            /* For each difference, first output the deleted part. */
     388  
     389            k = next->deleted;
     390  
     391            while (k--)
     392              {
     393                char const * const *line = &files[0].linbuf[i++];
     394                set_color_context (DELETE_CONTEXT);
     395                putc ('-', out);
     396                if (initial_tab && ! (suppress_blank_empty && **line == '\n'))
     397                  putc ('\t', out);
     398                print_1_line_nl (nullptr, line, true);
     399  
     400                set_color_context (RESET_CONTEXT);
     401  
     402                if (line[1][-1] == '\n')
     403                  putc ('\n', out);
     404              }
     405  
     406            /* Then output the inserted part. */
     407  
     408            k = next->inserted;
     409  
     410            while (k--)
     411              {
     412                char const * const *line = &files[1].linbuf[j++];
     413                set_color_context (ADD_CONTEXT);
     414                putc ('+', out);
     415                if (initial_tab && ! (suppress_blank_empty && **line == '\n'))
     416                  putc ('\t', out);
     417                print_1_line_nl (nullptr, line, true);
     418  
     419                set_color_context (RESET_CONTEXT);
     420  
     421                if (line[1][-1] == '\n')
     422                  putc ('\n', out);
     423              }
     424  
     425            /* We're done with this hunk, so on to the next! */
     426  
     427            next = next->link;
     428          }
     429      }
     430  }
     431  
     432  /* Scan a (forward-ordered) edit script for the first place that more than
     433     2*CONTEXT unchanged lines appear, and return a pointer
     434     to the 'struct change' for the last change before those lines.  */
     435  
     436  static struct change * ATTRIBUTE_PURE
     437  find_hunk (struct change *start)
     438  {
     439    struct change *prev;
     440    lin top0, top1;
     441    lin thresh;
     442  
     443    /* Threshold distance is CONTEXT if the second change is ignorable,
     444       2 * CONTEXT + 1 otherwise.  Integer overflow can't happen, due
     445       to CONTEXT_LIM.  */
     446    lin ignorable_threshold = context;
     447    lin non_ignorable_threshold = 2 * context + 1;
     448  
     449    do
     450      {
     451        /* Compute number of first line in each file beyond this changed.  */
     452        top0 = start->line0 + start->deleted;
     453        top1 = start->line1 + start->inserted;
     454        prev = start;
     455        start = start->link;
     456        thresh = (start && start->ignore
     457                  ? ignorable_threshold
     458                  : non_ignorable_threshold);
     459        /* It is not supposed to matter which file we check in the end-test.
     460           If it would matter, crash.  */
     461        if (start && start->line0 - top0 != start->line1 - top1)
     462          abort ();
     463      } while (start
     464               /* Keep going if less than THRESH lines
     465                  elapse before the affected line.  */
     466               && start->line0 - top0 < thresh);
     467  
     468    return prev;
     469  }
     470  
     471  /* Set the 'ignore' flag properly in each change in SCRIPT.
     472     It should be 1 if all the lines inserted or deleted in that change
     473     are ignorable lines.  */
     474  
     475  static void
     476  mark_ignorable (struct change *script)
     477  {
     478    while (script)
     479      {
     480        struct change *next = script->link;
     481        lin first0, last0, first1, last1;
     482  
     483        /* Turn this change into a hunk: detach it from the others.  */
     484        script->link = nullptr;
     485  
     486        /* Determine whether this change is ignorable.  */
     487        script->ignore = ! analyze_hunk (script,
     488                                         &first0, &last0, &first1, &last1);
     489  
     490        /* Reconnect the chain as before.  */
     491        script->link = next;
     492  
     493        /* Advance to the following change.  */
     494        script = next;
     495      }
     496  }
     497  
     498  /* Find the last function-header line in LINBUF prior to line number LINENUM.
     499     This is a line containing a match for the regexp in 'function_regexp'.
     500     Return the address of the text, or null if no function-header is found.  */
     501  
     502  static char const *
     503  find_function (char const * const *linbuf, lin linenum)
     504  {
     505    lin i = linenum;
     506    lin last = find_function_last_search;
     507    find_function_last_search = i;
     508  
     509    while (last <= --i)
     510      {
     511        /* See if this line is what we want.  */
     512        char const *line = linbuf[i];
     513        size_t linelen = linbuf[i + 1] - line - 1;
     514  
     515        /* This line is for documentation; in practice it's equivalent
     516  	 to LEN = LINELEN and no machine code is generated.  */
     517        regoff_t len = MIN (linelen, TYPE_MAXIMUM (regoff_t));
     518  
     519        if (0 <= re_search (&function_regexp, line, len, 0, len, nullptr))
     520          {
     521            find_function_last_match = i;
     522            return line;
     523          }
     524      }
     525    /* If we search back to where we started searching the previous time,
     526       find the line we found last time.  */
     527    if (find_function_last_match != LIN_MAX)
     528      return linbuf[find_function_last_match];
     529  
     530    return nullptr;
     531  }