]>
git.cameronkatri.com Git - apple_cmds.git/blob - patch_cmds/diffstat/diffstat.c
1 /******************************************************************************
2 * Copyright 1994-2010,2012 by Thomas E. Dickey *
3 * All Rights Reserved. *
5 * Permission to use, copy, modify, and distribute this software and its *
6 * documentation for any purpose and without fee is hereby granted, provided *
7 * that the above copyright notice appear in all copies and that both that *
8 * copyright notice and this permission notice appear in supporting *
9 * documentation, and that the name of the above listed copyright holder(s) *
10 * not be used in advertising or publicity pertaining to distribution of the *
11 * software without specific, written prior permission. *
13 * THE ABOVE LISTED COPYRIGHT HOLDER(S) DISCLAIM ALL WARRANTIES WITH REGARD *
14 * TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND *
15 * FITNESS, IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE *
16 * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES *
17 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN *
18 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR *
19 * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. *
20 ******************************************************************************/
23 static const char *Id
= "$Id: diffstat.c,v 1.55 2012/01/03 09:44:24 tom Exp $";
29 * Created: 02 Feb 1992
31 * 03 Jan 2012, Correct case for "xz" suffix in is_compressed()
32 * (patch from Frederic Culot in FreeBSD ports). Add
33 * "-R" option. Improve dequoting of filenames in
35 * 10 Oct 2010, correct display of new files when -S/-D options
36 * are used. Remove the temporary directory on
37 * error, introduced in 1.48+ (patch by Solar
39 * 19 Jul 2010, add missing "break" statement which left "-c"
40 * option falling-through into "-C".
41 * 16 Jul 2010, configure "xz" path explicitly, in case lzcat
42 * does not support xz format. Add "-s" (summary)
43 * and "-C" (color) options.
44 * 15 Jul 2010, fix strict gcc warnings, e.g., using const.
45 * 10 Jan 2010, improve a case where filenames have embedded blanks
46 * (patch by Reinier Post).
47 * 07 Nov 2009, correct suffix-check for ".xz" files as
48 * command-line parameters rather than as piped
49 * input (report by Moritz Barsnick).
50 * 06 Oct 2009, fixes to build/run with MSYS or MinGW. use
51 * $TMPDIR for path of temporary file used in
52 * decompression. correct else-condition for
53 * detecting compression type (patch by Zach Hirsch).
54 * 31 Aug 2009, improve lzma support, add support for xz (patch by
55 * Eric Blake). Add special case for no-newline
56 * message from some diff's (Ubuntu #269895).
57 * Improve configure check for getopt().
58 * 11 Aug 2009, Add logic to check standard input, decompress if
59 * possible. Add -N option, to truncate long names.
60 * Add pack/pcat as a compression type.
61 * Add lzma/lzcat as a compression type.
62 * Allow overriding program paths with environment.
63 * 10 Aug 2009, modify to work with Perforce-style diffs (patch
65 * 29 Mar 2009, modify to work with patch ".rej" files, which have
66 * no filename header (use the name of the ".rej"
67 * file if it is available).
68 * 29 Sep 2008, fix typo in usage message.
69 * 06 Aug 2008, add "-m", "-S" and "-D" options.
70 * 05 Aug 2008, add "-q" option to suppress 0-files-changed
71 * message (patch by Greg Norris).
72 * 04 Sep 2007, add "-b" option to suppress binary-files (patch
74 * 26 Aug 2007, add "-d" option to show debugging traces, rather
75 * than by defining DEBUG. Add check after
76 * unified-diff chunk to avoid adding non-diff text
77 * (report by Adrian Bunk). Quote pathname passed
78 * in command to gzip/uncompress. Add a check for
79 * default-diff output without the "diff" command
80 * supplied to provide filename, mark as "unknown".
81 * 16 Jul 2006, fix to avoid modifying which is being used by
82 * tsearch() for ordering the binary tree (report by
84 * 02 Jul 2006, do not ignore pathnames in /tmp/, since some tools
85 * create usable pathnames for both old/new files
86 * there (Debian #376086). Correct ifdef for
87 * fgetc_unlocked(). Add configure check for
88 * compress, gzip and bzip2 programs that may be used
89 * to decompress files.
90 * 24 Aug 2005, update usage message for -l, -r changes.
91 * 15 Aug 2005, apply PLURAL() to num_files (Jean Delvare).
92 * add -l option (request by Michael Burian).
93 * Use fgetc_locked() if available.
94 * 14 Aug 2005, add -r2 option (rounding with adjustment to ensure
95 * that nonzero values always display a histogram
96 * bar), adapted from patch by Jean Delvare. Extend
97 * the -f option (2=filled, 4=verbose).
98 * 12 Aug 2005, modify to use tsearch() for sorted lists.
99 * 11 Aug 2005, minor fixes to scaling of modified lines. Add
101 * 05 Aug 2005, add -t (table) option.
102 * 10 Apr 2005, change order of merging and prefix-stripping so
103 * stripping all prefixes, e.g., with -p9, will be
104 * sorted as expected (Patch by Jean Delvare
105 * <khali@linux-fr.org>).
106 * 10 Jan 2005, add support for '--help' and '--version' (Patch
107 * by Eric Blake <ebb9@byu.net>.)
108 * 16 Dec 2004, fix a different case for data beginning with "--"
109 * which was treated as a header line.
110 * 14 Dec 2004, Fix allocation problems. Open files in binary
111 * mode for reading. Getopt returns -1, not
112 * necessarily EOF. Add const where useful. Use
113 * NO_IDENT where necessary. malloc() comes from
114 * <stdlib.h> in standard systems (Patch by Eric
115 * Blake <ebb9@byu.net>.)
116 * 08 Nov 2004, minor fix for resync of unified diffs checks for
117 * range (line beginning with '@' without header
118 * lines (successive lines beginning with "---" and
119 * "+++"). Fix a few problems reported by valgrind.
120 * 09 Nov 2003, modify check for lines beginning with '-' or '+'
121 * to treat only "---" in old-style diffs as a
123 * 14 Feb 2003, modify check for filenames to allow for some cases
124 * of incomplete dates (the reported example omitted
125 * the day of the month). Correct a typo in usage().
126 * Add -e, -h, -o options.
127 * 04 Jan 2003, improve tracking of chunks in unified diff, in
128 * case the original files contained a '+' or '-' in
129 * the first column (Debian #155000). Add -v option
130 * (Debian #170947). Modify to allocate buffers big
131 * enough for long input lines. Do additional
132 * merging to handle unusual Index/diff constructs in
133 * recent makepatch script.
134 * 20 Aug 2002, add -u option to tell diffstat to preserve the
135 * order of filenames as given rather than sort them
136 * (request by H Peter Anvin <hpa@zytor.com>). Add
137 * -k option for completeness.
138 * 09 Aug 2002, allow either '/' or '-' as delimiters in dates,
139 * to accommodate diffutils 2.8 (report by Rik van
140 * Riel <riel@conectiva.com.br>).
141 * 10 Oct 2001, add bzip2 (.bz2) suffix as suggested by
142 * Gregory T Norris <haphazard@socket.net> in Debian
143 * bug report #82969).
144 * add check for diff from RCS archive where the
145 * "diff" lines do not reference a filename.
146 * 29 Mar 2000, add -c option. Check for compressed input, read
147 * via pipe. Change to ANSI C. Adapted change from
148 * Troy Engel to add option that displays a number
149 * only, rather than a histogram.
150 * 17 May 1998, handle Debian diff files, which do not contain
151 * dates on the header lines.
152 * 16 Jan 1998, accommodate patches w/o tabs in header lines (e.g.,
153 * from cut/paste). Strip suffixes such as ".orig".
154 * 24 Mar 1996, corrected -p0 logic, more fixes in do_merging.
155 * 16 Mar 1996, corrected state-change for "Binary". Added -p
157 * 17 Dec 1995, corrected matching algorithm in 'do_merging()'
158 * 11 Dec 1995, mods to accommodate diffs against /dev/null or
159 * /tmp/XXX (tempfiles).
160 * 06 May 1995, limit scaling -- only shrink-to-fit.
161 * 29 Apr 1995, recognize 'rcsdiff -u' format.
162 * 26 Dec 1994, strip common pathname-prefix.
163 * 13 Nov 1994, added '-n' option. Corrected logic of 'match'.
164 * 17 Jun 1994, ifdef-<string.h>
165 * 12 Jun 1994, recognize unified diff, and output of makepatch.
166 * 04 Oct 1993, merge multiple diff-files, busy message when the
167 * output is piped to a file.
169 * Function: this program reads the output of 'diff' and displays a histogram
170 * of the insertions/deletions/modifications per-file.
173 #if defined(HAVE_CONFIG_H)
177 #if defined(WIN32) && !defined(HAVE_CONFIG_H)
178 #define HAVE_STDLIB_H
179 #define HAVE_STRING_H
180 #define HAVE_MALLOC_H
181 #define HAVE_GETOPT_H
192 #define strrchr rindex
198 extern int atoi(const char *);
204 extern int isatty(int);
211 #if defined(HAVE_SEARCH_H) && defined(HAVE_TSEARCH)
217 #ifdef HAVE_GETC_UNLOCKED
218 #define MY_GETC getc_unlocked
225 #elif !defined(HAVE_GETOPT_HEADER)
226 extern int getopt(int, char *const *, const char *);
231 #include <sys/types.h>
232 #include <sys/stat.h>
234 #if !defined(EXIT_SUCCESS)
235 #define EXIT_SUCCESS 0
236 #define EXIT_FAILURE 1
240 #define BZCAT_PATH ""
244 #define BZIP2_PATH ""
247 #ifndef COMPRESS_PATH
248 #define COMPRESS_PATH ""
256 #define LZCAT_PATH ""
263 #ifndef UNCOMPRESS_PATH
264 #define UNCOMPRESS_PATH ""
275 /******************************************************************************/
277 #if defined(__MINGW32__) || defined(WIN32)
278 #define MKDIR(name,mode) mkdir(name)
280 #define MKDIR(name,mode) mkdir(name,mode)
283 #if defined(WIN32) && !defined(__MINGW32__)
293 #define UC(c) ((unsigned char)(c))
300 #define TRACE(p) if (trace_opt) printf p
302 #define TRACE(p) /*nothing */
305 #define contain_any(s,reject) (strcspn(s,reject) != strlen(s))
307 #define HAVE_NOTHING 0
308 #define HAVE_GENERIC 1 /* e.g., "Index: foo" w/o pathname */
309 #define HAVE_PATH 2 /* reference-file from "diff dirname/foo" */
310 #define HAVE_PATH2 4 /* comparison-file from "diff dirname/foo" */
312 #define FMT_CONCISE 0
315 #define FMT_VERBOSE 4
317 typedef enum comment
{
321 #define MARKS 4 /* each of +, - and ! */
330 #define InsOf(p) (p)->count[cInsert] /* "+" count inserted lines */
331 #define DelOf(p) (p)->count[cDelete] /* "-" count deleted lines */
332 #define ModOf(p) (p)->count[cModify] /* "!" count modified lines */
333 #define EqlOf(p) (p)->count[cEquals] /* "=" count unmodified lines */
335 #define TotalOf(p) (InsOf(p) + DelOf(p) + ModOf(p) + EqlOf(p))
336 #define for_each_mark(n) for (n = 0; n < num_marks; ++n)
338 typedef struct _data
{
340 char *name
; /* the filename */
341 int copy
; /* true if filename is const-literal */
342 int base
; /* beginning of name if -p option used */
345 long chunks
; /* total number of chunks */
346 long chunk
[MARKS
]; /* counts for the current chunk */
347 long count
[MARKS
]; /* counts for the file */
361 static const char marks
[MARKS
+ 1] = "+-!=";
362 static const int colors
[MARKS
+ 1] =
365 static DATA
*all_data
;
366 static const char *comment_opt
= "";
367 static char *path_opt
= 0;
368 static int format_opt
= FMT_NORMAL
;
369 static int max_width
; /* the specified width-limit */
370 static int merge_names
= 1; /* true if we merge similar filenames */
371 static int merge_opt
= 0; /* true if we merge ins/del as modified */
372 static int min_name_wide
; /* minimum amount reserved for filenames */
373 static int max_name_wide
; /* maximum amount reserved for filenames */
374 static int names_only
; /* true if we list filenames only */
375 static int num_marks
= 3; /* 3 or 4, according to "-P" option */
376 static int reverse_opt
; /* true if results are reversed */
377 static int show_colors
; /* true if showing SGR colors */
378 static int show_progress
; /* if not writing to tty, show progress */
379 static int summary_only
= 0; /* true if only summary line is shown */
380 static int path_dest
; /* true if path_opt is destination (patched) */
381 static int plot_width
; /* the amount left over for histogram */
382 static int prefix_opt
= -1; /* if positive, controls stripping of PATHSEP */
383 static int round_opt
= 0; /* if nonzero, round data for histogram */
384 static int table_opt
= 0; /* if nonzero, write table rather than plot */
385 static int trace_opt
= 0; /* if nonzero, write debugging information */
386 static int sort_names
= 1; /* true if we sort filenames */
387 static int verbose
= 0; /* -v option */
388 static int quiet
= 0; /* -q option */
389 static int suppress_binary
= 0; /* -b option */
390 static long plot_scale
; /* the effective scale (1:maximum) */
393 static int use_tsearch
;
394 static void *sorted_data
;
397 static int prefix_len
= -1;
399 /******************************************************************************/
402 failed(const char *s
)
408 /* malloc wrapper that never returns NULL */
413 if ((p
= malloc(s
)) == NULL
)
419 is_dir(const char *name
)
422 return (stat(name
, &sb
) == 0 &&
423 (sb
.st_mode
& S_IFMT
) == S_IFDIR
);
430 (void) fputc(c
, stderr
);
431 (void) fflush(stderr
);
436 new_string(const char *s
)
438 return strcpy((char *) xmalloc((size_t) (strlen(s
) + 1)), s
);
442 compare_data(const void *a
, const void *b
)
444 const DATA
*p
= (const DATA
*) a
;
445 const DATA
*q
= (const DATA
*) b
;
446 return strcmp(p
->name
+ p
->base
, q
->name
+ q
->base
);
450 init_data(DATA
* data
, const char *name
, int copy
, int base
)
452 memset(data
, 0, sizeof(*data
));
453 data
->name
= (char *) name
;
460 new_data(const char *name
, int base
)
462 DATA
*r
= (DATA
*) xmalloc(sizeof(DATA
));
464 init_data(r
, new_string(name
), 0, base
);
471 add_tsearch_data(const char *name
, int base
)
477 init_data(&find
, name
, 1, base
);
478 if ((pp
= tfind(&find
, &sorted_data
, compare_data
)) != 0) {
479 result
= *(DATA
**) pp
;
482 result
= new_data(name
, base
);
483 (void) tsearch(result
, &sorted_data
, compare_data
);
484 result
->link
= all_data
;
492 find_data(const char *name
)
498 TRACE(("** find_data(%s)\n", name
));
500 /* Compute the base offset if the prefix option is used */
501 if (prefix_opt
>= 0) {
504 for (n
= prefix_opt
; n
> 0; n
--) {
505 char *s
= strchr(name
+ base
, PATHSEP
);
506 if (s
== 0 || *++s
== EOS
)
508 base
= (int) (s
- name
);
510 TRACE(("** base set to %d\n", base
));
513 /* Insert into sorted list (usually sorted). If we are not sorting or
514 * merging names, we fall off the end and link the new entry to the end of
515 * the list. If the prefix option is used, the prefix is ignored by the
516 * merge and sort operations.
518 * If we have tsearch(), we will maintain the sorted list using it and
523 r
= add_tsearch_data(name
, base
);
527 init_data(&find
, name
, 1, base
);
528 for (p
= all_data
, q
= 0; p
!= 0; q
= p
, p
= p
->link
) {
529 int cmp
= compare_data(p
, &find
);
530 if (merge_names
&& (cmp
== 0))
532 if (sort_names
&& (cmp
> 0))
535 r
= new_data(name
, base
);
548 * Remove a unneeded data item from the linked list. Free the name as well.
555 TRACE(("** delink '%s'\n", data
->name
));
559 if (tdelete(data
, &sorted_data
, compare_data
) == 0)
563 for (p
= all_data
, q
= 0; p
!= 0; q
= p
, p
= p
->link
) {
579 * Compare string 's' against a constant, returning either a pointer just
580 * past the matched part of 's' if it matches exactly, or null if a mismatch
584 match(char *s
, const char *p
)
595 if (*s
== EOS
&& *p
== EOS
) {
604 version_num(const char *s
)
606 int main_ver
, sub_ver
;
608 return (sscanf(s
, "%d.%d%c", &main_ver
, &sub_ver
, temp
) == 2);
612 * Check for a range of line-numbers, used in editing scripts.
615 edit_range(const char *s
)
619 return (sscanf(s
, "%d,%d%c", &first
, &last
, temp
) == 2)
620 || (sscanf(s
, "%d%c", &first
, temp
) == 1);
624 * Decode a range for default diff.
627 decode_default(char *s
,
628 long *first
, long *first_size
,
629 long *second
, long *second_size
)
634 if (isdigit(UC(*s
))) {
638 *first
= strtol(s
, &next
, 10);
639 if (next
!= 0 && next
!= s
) {
642 *first_size
= strtol(s
, &next
, 10) + 1 - *first
;
645 if (next
!= 0 && next
!= s
) {
651 *second
= strtol(s
, &next
, 10);
652 if (next
!= 0 && next
!= s
) {
655 *second_size
= strtol(s
, &next
, 10) + 1 - *second
;
658 if (next
!= 0 && next
!= s
&& *next
== EOS
)
668 * Decode a range for unified diff. Oddly, the comments in diffutils code
669 * claim that both numbers are line-numbers. However, inspection of the output
670 * shows that the numbers are a line-number followed by a count.
673 decode_range(const char *s
, int *first
, int *second
)
678 if (isdigit(UC(*s
))) {
679 if (sscanf(s
, "%d,%d%c", first
, second
, &check
) == 2) {
680 TRACE(("** decode_range #1 first=%d, second=%d\n", *first
, *second
));
682 } else if (sscanf(s
, "%d%c", first
, &check
) == 1) {
683 *second
= *first
; /* diffutils 2.7 does this */
684 TRACE(("** decode_range #2 first=%d, second=%d\n", *first
, *second
));
692 HadDiffs(const DATA
* data
)
694 return InsOf(data
) != 0
697 || data
->cmt
!= Normal
;
701 * If the given path is not one of the "ignore" paths, then return true.
704 can_be_merged(const char *path
)
708 && strcmp(path
, "/dev/null"))
714 is_leaf(const char *theLeaf
, const char *path
)
718 if (strchr(theLeaf
, PATHSEP
) == 0
719 && (s
= strrchr(path
, PATHSEP
)) != 0
720 && !strcmp(++s
, theLeaf
))
726 trim_datapath(DATA
** datap
, size_t length
, int *localp
)
728 char *target
= (*datap
)->name
;
732 * If we are using tsearch(), make a local copy of the data
733 * so we can trim it without interfering with tsearch's
734 * notion of the ordering of data. That will create some
735 * spurious empty data, so we add the changed() macro in a
736 * few places to skip over those.
739 char *trim
= new_string(target
);
741 *datap
= add_tsearch_data(trim
, (*datap
)->base
);
742 target
= (*datap
)->name
;
747 target
[length
] = EOS
;
753 * The 'data' parameter points to the first of two markers, while
754 * 'path' is the pathname from the second marker.
756 * On the first call for
757 * a given file, the 'data' parameter stores no differences.
760 do_merging(DATA
* data
, char *path
, int *freed
)
762 char *target
= reverse_opt
? path
: data
->name
;
763 char *source
= reverse_opt
? data
->name
: path
;
764 char *result
= source
;
766 TRACE(("** do_merging(\"%s\",\"%s\") diffs:%d\n",
767 data
->name
, path
, HadDiffs(data
)));
770 if (!HadDiffs(data
)) {
772 if (is_leaf(target
, source
)) {
773 TRACE(("** is_leaf: \"%s\" vs \"%s\"\n", target
, source
));
775 TRACE((".. no action @%d\n", __LINE__
));
777 *freed
= delink(data
);
779 } else if (can_be_merged(target
)
780 && can_be_merged(source
)) {
781 size_t len1
= strlen(target
);
782 size_t len2
= strlen(source
);
789 * If the source/target differ only by some suffix, e.g., ".orig"
790 * or ".bak", strip that off. The target may may also be a
791 * temporary filename (which would not be merged since it has no
792 * apparent relationship to the current).
795 if (!strncmp(target
, source
, len2
)) {
796 TRACE(("** trimming data \"%s\" to \"%.*s\"\n",
797 target
, (int) len2
, target
));
799 TRACE((".. no action @%d\n", __LINE__
));
801 target
= trim_datapath(&data
, len1
= len2
, &local
);
804 } else if (len1
< len2
) {
805 if (!strncmp(target
, source
, len1
)) {
806 TRACE(("** trimming source \"%s\" to \"%.*s\"\n",
807 source
, (int) len1
, source
));
809 TRACE((".. no action @%d\n", __LINE__
));
811 source
[len2
= len1
] = EOS
;
817 * If there was no "-p" option, look for the best match by
818 * stripping prefixes from both source/target strings.
820 if (prefix_opt
< 0) {
822 * Now (whether or not we trimmed a suffix), scan back from the
823 * end of source/target strings to find if they happen to share
824 * a common ending, e.g., a/b/c versus d/b/c. If the strings
825 * are not identical, then 'diff' will be set, but if they have
826 * a common ending then 'matched' will be set.
828 for (n
= 1; n
<= len1
&& n
<= len2
; n
++) {
829 if (target
[len1
- n
] != source
[len2
- n
]) {
833 if (source
[len2
- n
] == PATHSEP
) {
838 TRACE(("** merge @%d, prefix_opt=%d matched=%d diff=%d\n",
839 __LINE__
, prefix_opt
, matched
, diff
));
840 if (matched
!= 0 && diff
) {
842 TRACE((".. no action @%d\n", __LINE__
));
844 result
= source
+ ((int) len2
- matched
+ 1);
851 TRACE((".. no action @%d\n", __LINE__
));
853 *freed
= delink(data
);
856 } else if (reverse_opt
) {
857 TRACE((".. no action @%d\n", __LINE__
));
858 if (can_be_merged(source
)) {
859 TRACE(("** merge @%d\n", __LINE__
));
861 TRACE(("** do not merge, retain @%d\n", __LINE__
));
862 /* must not merge, retain existing name */
866 if (can_be_merged(source
)) {
867 TRACE(("** merge @%d\n", __LINE__
));
868 *freed
= delink(data
);
870 TRACE(("** do not merge, retain @%d\n", __LINE__
));
871 /* must not merge, retain existing name */
875 } else if (reverse_opt
) {
876 TRACE((".. no action @%d\n", __LINE__
));
877 if (can_be_merged(source
)) {
878 TRACE(("** merge @%d\n", __LINE__
));
881 TRACE(("** do not merge, retain @%d\n", __LINE__
));
884 if (can_be_merged(source
)) {
885 TRACE(("** merge @%d\n", __LINE__
));
887 TRACE(("** do not merge, retain @%d\n", __LINE__
));
891 TRACE(("** finish do_merging ->\"%s\"\n", result
));
896 begin_data(const DATA
* p
)
898 if (!can_be_merged(p
->name
)
899 && strchr(p
->name
, PATHSEP
) != 0) {
900 TRACE(("** begin_data:HAVE_PATH\n"));
903 TRACE(("** begin_data:HAVE_GENERIC\n"));
910 while (isspace(UC(*s
)))
916 * Skip a filename, which may be in quotes, to allow embedded blanks in the
920 skip_filename(char *s
)
922 if (*s
== SQUOTE
&& s
[1] != EOS
&& strchr(s
+ 1, SQUOTE
)) {
924 while (*s
!= EOS
&& (*s
!= SQUOTE
) && isgraph(UC(*s
))) {
929 while (*s
!= EOS
&& isgraph(UC(*s
))) {
937 skip_options(char *params
)
939 while (*params
!= EOS
) {
940 params
= skip_blanks(params
);
941 if (*params
== '-') {
942 while (isgraph(UC(*params
)))
948 return skip_blanks(params
);
952 * Strip single-quotes from a name (needed for recent makepatch versions).
957 size_t len
= strlen(s
);
960 if (*s
== SQUOTE
&& len
> 2 && s
[len
- 1] == SQUOTE
) {
961 for (n
= 0; (s
[n
] = s
[n
+ 1]) != EOS
; ++n
) {
969 * Allocate a fixed-buffer
972 fixed_buffer(char **buffer
, size_t want
)
974 *buffer
= (char *) xmalloc(want
);
978 * Reallocate a fixed-buffer
981 adjust_buffer(char **buffer
, size_t want
)
983 if ((*buffer
= (char *) realloc(*buffer
, want
)) == 0)
988 * Read until newline or end-of-file, allocating the line-buffer so it is long
989 * enough for the input.
992 get_line(char **buffer
, size_t *have
, FILE *fp
)
997 while ((ch
= MY_GETC(fp
)) != EOF
) {
998 if (used
+ 2 > *have
) {
999 adjust_buffer(buffer
, *have
*= 2);
1001 (*buffer
)[used
++] = (char) ch
;
1005 (*buffer
)[used
] = EOS
;
1010 data_filename(const DATA
* p
)
1012 return (p
->name
+ (prefix_opt
>= 0 ? p
->base
: prefix_len
));
1016 * Count the (new)lines in a file, return -1 if the file is not found.
1019 count_lines(DATA
* p
)
1023 char *filetail
= data_filename(p
);
1024 size_t want
= strlen(path_opt
) + 2 + strlen(filetail
);
1028 if ((filename
= malloc(want
)) != 0) {
1032 size_t path_len
= strlen(path_opt
);
1034 char *tail_sep
= strchr(filetail
, PATHSEP
);
1036 if (tail_sep
!= 0) {
1037 tail_len
= (size_t) (tail_sep
- filetail
);
1038 if (tail_len
!= 0 && tail_len
<= path_len
) {
1039 if (tail_len
< path_len
1040 && path_opt
[path_len
- tail_len
- 1] != PATHSEP
) {
1042 } else if (!strncmp(path_opt
+ path_len
- tail_len
,
1046 if (path_len
> tail_len
) {
1047 sprintf(filename
, "%.*s%c%s",
1048 (int) (path_len
- tail_len
),
1053 strcpy(filename
, filetail
);
1060 sprintf(filename
, "%s%c%s", path_opt
, PATHSEP
, filetail
);
1063 TRACE(("count_lines %s\n", filename
));
1064 if ((fp
= fopen(filename
, "r")) != 0) {
1066 while ((ch
= MY_GETC(fp
)) != EOF
) {
1072 fprintf(stderr
, "Cannot open %s\n", filename
);
1076 failed("count_lines");
1082 update_chunk(DATA
* p
, Change change
)
1086 p
->chunk
[change
] += 1;
1088 p
->count
[change
] += 1;
1093 finish_chunk(DATA
* p
)
1102 * This is crude, but to make it really precise we would have
1103 * to keep an array of line-numbers to which which in a chunk
1104 * are marked as insert/delete.
1106 if (p
->chunk
[cInsert
] && p
->chunk
[cDelete
]) {
1108 if (p
->chunk
[cInsert
] > p
->chunk
[cDelete
]) {
1109 change
= p
->chunk
[cDelete
];
1111 change
= p
->chunk
[cInsert
];
1113 p
->chunk
[cInsert
] -= change
;
1114 p
->chunk
[cDelete
] -= change
;
1115 p
->chunk
[cModify
] += change
;
1119 p
->count
[i
] += p
->chunk
[i
];
1125 #define date_delims(a,b) (((a)=='/' && (b)=='/') || ((a) == '-' && (b) == '-'))
1126 #define CASE_TRACE() TRACE(("** handle case for '%c' %d:%s\n", *buffer, ok, that ? that->name : ""))
1129 do_file(FILE *fp
, const char *default_name
)
1131 static const char *only_stars
= "***************";
1134 DATA
*that
= &dummy
;
1143 int ok
= HAVE_NOTHING
;
1150 int expect_unify
= 0;
1162 init_data(&dummy
, "", 1, 0);
1164 fixed_buffer(&buffer
, fixed
= length
= BUFSIZ
);
1165 fixed_buffer(&b_fname
, length
);
1166 fixed_buffer(&b_temp1
, length
);
1167 fixed_buffer(&b_temp2
, length
);
1168 fixed_buffer(&b_temp3
, length
);
1170 while (get_line(&buffer
, &length
, fp
)) {
1172 * Adjust size of fixed-buffers so that a sscanf cannot overflow.
1174 if (length
> fixed
) {
1176 adjust_buffer(&b_fname
, length
);
1177 adjust_buffer(&b_temp1
, length
);
1178 adjust_buffer(&b_temp2
, length
);
1179 adjust_buffer(&b_temp3
, length
);
1183 * Trim trailing newline.
1185 for (s
= buffer
+ strlen(buffer
); s
> buffer
; s
--) {
1186 if ((UC(s
[-1]) == '\n') || (UC(s
[-1]) == '\r'))
1192 TRACE(("[%05d] %s\n", line_no
, buffer
));
1195 * "patch -U" can create ".rej" files lacking a filename header,
1196 * in unified format. Check for those.
1198 if (line_no
== 1 && !strncmp(buffer
, "@@", (size_t) 2)) {
1200 that
= find_data(default_name
);
1201 ok
= begin_data(that
);
1205 * The lines identifying files in a context diff depend on how it was
1206 * invoked. But after the header, each chunk begins with a line
1207 * containing 15 *'s. Each chunk may contain a line-range with '***'
1208 * for the "before", and a line-range with '---' for the "after". The
1209 * part of the chunk depicting the deletion may be absent, though the
1210 * edit line is present.
1212 * The markers for unified diff are a little different from the normal
1213 * context-diff. Also, the edit-lines in a unified diff won't have a
1214 * space in column 2. Because of the missing space, we have to count
1215 * lines to ensure we do not confuse the marker lines.
1218 if (that
!= &dummy
&& !strcmp(buffer
, only_stars
)) {
1220 TRACE(("** begin context chunk\n"));
1222 } else if (line_no
== 1 && !strcmp(buffer
, only_stars
)) {
1223 TRACE(("** begin context chunk\n"));
1225 that
= find_data(default_name
);
1226 ok
= begin_data(that
);
1227 } else if (context
== 2 && match(buffer
, "*** ")) {
1229 } else if (context
== 1 && match(buffer
, "--- ")) {
1232 } else if (match(buffer
, "*** ")) {
1233 } else if ((old_unify
+ new_unify
) == 0 && match(buffer
, "==== ")) {
1236 } else if ((old_unify
+ new_unify
) == 0 && match(buffer
, "--- ")) {
1238 marker
= unified
= 1;
1239 } else if ((old_unify
+ new_unify
) == 0 && match(buffer
, "+++ ")) {
1240 marker
= unified
= 2;
1241 } else if (unified
== 2
1242 || ((old_unify
+ new_unify
) == 0 && (*buffer
== '@'))) {
1245 if (*buffer
== '@') {
1246 int old_base
, new_base
, old_size
, new_size
;
1249 old_unify
= new_unify
= 0;
1250 if (sscanf(buffer
, "@@ -%[0-9,] +%[0-9,] @%c",
1255 && decode_range(b_temp1
, &old_base
, &old_size
)
1256 && decode_range(b_temp2
, &new_base
, &new_size
)) {
1257 old_unify
= old_size
;
1258 new_unify
= new_size
;
1262 } else if (unified
== 1 && !context
) {
1264 * If unified==1, we guessed we would find a "+++" line, but since
1265 * we are here, we did not find that. The context check ensures
1266 * we do not mistake the "---" for a unified diff with that for
1267 * a context diff's "after" line-range.
1269 * If we guessed wrong, then we probably found a data line with
1270 * "--" in the first two columns of the diff'd file.
1273 TRACE(("?? Expected \"+++\" for unified diff\n"));
1278 && strcmp(prev
->name
, that
->name
)) {
1279 TRACE(("?? giveup on %ld/%ld %s\n", InsOf(that
),
1280 DelOf(that
), that
->name
));
1281 TRACE(("?? revert to %ld/%ld %s\n", InsOf(prev
),
1282 DelOf(prev
), prev
->name
));
1283 (void) delink(that
);
1285 update_chunk(that
, cDelete
);
1287 } else if (old_unify
+ new_unify
) {
1305 if (strstr(buffer
, "newline") != 0) {
1310 TRACE(("?? expected more in chunk\n"));
1311 old_unify
= new_unify
= 0;
1314 if (!(old_unify
+ new_unify
)) {
1318 long old_base
, new_base
;
1323 && decode_default(buffer
,
1324 &old_base
, &old_dft
,
1325 &new_base
, &new_dft
)) {
1326 TRACE(("DFT %ld,%ld -> %ld,%ld\n",
1327 old_base
, old_base
+ old_dft
- 1,
1328 new_base
, new_base
+ new_dft
- 1));
1330 that
= find_data("unknown");
1331 ok
= begin_data(that
);
1336 * If the previous line ended a chunk of a unified diff, we may begin
1337 * another chunk, or begin another type of diff. If neither, do not
1338 * continue to accumulate counts for the unified diff which has ended.
1340 if (expect_unify
!= 0) {
1341 if (expect_unify
-- == 1) {
1343 TRACE(("?? did not get chunk\n"));
1351 * Override the beginning of the line to simplify the case statement
1355 TRACE(("** have marker=%d, override %s\n", marker
, buffer
));
1356 (void) strncpy(buffer
, "***", (size_t) 3);
1360 * Use the first character of the input line to determine its
1364 case 'O': /* Only */
1366 if (match(buffer
, "Only in ")) {
1367 char *path
= buffer
+ 8;
1369 for (s
= path
; *s
!= EOS
; s
++) {
1370 if (match(s
, ": ")) {
1373 while ((s
[0] = s
[1]) != EOS
)
1381 that
= find_data(path
);
1389 * Several different scripts produce "Index:" lines
1390 * (e.g., "makepatch"). Not all bother to put the
1391 * pathname of the files; some put only the leaf names.
1395 if ((s
= match(buffer
, "Index: ")) != 0) {
1400 s
= do_merging(that
, s
, &freed
);
1401 that
= find_data(s
);
1402 ok
= begin_data(that
);
1406 case 'd': /* diff command trace */
1408 if ((s
= match(buffer
, "diff ")) != 0
1409 && *(s
= skip_options(s
)) != EOS
) {
1411 *skip_filename(s
) = EOS
;
1413 s
= skip_filename(s
);
1419 s
= do_merging(that
, s
, &freed
);
1420 that
= find_data(s
);
1421 ok
= begin_data(that
);
1427 if (!(ok
& HAVE_PATH
)) {
1428 int ddd
, hour
, minute
, second
;
1429 int day
, month
, year
;
1432 /* check for tab-delimited first, so we can
1433 * accept filenames containing spaces.
1436 "*** %[^\t]\t%[^ ] %[^ ] %d %d:%d:%d %d",
1438 b_temp2
, b_temp3
, &ddd
,
1439 &hour
, &minute
, &second
, &year
) == 8
1441 "*** %[^\t]\t%d%c%d%c%d %d:%d:%d",
1443 &year
, &yrmon
, &month
, &monday
, &day
,
1444 &hour
, &minute
, &second
) == 9
1445 && date_delims(yrmon
, monday
)
1446 && !version_num(b_fname
))
1448 "*** %[^\t ]%[\t ]%[^ ] %[^ ] %d %d:%d:%d %d",
1451 b_temp2
, b_temp3
, &ddd
,
1452 &hour
, &minute
, &second
, &year
) == 9
1454 "*** %[^\t ]%[\t ]%d%c%d%c%d %d:%d:%d",
1457 &year
, &yrmon
, &month
, &monday
, &day
,
1458 &hour
, &minute
, &second
) == 10
1459 && date_delims(yrmon
, monday
)
1460 && !version_num(b_fname
))
1462 "*** %[^\t ]%[\t ]",
1465 && !version_num(b_fname
)
1466 && !contain_any(b_fname
, "*")
1467 && !edit_range(b_fname
))
1471 s
= do_merging(that
, b_fname
, &freed
);
1474 that
= find_data(s
);
1475 ok
= begin_data(that
);
1476 TRACE(("** after merge:%d:%s\n", ok
, s
));
1483 if (!(ok
& HAVE_PATH
)) {
1486 if (((sscanf(buffer
,
1487 "==== %[^\t #]#%d - %[^\t ]",
1492 "==== %[^\t #]#%d (%[^)]) - %[^\t ]",
1497 && !version_num(b_fname
)
1498 && !contain_any(b_fname
, "*")
1499 && !edit_range(b_fname
)) {
1500 TRACE(("** found p4-diff\n"));
1503 s
= do_merging(that
, b_fname
, &freed
);
1506 that
= find_data(s
);
1507 ok
= begin_data(that
);
1508 TRACE(("** after merge:%d:%s\n", ok
, s
));
1518 update_chunk(that
, cInsert
);
1527 if (!unified
&& !strcmp(buffer
, "---")) {
1535 update_chunk(that
, cDelete
);
1542 update_chunk(that
, cModify
);
1546 /* Expecting "Binary files XXX and YYY differ" */
1547 case 'B': /* Binary */
1549 case 'b': /* binary */
1551 if ((s
= match(buffer
+ 1, "inary files ")) != 0) {
1552 char *first
= skip_blanks(s
);
1553 /* blindly assume the first filename does not contain " and " */
1554 char *at_and
= strstr(s
, " and ");
1555 s
= strrchr(buffer
, BLANK
);
1556 if ((at_and
!= NULL
) && !strcmp(s
, " differ")) {
1557 char *second
= skip_blanks(at_and
+ 5);
1568 that
= find_data(s
);
1579 finish_chunk(&dummy
);
1590 show_color(int color
)
1593 printf("\033[%dm", color
+ 30);
1595 printf("\033[0;39m");
1599 plot_bar(long count
, int c
, int color
)
1601 long result
= count
;
1603 if (show_colors
&& result
!= 0)
1606 while (--count
>= 0)
1609 if (show_colors
&& result
!= 0)
1616 * Each call to 'plot_num()' prints a scaled bar of 'c' characters. The
1617 * 'extra' parameter is used to keep the accumulated error in the bar's total
1618 * length from getting large.
1621 plot_num(long num_value
, int c
, int color
, long *extra
)
1626 /* the value to plot */
1627 /* character to display in the bar */
1628 /* accumulated error in the bar */
1630 product
= (plot_width
* num_value
);
1631 result
= ((product
+ *extra
) / plot_scale
);
1632 *extra
= product
- (result
* plot_scale
) - *extra
;
1633 plot_bar(result
, c
, color
);
1639 plot_round1(const long num
[MARKS
])
1646 long half
= (plot_scale
/ 2);
1650 long product
= (plot_width
* num
[i
]);
1651 scaled
[i
] = (product
/ plot_scale
);
1652 remain
[i
] = (product
% plot_scale
);
1654 have
+= product
- remain
[i
];
1656 while (want
> have
) {
1660 && (remain
[i
] > (j
>= 0 ? remain
[j
] : half
))) {
1673 plot_bar(scaled
[i
], marks
[i
], colors
[i
]);
1674 result
+= scaled
[i
];
1680 * Print a scaled bar of characters, where c[0] is for insertions, c[1]
1681 * for deletions and c[2] for modifications. The num array contains the
1682 * count for each type of change, in the same order.
1685 plot_round2(const long num
[MARKS
])
1693 for (i
= 0; i
< MARKS
; i
++)
1699 total
= (total
* plot_width
+ (plot_scale
/ 2)) / plot_scale
;
1700 /* display at least one character */
1705 scaled
[i
] = num
[i
] * plot_width
/ plot_scale
;
1706 remain
[i
] = num
[i
] * plot_width
- scaled
[i
] * plot_scale
;
1710 /* assign the missing chars using the largest remainder algo */
1712 int largest
, largest_count
; /* largest is a bit field */
1715 /* search for the largest remainder */
1716 largest
= largest_count
= 0;
1719 if (remain
[i
] > max_remain
) {
1722 max_remain
= remain
[i
];
1723 } else if (remain
[i
] == max_remain
) { /* ex aequo */
1729 /* if there are more greatest remainders than characters
1730 missing, don't assign them at all */
1731 if (total
< largest_count
)
1734 /* allocate the extra characters */
1736 if (largest
& (1 << i
)) {
1739 remain
[i
] -= plot_width
;
1745 result
+= plot_bar(scaled
[i
], marks
[i
], colors
[i
]);
1752 plot_numbers(const DATA
* p
)
1758 printf("%5ld ", TotalOf(p
));
1760 if (format_opt
& FMT_VERBOSE
) {
1761 printf("%5ld ", InsOf(p
));
1762 printf("%5ld ", DelOf(p
));
1763 printf("%5ld ", ModOf(p
));
1765 printf("%5ld ", EqlOf(p
));
1768 if (format_opt
== FMT_CONCISE
) {
1770 printf("\t%ld %c", p
->count
[i
], marks
[i
]);
1773 switch (round_opt
) {
1776 used
+= plot_num(p
->count
[i
], marks
[i
], colors
[i
], &temp
);
1780 used
= plot_round1(p
->count
);
1784 used
= plot_round2(p
->count
);
1788 if ((format_opt
& FMT_FILLED
) != 0) {
1789 if (used
> plot_width
)
1790 printf("%ld", used
- plot_width
); /* oops */
1792 plot_bar(plot_width
- used
, '.', 0);
1797 #define changed(p) (!merge_names \
1798 || (p)->cmt != Normal \
1799 || (TotalOf(p)) != 0)
1802 show_data(const DATA
* p
)
1804 char *name
= data_filename(p
);
1809 } else if (!changed(p
)) {
1811 } else if (p
->cmt
== Binary
&& suppress_binary
== 1) {
1813 } else if (table_opt
) {
1815 printf("%s\n", name
);
1817 printf("%ld,%ld,%ld,",
1822 printf("%ld,", EqlOf(p
));
1823 printf("%s\n", name
);
1825 } else if (names_only
) {
1826 printf("%s\n", name
);
1828 printf("%s ", comment_opt
);
1829 if (max_name_wide
> 0
1830 && max_name_wide
< min_name_wide
1831 && max_name_wide
< ((width
= (int) strlen(name
)))) {
1832 printf("%.*s", max_name_wide
, name
+ (width
- max_name_wide
));
1834 width
= ((max_name_wide
> 0 && max_name_wide
< min_name_wide
)
1837 printf("%-*.*s", width
, width
, name
);
1858 show_tsearch(const void *nodep
, const VISIT which
, const int depth
)
1860 const DATA
*p
= *(DATA
* const *) nodep
;
1862 if (which
== postorder
|| which
== leaf
)
1868 ignore_data(DATA
* p
)
1870 return ((!changed(p
))
1871 || (p
->cmt
== Binary
&& suppress_binary
));
1883 int num_files
= 0, shortest_name
= -1, longest_name
= -1;
1886 for (p
= all_data
; p
; p
= p
->link
) {
1887 int len
= (int) strlen(p
->name
);
1893 * If "-pX" option is given, prefix_opt is positive.
1895 * "-p0" gives the whole pathname unmodified. "-p1" strips
1896 * through the first path-separator, etc.
1898 if (prefix_opt
>= 0) {
1899 /* p->base has been computed at node creation */
1900 if (min_name_wide
< (len
- p
->base
))
1901 min_name_wide
= (len
- p
->base
);
1904 * If "-pX" option is not given, strip off any prefix which is
1905 * shared by all of the names.
1907 if (len
< prefix_len
|| prefix_len
< 0)
1909 while (prefix_len
> 0) {
1910 if (p
->name
[prefix_len
- 1] != PATHSEP
)
1912 else if (strncmp(all_data
->name
, p
->name
, (size_t) prefix_len
))
1918 if (len
> longest_name
)
1920 if (len
< shortest_name
|| shortest_name
< 0)
1921 shortest_name
= len
;
1926 * Use a separate loop after computing prefix_len so we can apply the "-S"
1927 * or "-D" options to find files that we can use as reference for the
1930 for (p
= all_data
; p
; p
= p
->link
) {
1931 if (!ignore_data(p
)) {
1934 int save_ins
= InsOf(p
);
1935 int save_del
= DelOf(p
);
1936 InsOf(p
) = save_del
;
1937 DelOf(p
) = save_ins
;
1939 if (path_opt
!= 0) {
1940 int count
= count_lines(p
);
1943 EqlOf(p
) = count
- ModOf(p
);
1944 if (path_dest
!= 0) {
1945 EqlOf(p
) -= InsOf(p
);
1947 EqlOf(p
) -= DelOf(p
);
1954 total_ins
+= InsOf(p
);
1955 total_del
+= DelOf(p
);
1956 total_mod
+= ModOf(p
);
1957 total_eql
+= EqlOf(p
);
1959 if (temp
> plot_scale
)
1964 if (prefix_opt
< 0) {
1967 if ((longest_name
- prefix_len
) > min_name_wide
)
1968 min_name_wide
= (longest_name
- prefix_len
);
1971 min_name_wide
++; /* make sure it's nonzero */
1972 plot_width
= (max_width
- min_name_wide
- 8);
1973 if (plot_width
< 10)
1976 if (plot_scale
< plot_width
)
1977 plot_scale
= plot_width
; /* 1:1 */
1981 printf("INSERTED,DELETED,MODIFIED,");
1983 printf("UNCHANGED,");
1985 printf("FILENAME\n");
1989 twalk(sorted_data
, show_tsearch
);
1992 for (p
= all_data
; p
; p
= p
->link
) {
1996 if (!table_opt
&& !names_only
) {
1997 #define PLURAL(n) n, n != 1 ? "s" : ""
1998 if (num_files
> 0 || !quiet
) {
1999 printf("%s %d file%s changed", comment_opt
, PLURAL(num_files
));
2001 printf(", %ld insertion%s(+)", PLURAL(total_ins
));
2003 printf(", %ld deletion%s(-)", PLURAL(total_del
));
2005 printf(", %ld modification%s(!)", PLURAL(total_mod
));
2006 if (total_eql
&& path_opt
!= 0)
2007 printf(", %ld unchanged line%s(=)", PLURAL(total_eql
));
2008 (void) putchar('\n');
2015 get_program(const char *name
, const char *dft
)
2017 const char *result
= getenv(name
);
2018 if (result
== 0 || *result
== EOS
)
2020 TRACE(("get_program(%s) = %s\n", name
, result
));
2023 #define GET_PROGRAM(name) get_program("DIFFSTAT_" #name, name)
2026 decompressor(Decompress which
, const char *name
)
2028 const char *verb
= 0;
2029 const char *opts
= "";
2031 size_t len
= strlen(name
);
2035 verb
= GET_PROGRAM(BZCAT_PATH
);
2036 if (*verb
== '\0') {
2037 verb
= GET_PROGRAM(BZIP2_PATH
);
2042 verb
= GET_PROGRAM(ZCAT_PATH
);
2043 if (*verb
== '\0') {
2044 verb
= GET_PROGRAM(UNCOMPRESS_PATH
);
2046 if (*verb
== '\0') {
2047 /* not all compress's recognize the options, test this last */
2048 verb
= GET_PROGRAM(COMPRESS_PATH
);
2054 verb
= GET_PROGRAM(GZIP_PATH
);
2058 verb
= GET_PROGRAM(LZCAT_PATH
);
2062 verb
= GET_PROGRAM(PCAT_PATH
);
2065 verb
= GET_PROGRAM(XZ_PATH
);
2073 if (verb
!= 0 && *verb
!= '\0') {
2074 result
= (char *) xmalloc(strlen(verb
) + 10 + len
);
2075 sprintf(result
, "%s %s", verb
, opts
);
2076 if (*name
!= '\0') {
2077 sprintf(result
+ strlen(result
), " \"%s\"", name
);
2084 is_compressed(const char *name
)
2086 size_t len
= strlen(name
);
2089 if (len
> 2 && !strcmp(name
+ len
- 2, ".Z")) {
2091 } else if (len
> 2 && !strcmp(name
+ len
- 2, ".z")) {
2093 } else if (len
> 3 && !strcmp(name
+ len
- 3, ".gz")) {
2095 } else if (len
> 4 && !strcmp(name
+ len
- 4, ".bz2")) {
2097 } else if (len
> 5 && !strcmp(name
+ len
- 5, ".lzma")) {
2099 } else if (len
> 3 && !strcmp(name
+ len
- 3, ".xz")) {
2104 return decompressor(which
, name
);
2108 #define MY_MKDTEMP(path) mkdtemp(path)
2111 * mktemp is supposedly marked obsolete at the same point that mkdtemp is
2115 my_mkdtemp(char *path
)
2117 char *result
= mktemp(path
);
2119 if (MKDIR(result
, 0700) < 0) {
2125 #define MY_MKDTEMP(path) my_mkdtemp(path)
2129 copy_stdin(char **dirpath
)
2131 const char *tmp
= getenv("TMPDIR");
2138 *dirpath
= xmalloc(strlen(tmp
) + 12);
2140 strcpy(*dirpath
, tmp
);
2141 strcat(*dirpath
, "/diffXXXXXX");
2142 if (MY_MKDTEMP(*dirpath
) != 0) {
2143 result
= xmalloc(strlen(*dirpath
) + 10);
2144 sprintf(result
, "%s/stdin", *dirpath
);
2146 if ((fp
= fopen(result
, "w")) != 0) {
2147 while ((ch
= MY_GETC(stdin
)) != EOF
) {
2154 rmdir(*dirpath
); /* Assume that the /stdin file was not created */
2167 set_path_opt(char *value
, int destination
)
2170 path_dest
= destination
;
2171 if (*path_opt
!= 0) {
2172 if (is_dir(path_opt
)) {
2175 fprintf(stderr
, "Not a directory:%s\n", path_opt
);
2184 static const char *msg
[] =
2186 "Usage: diffstat [options] [files]",
2188 "Reads from one or more input files which contain output from 'diff',",
2189 "producing a histogram of total lines changed for each file referenced.",
2190 "If no filename is given on the command line, reads from standard input.",
2193 " -c prefix each line with comment (#)",
2195 " -d debug - prints a lot of information",
2197 " -D PATH specify location of patched files, use for unchanged-count",
2198 " -e FILE redirect standard error to FILE",
2199 " -f NUM format (0=concise, 1=normal, 2=filled, 4=values)",
2200 " -h print this message",
2201 " -k do not merge filenames",
2202 " -l list filenames only",
2203 " -m merge insert/delete data in chunks as modified-lines",
2204 " -n NUM specify minimum width for the filenames (default: auto)",
2205 " -N NUM specify maximum width for the filenames (default: auto)",
2206 " -o FILE redirect standard output to FILE",
2207 " -p NUM specify number of pathname-separators to strip (default: common)",
2208 " -q suppress the \"0 files changed\" message for empty diffs",
2209 " -r NUM specify rounding for histogram (0=none, 1=simple, 2=adjusted)",
2210 " -R assume patch was created with old and new files swapped",
2211 " -S PATH specify location of original files, use for unchanged-count",
2212 " -t print a table (comma-separated-values) rather than histogram",
2213 " -u do not sort the input list",
2214 " -v show progress if output is redirected to a file",
2215 " -V prints the version number",
2216 " -w NUM specify maximum width of the output (default: 80)",
2219 for (j
= 0; j
< sizeof(msg
) / sizeof(msg
[0]); j
++)
2220 fprintf(fp
, "%s\n", msg
[j
]);
2223 /* Wrapper around getopt that also parses "--help" and "--version".
2224 * argc, argv, opts, return value, and globals optarg, optind,
2225 * opterr, and optopt are as in getopt(). help and version designate
2226 * what should be returned if --help or --version are encountered. */
2228 getopt_helper(int argc
, char *const argv
[], const char *opts
,
2229 int help
, int version
)
2231 if (optind
< argc
&& argv
[optind
] != NULL
) {
2232 if (strcmp(argv
[optind
], "--help") == 0) {
2235 } else if (strcmp(argv
[optind
], "--version") == 0) {
2240 return getopt(argc
, argv
, opts
);
2244 main(int argc
, char *argv
[])
2251 while ((j
= getopt_helper(argc
, argv
,
2252 "bcCdD:e:f:hklmn:N:o:p:qr:RsS:tuvVw:", 'h', 'V'))
2256 suppress_binary
= 1;
2270 set_path_opt(optarg
, 1);
2273 if (freopen(optarg
, "w", stderr
) == 0)
2277 format_opt
= atoi(optarg
);
2281 return (EXIT_SUCCESS
);
2292 min_name_wide
= atoi(optarg
);
2295 max_name_wide
= atoi(optarg
);
2298 if (freopen(optarg
, "w", stdout
) == 0)
2302 prefix_opt
= atoi(optarg
);
2305 round_opt
= atoi(optarg
);
2314 set_path_opt(optarg
, 0);
2327 if (!sscanf(Id
, "%*s %*s %s", version
))
2329 (void) strcpy(version
, "?");
2330 printf("diffstat version %s\n", version
);
2331 return (EXIT_SUCCESS
);
2333 max_width
= atoi(optarg
);
2340 return (EXIT_FAILURE
);
2345 * The numbers from -S/-D options will only be useful if the merge option
2351 show_progress
= verbose
&& (!isatty(fileno(stdout
))
2352 && isatty(fileno(stderr
)));
2355 use_tsearch
= (sort_names
&& merge_names
);
2358 if (optind
< argc
) {
2359 while (optind
< argc
) {
2361 char *name
= argv
[optind
++];
2363 char *command
= is_compressed(name
);
2365 if ((fp
= popen(command
, "r")) != 0) {
2366 if (show_progress
) {
2367 (void) fprintf(stderr
, "%s\n", name
);
2368 (void) fflush(stderr
);
2376 if ((fp
= fopen(name
, "rb")) != 0) {
2377 if (show_progress
) {
2378 (void) fprintf(stderr
, "%s\n", name
);
2379 (void) fflush(stderr
);
2390 Decompress which
= dcEmpty
;
2391 char *stdin_dir
= 0;
2398 if ((ch
= MY_GETC(stdin
)) != EOF
) {
2400 if (ch
== 'B') { /* perhaps bzip2 (poor magic design...) */
2401 sniff
[got
++] = (char) ch
;
2403 if ((ch
= MY_GETC(stdin
)) == EOF
)
2405 sniff
[got
++] = (char) ch
;
2408 && !strncmp(sniff
, "BZh", (size_t) 3)
2409 && isdigit((unsigned char) sniff
[3])
2410 && isdigit((unsigned char) sniff
[4])) {
2413 } else if (ch
== ']') { /* perhaps lzma */
2414 sniff
[got
++] = (char) ch
;
2416 if ((ch
= MY_GETC(stdin
)) == EOF
)
2418 sniff
[got
++] = (char) ch
;
2421 && !memcmp(sniff
, "]\0\0\200", (size_t) 4)) {
2424 } else if (ch
== 0xfd) { /* perhaps xz */
2425 sniff
[got
++] = (char) ch
;
2427 if ((ch
= MY_GETC(stdin
)) == EOF
)
2429 sniff
[got
++] = (char) ch
;
2432 && !memcmp(sniff
, "\3757zXZ\0", (size_t) 6)) {
2435 } else if (ch
== '\037') { /* perhaps compress, etc. */
2436 sniff
[got
++] = (char) ch
;
2437 if ((ch
= MY_GETC(stdin
)) != EOF
) {
2438 sniff
[got
++] = (char) ch
;
2452 sniff
[got
++] = (char) ch
;
2456 * The C standard only guarantees one ungetc;
2457 * virtually everyone allows more.
2460 ungetc(sniff
[--got
], stdin
);
2464 && (myfile
= copy_stdin(&stdin_dir
)) != 0) {
2466 /* open pipe to decompress temporary file */
2467 command
= decompressor(which
, myfile
);
2468 if ((fp
= popen(command
, "r")) != 0) {
2469 do_file(fp
, "stdin");
2480 } else if (which
!= dcEmpty
)
2482 do_file(stdin
, "stdin");
2485 #if defined(NO_LEAKS)
2486 while (all_data
!= 0) {
2490 return (EXIT_SUCCESS
);