/* Context-format output routines for GNU DIFF.12Copyright (C) 1988, 1989, 1991, 1992, 1993, 1994, 1995, 1998, 2001,32002, 2004 Free Software Foundation, Inc.45This file is part of GNU DIFF.67GNU DIFF is free software; you can redistribute it and/or modify8it under the terms of the GNU General Public License as published by9the Free Software Foundation; either version 2, or (at your option)10any later version.1112GNU DIFF is distributed in the hope that it will be useful,13but WITHOUT ANY WARRANTY; without even the implied warranty of14MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the15GNU General Public License for more details.1617You should have received a copy of the GNU General Public License18along with this program; see the file COPYING.19If not, write to the Free Software Foundation,2059 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */2122#include "diff.h"23#include <inttostr.h>2425#ifdef ST_MTIM_NSEC26# define TIMESPEC_NS(timespec) ((timespec).ST_MTIM_NSEC)27#else28# define TIMESPEC_NS(timespec) 029#endif3031size_t nstrftime (char *, size_t, char const *, struct tm const *, int, long);3233static char const *find_function (char const * const *, lin);34static struct change *find_hunk (struct change *);35static void mark_ignorable (struct change *);36static void pr_context_hunk (struct change *);37static void pr_unidiff_hunk (struct change *);3839/* Last place find_function started searching from. */40static lin find_function_last_search;4142/* The value find_function returned when it started searching there. */43static lin find_function_last_match;4445/* Print a label for a context diff, with a file name and date or a label. */4647static void48print_context_label (char const *mark,49struct file_data *inf,50char const *label)51{52if (label)53fprintf (outfile, "%s %s\n", mark, label);54else55{56char buf[MAX (INT_STRLEN_BOUND (int) + 32,57INT_STRLEN_BOUND (time_t) + 11)];58struct tm const *tm = localtime (&inf->stat.st_mtime);59long nsec = TIMESPEC_NS (inf->stat.st_mtim);60if (! (tm && nstrftime (buf, sizeof buf, time_format, tm, 0, nsec)))61{62time_t sec = inf->stat.st_mtime;63verify (info_preserved, sizeof inf->stat.st_mtime <= sizeof sec);64sprintf (buf, "%jd.%.9ld", (intmax_t)sec, nsec);65}66fprintf (outfile, "%s %s\t%s\n", mark, inf->name, buf);67}68}6970/* Print a header for a context diff, with the file names and dates. */7172void73print_context_header (struct file_data inf[], bool unidiff)74{75if (unidiff)76{77print_context_label ("---", &inf[0], file_label[0]);78print_context_label ("+++", &inf[1], file_label[1]);79}80else81{82print_context_label ("***", &inf[0], file_label[0]);83print_context_label ("---", &inf[1], file_label[1]);84}85}8687/* Print an edit script in context format. */8889void90print_context_script (struct change *script, bool unidiff)91{92if (ignore_blank_lines || ignore_regexp.fastmap)93mark_ignorable (script);94else95{96struct change *e;97for (e = script; e; e = e->link)98e->ignore = false;99}100101find_function_last_search = - files[0].prefix_lines;102find_function_last_match = LIN_MAX;103104if (unidiff)105print_script (script, find_hunk, pr_unidiff_hunk);106else107print_script (script, find_hunk, pr_context_hunk);108}109110/* Print a pair of line numbers with a comma, translated for file FILE.111If the second number is not greater, use the first in place of it.112113Args A and B are internal line numbers.114We print the translated (real) line numbers. */115116static void117print_context_number_range (struct file_data const *file, lin a, lin b)118{119long int trans_a, trans_b;120translate_range (file, a, b, &trans_a, &trans_b);121122/* We can have B <= A in the case of a range of no lines.123In this case, we should print the line number before the range,124which is B.125126POSIX 1003.1-2001 requires two line numbers separated by a comma127even if the line numbers are the same. However, this does not128match existing practice and is surely an error in the129specification. */130131if (trans_b <= trans_a)132fprintf (outfile, "%ld", trans_b);133else134fprintf (outfile, "%ld,%ld", trans_a, trans_b);135}136137/* Print FUNCTION in a context header. */138static void139print_context_function (FILE *out, char const *function)140{141int i;142putc (' ', out);143for (i = 0; i < 40 && function[i] != '\n'; i++)144continue;145fwrite (function, sizeof (char), i, out);146}147148/* Print a portion of an edit script in context format.149HUNK is the beginning of the portion to be printed.150The end is marked by a `link' that has been nulled out.151152Prints out lines from both files, and precedes each153line with the appropriate flag-character. */154155static void156pr_context_hunk (struct change *hunk)157{158lin first0, last0, first1, last1, i;159char const *prefix;160char const *function;161FILE *out;162163/* Determine range of line numbers involved in each file. */164165enum changes changes = analyze_hunk (hunk, &first0, &last0, &first1, &last1);166if (! changes)167return;168169/* Include a context's width before and after. */170171i = - files[0].prefix_lines;172first0 = MAX (first0 - context, i);173first1 = MAX (first1 - context, i);174if (last0 < files[0].valid_lines - context)175last0 += context;176else177last0 = files[0].valid_lines - 1;178if (last1 < files[1].valid_lines - context)179last1 += context;180else181last1 = files[1].valid_lines - 1;182183/* If desired, find the preceding function definition line in file 0. */184function = 0;185if (function_regexp.fastmap)186function = find_function (files[0].linbuf, first0);187188begin_output ();189out = outfile;190191fprintf (out, "***************");192193if (function)194print_context_function (out, function);195196fprintf (out, "\n*** ");197print_context_number_range (&files[0], first0, last0);198fprintf (out, " ****\n");199200if (changes & OLD)201{202struct change *next = hunk;203204for (i = first0; i <= last0; i++)205{206/* Skip past changes that apply (in file 0)207only to lines before line I. */208209while (next && next->line0 + next->deleted <= i)210next = next->link;211212/* Compute the marking for line I. */213214prefix = " ";215if (next && next->line0 <= i)216/* The change NEXT covers this line.217If lines were inserted here in file 1, this is "changed".218Otherwise it is "deleted". */219prefix = (next->inserted > 0 ? "!" : "-");220221print_1_line (prefix, &files[0].linbuf[i]);222}223}224225fprintf (out, "--- ");226print_context_number_range (&files[1], first1, last1);227fprintf (out, " ----\n");228229if (changes & NEW)230{231struct change *next = hunk;232233for (i = first1; i <= last1; i++)234{235/* Skip past changes that apply (in file 1)236only to lines before line I. */237238while (next && next->line1 + next->inserted <= i)239next = next->link;240241/* Compute the marking for line I. */242243prefix = " ";244if (next && next->line1 <= i)245/* The change NEXT covers this line.246If lines were deleted here in file 0, this is "changed".247Otherwise it is "inserted". */248prefix = (next->deleted > 0 ? "!" : "+");249250print_1_line (prefix, &files[1].linbuf[i]);251}252}253}254255/* Print a pair of line numbers with a comma, translated for file FILE.256If the second number is smaller, use the first in place of it.257If the numbers are equal, print just one number.258259Args A and B are internal line numbers.260We print the translated (real) line numbers. */261262static void263print_unidiff_number_range (struct file_data const *file, lin a, lin b)264{265long int trans_a, trans_b;266translate_range (file, a, b, &trans_a, &trans_b);267268/* We can have B < A in the case of a range of no lines.269In this case, we print the line number before the range,270which is B. It would be more logical to print A, but271'patch' expects B in order to detect diffs against empty files. */272if (trans_b <= trans_a)273fprintf (outfile, trans_b < trans_a ? "%ld,0" : "%ld", trans_b);274else275fprintf (outfile, "%ld,%ld", trans_a, trans_b - trans_a + 1);276}277278/* Print a portion of an edit script in unidiff format.279HUNK is the beginning of the portion to be printed.280The end is marked by a `link' that has been nulled out.281282Prints out lines from both files, and precedes each283line with the appropriate flag-character. */284285static void286pr_unidiff_hunk (struct change *hunk)287{288lin first0, last0, first1, last1;289lin i, j, k;290struct change *next;291char const *function;292FILE *out;293294/* Determine range of line numbers involved in each file. */295296if (! analyze_hunk (hunk, &first0, &last0, &first1, &last1))297return;298299/* Include a context's width before and after. */300301i = - files[0].prefix_lines;302first0 = MAX (first0 - context, i);303first1 = MAX (first1 - context, i);304if (last0 < files[0].valid_lines - context)305last0 += context;306else307last0 = files[0].valid_lines - 1;308if (last1 < files[1].valid_lines - context)309last1 += context;310else311last1 = files[1].valid_lines - 1;312313/* If desired, find the preceding function definition line in file 0. */314function = 0;315if (function_regexp.fastmap)316function = find_function (files[0].linbuf, first0);317318begin_output ();319out = outfile;320321fprintf (out, "@@ -");322print_unidiff_number_range (&files[0], first0, last0);323fprintf (out, " +");324print_unidiff_number_range (&files[1], first1, last1);325fprintf (out, " @@");326327if (function)328print_context_function (out, function);329330putc ('\n', out);331332next = hunk;333i = first0;334j = first1;335336while (i <= last0 || j <= last1)337{338339/* If the line isn't a difference, output the context from file 0. */340341if (!next || i < next->line0)342{343putc (initial_tab ? '\t' : ' ', out);344print_1_line (0, &files[0].linbuf[i++]);345j++;346}347else348{349/* For each difference, first output the deleted part. */350351k = next->deleted;352while (k--)353{354putc ('-', out);355if (initial_tab)356putc ('\t', out);357print_1_line (0, &files[0].linbuf[i++]);358}359360/* Then output the inserted part. */361362k = next->inserted;363while (k--)364{365putc ('+', out);366if (initial_tab)367putc ('\t', out);368print_1_line (0, &files[1].linbuf[j++]);369}370371/* We're done with this hunk, so on to the next! */372373next = next->link;374}375}376}377378/* Scan a (forward-ordered) edit script for the first place that more than3792*CONTEXT unchanged lines appear, and return a pointer380to the `struct change' for the last change before those lines. */381382static struct change *383find_hunk (struct change *start)384{385struct change *prev;386lin top0, top1;387lin thresh;388389/* Threshold distance is 2 * CONTEXT + 1 between two non-ignorable390changes, but only CONTEXT if one is ignorable. Watch out for391integer overflow, though. */392lin non_ignorable_threshold =393(LIN_MAX - 1) / 2 < context ? LIN_MAX : 2 * context + 1;394lin ignorable_threshold = context;395396do397{398/* Compute number of first line in each file beyond this changed. */399top0 = start->line0 + start->deleted;400top1 = start->line1 + start->inserted;401prev = start;402start = start->link;403thresh = (prev->ignore || (start && start->ignore)404? ignorable_threshold405: non_ignorable_threshold);406/* It is not supposed to matter which file we check in the end-test.407If it would matter, crash. */408if (start && start->line0 - top0 != start->line1 - top1)409abort ();410} while (start411/* Keep going if less than THRESH lines412elapse before the affected line. */413&& start->line0 - top0 < thresh);414415return prev;416}417418/* Set the `ignore' flag properly in each change in SCRIPT.419It should be 1 if all the lines inserted or deleted in that change420are ignorable lines. */421422static void423mark_ignorable (struct change *script)424{425while (script)426{427struct change *next = script->link;428lin first0, last0, first1, last1;429430/* Turn this change into a hunk: detach it from the others. */431script->link = 0;432433/* Determine whether this change is ignorable. */434script->ignore = ! analyze_hunk (script,435&first0, &last0, &first1, &last1);436437/* Reconnect the chain as before. */438script->link = next;439440/* Advance to the following change. */441script = next;442}443}444445/* Find the last function-header line in LINBUF prior to line number LINENUM.446This is a line containing a match for the regexp in `function_regexp'.447Return the address of the text, or 0 if no function-header is found. */448449static char const *450find_function (char const * const *linbuf, lin linenum)451{452lin i = linenum;453lin last = find_function_last_search;454find_function_last_search = i;455456while (last <= --i)457{458/* See if this line is what we want. */459char const *line = linbuf[i];460size_t linelen = linbuf[i + 1] - line - 1;461462/* FIXME: re_search's size args should be size_t, not int. */463int len = MIN (linelen, INT_MAX);464465if (0 <= re_search (&function_regexp, line, len, 0, len, 0))466{467find_function_last_match = i;468return line;469}470}471/* If we search back to where we started searching the previous time,472find the line we found last time. */473if (find_function_last_match != LIN_MAX)474return linbuf[find_function_last_match];475476return 0;477}478479480