#ifndef NO_IDENT
static const char *Id = "$Id: diffstat.c,v 1.51 2009/11/08 01:59:15 tom Exp $";
#endif
#if defined(HAVE_CONFIG_H)
#include <config.h>
#endif
#if defined(WIN32) && !defined(HAVE_CONFIG_H)
#define HAVE_STDLIB_H
#define HAVE_STRING_H
#define HAVE_MALLOC_H
#define HAVE_GETOPT_H
#endif
#include <stdio.h>
#include <ctype.h>
#ifdef HAVE_STRING_H
#include <string.h>
#else
#include <strings.h>
#define strchr index
#define strrchr rindex
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#else
extern int atoi(const char *);
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#else
extern int isatty(int);
#endif
#ifdef HAVE_MALLOC_H
#include <malloc.h>
#endif
#if defined(HAVE_SEARCH_H) && defined(HAVE_TSEARCH)
#include <search.h>
#else
#undef HAVE_TSEARCH
#endif
#ifdef HAVE_GETC_UNLOCKED
#define MY_GETC getc_unlocked
#else
#define MY_GETC getc
#endif
#ifdef HAVE_GETOPT_H
#include <getopt.h>
#elif !defined(HAVE_GETOPT_HEADER)
extern int getopt(int, char *const *, const char *);
extern char *optarg;
extern int optind;
#endif
#include <sys/types.h>
#include <sys/stat.h>
#if !defined(EXIT_SUCCESS)
#define EXIT_SUCCESS 0
#define EXIT_FAILURE 1
#endif
#ifndef BZCAT_PATH
#define BZCAT_PATH ""
#endif
#ifndef BZIP2_PATH
#define BZIP2_PATH ""
#endif
#ifndef COMPRESS_PATH
#define COMPRESS_PATH ""
#endif
#ifndef GZIP_PATH
#define GZIP_PATH ""
#endif
#ifndef LZCAT_PATH
#define LZCAT_PATH ""
#endif
#ifndef PCAT_PATH
#define PCAT_PATH ""
#endif
#ifndef UNCOMPRESS_PATH
#define UNCOMPRESS_PATH ""
#endif
#ifndef ZCAT_PATH
#define ZCAT_PATH ""
#endif
#if defined(__MINGW32__) || defined(WIN32)
#define MKDIR(name,mode) mkdir(name)
#else
#define MKDIR(name,mode) mkdir(name,mode)
#endif
#if defined(WIN32) && !defined(__MINGW32__)
#define PATHSEP '\\'
#else
#define PATHSEP '/'
#endif
#define SQUOTE '\''
#define EOS '\0'
#define BLANK ' '
#define UC(c) ((unsigned char)(c))
#ifndef OPT_TRACE
#define OPT_TRACE 1
#endif
#if OPT_TRACE
#define TRACE(p) if (trace_opt) printf p
#else
#define TRACE(p)
#endif
#define contain_any(s,reject) (strcspn(s,reject) != strlen(s))
#define HAVE_NOTHING 0
#define HAVE_GENERIC 1
#define HAVE_PATH 2
#define HAVE_PATH2 4
#define FMT_CONCISE 0
#define FMT_NORMAL 1
#define FMT_FILLED 2
#define FMT_VERBOSE 4
typedef enum comment {
Normal, Only, Binary
} Comment;
#define MARKS 4
typedef enum {
cInsert = 0,
cDelete,
cModify,
cEquals
} Change;
#define InsOf(p) (p)->count[cInsert]
#define DelOf(p) (p)->count[cDelete]
#define ModOf(p) (p)->count[cModify]
#define EqlOf(p) (p)->count[cEquals]
#define TotalOf(p) (InsOf(p) + DelOf(p) + ModOf(p) + EqlOf(p))
#define for_each_mark(n) for (n = 0; n < num_marks; ++n)
typedef struct _data {
struct _data *link;
char *name;
int base;
Comment cmt;
int pending;
long chunks;
long chunk[MARKS];
long count[MARKS];
} DATA;
typedef enum {
dcNone = 0,
dcBzip,
dcCompress,
dcGzip,
dcLzma,
dcPack,
dcEmpty
} Decompress;
static const char marks[MARKS + 1] = "+-!=";
static DATA *all_data;
static char *comment_opt = "";
static char *path_opt = 0;
static int format_opt = FMT_NORMAL;
static int max_width;
static int merge_names = 1;
static int merge_opt = 0;
static int min_name_wide;
static int max_name_wide;
static int names_only;
static int num_marks = 3;
static int show_progress;
static int path_dest;
static int plot_width;
static int prefix_opt = -1;
static int round_opt = 0;
static int table_opt = 0;
static int trace_opt = 0;
static int sort_names = 1;
static int verbose = 0;
static int quiet = 0;
static int suppress_binary = 0;
static long plot_scale;
#ifdef HAVE_TSEARCH
static int use_tsearch;
static void *sorted_data;
#endif
static int prefix_len = -1;
static void
failed(const char *s)
{
perror(s);
exit(EXIT_FAILURE);
}
static void *
xmalloc(size_t s)
{
void *p;
if ((p = malloc(s)) == NULL)
failed("malloc");
return p;
}
static int
is_dir(const char *name)
{
struct stat sb;
return (stat(name, &sb) == 0 &&
(sb.st_mode & S_IFMT) == S_IFDIR);
}
static void
blip(int c)
{
if (show_progress) {
(void) fputc(c, stderr);
(void) fflush(stderr);
}
}
static char *
new_string(const char *s)
{
return strcpy((char *) xmalloc((size_t) (strlen(s) + 1)), s);
}
static int
compare_data(const void *a, const void *b)
{
const DATA *p = (const DATA *) a;
const DATA *q = (const DATA *) b;
return strcmp(p->name + p->base, q->name + q->base);
}
static void
init_data(DATA * data, char *name, int base)
{
memset(data, 0, sizeof(*data));
data->name = name;
data->base = base;
data->cmt = Normal;
}
static DATA *
new_data(char *name, int base)
{
DATA *r = (DATA *) xmalloc(sizeof(DATA));
init_data(r, new_string(name), base);
return r;
}
#ifdef HAVE_TSEARCH
static DATA *
add_tsearch_data(char *name, int base)
{
DATA find;
DATA *result;
void *pp;
init_data(&find, name, base);
if ((pp = tfind(&find, &sorted_data, compare_data)) != 0) {
result = *(DATA **) pp;
return result;
}
result = new_data(name, base);
(void) tsearch(result, &sorted_data, compare_data);
result->link = all_data;
all_data = result;
return result;
}
#endif
static DATA *
find_data(char *name)
{
DATA *p, *q, *r;
DATA find;
int base = 0;
TRACE(("** find_data(%s)\n", name));
if (prefix_opt >= 0) {
int n;
for (n = prefix_opt; n > 0; n--) {
char *s = strchr(name + base, PATHSEP);
if (s == 0 || *++s == EOS)
break;
base = s - name;
}
TRACE(("** base set to %d\n", base));
}
#ifdef HAVE_TSEARCH
if (use_tsearch) {
r = add_tsearch_data(name, base);
} else
#endif
{
init_data(&find, name, base);
for (p = all_data, q = 0; p != 0; q = p, p = p->link) {
int cmp = compare_data(p, &find);
if (merge_names && (cmp == 0))
return p;
if (sort_names && (cmp > 0))
break;
}
r = new_data(name, base);
if (q != 0)
q->link = r;
else
all_data = r;
r->link = p;
}
return r;
}
static int
delink(DATA * data)
{
DATA *p, *q;
TRACE(("** delink '%s'\n", data->name));
#ifdef HAVE_TSEARCH
if (use_tsearch) {
if (tdelete(data, &sorted_data, compare_data) == 0)
return 0;
}
#endif
for (p = all_data, q = 0; p != 0; q = p, p = p->link) {
if (p == data) {
if (q != 0)
q->link = p->link;
else
all_data = p->link;
free(p->name);
free(p);
return 1;
}
}
return 0;
}
static int
match(const char *s, const char *p)
{
int ok = 0;
while (*s != EOS) {
if (*p == EOS) {
ok = 1;
break;
}
if (*s++ != *p++)
break;
if (*s == EOS && *p == EOS) {
ok = 1;
break;
}
}
return ok;
}
static int
version_num(const char *s)
{
int main_ver, sub_ver;
char temp[2];
return (sscanf(s, "%d.%d%c", &main_ver, &sub_ver, temp) == 2);
}
static int
edit_range(const char *s)
{
int first, last;
char temp[2];
return (sscanf(s, "%d,%d%c", &first, &last, temp) == 2)
|| (sscanf(s, "%d%c", &first, temp) == 1);
}
static int
decode_default(char *s,
int *first, int *first_size,
int *second, int *second_size)
{
int rc = 0;
char *next;
if (isdigit(UC(*s))) {
*first_size = 1;
*second_size = 1;
*first = strtol(s, &next, 10);
if (next != 0 && next != s) {
if (*next == ',') {
s = ++next;
*first_size = strtol(s, &next, 10) + 1 - *first;
}
}
if (next != 0 && next != s) {
switch (*next++) {
case 'a':
case 'c':
case 'd':
s = next;
*second = strtol(s, &next, 10);
if (next != 0 && next != s) {
if (*next == ',') {
s = ++next;
*second_size = strtol(s, &next, 10) + 1 - *second;
}
}
if (next != 0 && next != s && *next == '\0')
rc = 1;
break;
}
}
}
return rc;
}
static int
decode_range(const char *s, int *first, int *second)
{
int rc = 0;
char check;
if (isdigit(UC(*s))) {
if (sscanf(s, "%d,%d%c", first, second, &check) == 2) {
TRACE(("** decode_range #1 first=%d, second=%d\n", *first, *second));
rc = 1;
} else if (sscanf(s, "%d%c", first, &check) == 1) {
*second = *first;
TRACE(("** decode_range #2 first=%d, second=%d\n", *first, *second));
rc = 1;
}
}
return rc;
}
static int
HadDiffs(const DATA * data)
{
return InsOf(data) != 0
|| DelOf(data) != 0
|| ModOf(data) != 0
|| data->cmt != Normal;
}
static int
can_be_merged(const char *path)
{
int result = 0;
if (strcmp(path, "")
&& strcmp(path, "/dev/null"))
result = 1;
return result;
}
static int
is_leaf(const char *theLeaf, const char *path)
{
char *s;
if (strchr(theLeaf, PATHSEP) == 0
&& (s = strrchr(path, PATHSEP)) != 0
&& !strcmp(++s, theLeaf))
return 1;
return 0;
}
static char *
do_merging(DATA * data, char *path, int *freed)
{
TRACE(("** do_merging(%s,%s) diffs:%d\n", data->name, path, HadDiffs(data)));
*freed = 0;
if (!HadDiffs(data)) {
if (is_leaf(data->name, path)) {
TRACE(("** is_leaf: %s vs %s\n", data->name, path));
*freed = delink(data);
} else if (can_be_merged(data->name)
&& can_be_merged(path)) {
size_t len1 = strlen(data->name);
size_t len2 = strlen(path);
unsigned n;
int matched = 0;
int diff = 0;
int local = 0;
if (len1 > len2) {
if (!strncmp(data->name, path, len2)) {
TRACE(("** trimming data '%s' to '%.*s'\n",
data->name, (int) len2, data->name));
len1 = len2;
#ifdef HAVE_TSEARCH
if (use_tsearch) {
char *trim = new_string(data->name);
trim[len1] = EOS;
data = add_tsearch_data(trim, data->base);
free(trim);
local = 1;
} else
#endif
data->name[len1] = EOS;
}
} else if (len1 < len2) {
if (!strncmp(data->name, path, len1)) {
TRACE(("** trimming path '%s' to '%.*s'\n",
path, (int) len1, path));
path[len2 = len1] = EOS;
}
}
for (n = 1; n <= len1 && n <= len2; n++) {
if (data->name[len1 - n] != path[len2 - n]) {
diff = (int) n;
break;
}
if (path[len2 - n] == PATHSEP)
matched = (int) n;
}
if (prefix_opt < 0
&& matched != 0
&& diff)
path += ((int) len2 - matched + 1);
if (!local)
*freed = delink(data);
TRACE(("** merge @%d, prefix_opt=%d matched=%d diff=%d\n",
__LINE__, prefix_opt, matched, diff));
} else if (!can_be_merged(path)) {
TRACE(("** do not merge, retain @%d\n", __LINE__));
path = data->name;
} else {
TRACE(("** merge @%d\n", __LINE__));
*freed = delink(data);
}
} else if (!can_be_merged(path)) {
path = data->name;
}
TRACE(("** finish do_merging ->%s\n", path));
return path;
}
static int
begin_data(const DATA * p)
{
if (!can_be_merged(p->name)
&& strchr(p->name, PATHSEP) != 0) {
TRACE(("** begin_data:HAVE_PATH\n"));
return HAVE_PATH;
}
TRACE(("** begin_data:HAVE_GENERIC\n"));
return HAVE_GENERIC;
}
static char *
skip_blanks(char *s)
{
while (isspace(UC(*s)))
++s;
return s;
}
static char *
skip_options(char *params)
{
while (*params != '\0') {
params = skip_blanks(params);
if (*params == '-') {
while (isgraph(UC(*params)))
params++;
} else {
break;
}
}
return params;
}
static void
dequote(char *s)
{
size_t len = strlen(s);
int n;
if (*s == SQUOTE && len > 2 && s[len - 1] == SQUOTE) {
for (n = 0; (s[n] = s[n + 1]) != EOS; ++n) {
;
}
s[len - 2] = EOS;
}
}
static void
fixed_buffer(char **buffer, size_t want)
{
*buffer = (char *) xmalloc(want);
}
static void
adjust_buffer(char **buffer, size_t want)
{
if ((*buffer = (char *) realloc(*buffer, want)) == 0)
failed("realloc");
}
static int
get_line(char **buffer, size_t *have, FILE *fp)
{
int ch;
size_t used = 0;
while ((ch = MY_GETC(fp)) != EOF) {
if (used + 2 > *have) {
adjust_buffer(buffer, *have *= 2);
}
(*buffer)[used++] = (char) ch;
if (ch == '\n')
break;
}
(*buffer)[used] = '\0';
return (used != 0);
}
static char *
data_filename(const DATA * p)
{
return (p->name + (prefix_opt >= 0 ? p->base : prefix_len));
}
static int
count_lines(DATA * p)
{
int result = -1;
char *filename = 0;
char *filetail = data_filename(p);
unsigned want = strlen(path_opt) + 2 + strlen(filetail);
FILE *fp;
int ch;
if ((filename = malloc(want)) != 0) {
sprintf(filename, "%s/%s", path_opt, filetail);
TRACE(("count_lines %s\n", filename));
if ((fp = fopen(filename, "r")) != 0) {
result = 0;
while ((ch = MY_GETC(fp)) != EOF) {
if (ch == '\n')
++result;
}
fclose(fp);
} else {
fprintf(stderr, "Cannot open %s\n", filename);
}
free(filename);
} else {
failed("count_lines");
}
return result;
}
static void
update_chunk(DATA * p, Change change)
{
if (merge_opt) {
p->pending += 1;
p->chunk[change] += 1;
} else {
p->count[change] += 1;
}
}
static void
finish_chunk(DATA * p)
{
int i;
if (p->pending) {
p->pending = 0;
p->chunks += 1;
if (merge_opt) {
if (p->chunk[cInsert] && p->chunk[cDelete]) {
int change;
if (p->chunk[cInsert] > p->chunk[cDelete]) {
change = p->chunk[cDelete];
} else {
change = p->chunk[cInsert];
}
p->chunk[cInsert] -= change;
p->chunk[cDelete] -= change;
p->chunk[cModify] += change;
}
}
for_each_mark(i) {
p->count[i] += p->chunk[i];
p->chunk[i] = 0;
}
}
}
#define date_delims(a,b) (((a)=='/' && (b)=='/') || ((a) == '-' && (b) == '-'))
#define CASE_TRACE() TRACE(("** handle case for '%c' %d:%s\n", *buffer, ok, that ? that->name : ""))
static void
do_file(FILE *fp, char *default_name)
{
static const char *only_stars = "***************";
DATA dummy;
DATA *that = &dummy;
DATA *prev = 0;
char *buffer = 0;
char *b_fname = 0;
char *b_temp1 = 0;
char *b_temp2 = 0;
char *b_temp3 = 0;
size_t length = 0;
size_t fixed = 0;
int ok = HAVE_NOTHING;
int marker;
int freed = 0;
int unified = 0;
int old_unify = 0;
int new_unify = 0;
int expect_unify = 0;
int old_dft = 0;
int new_dft = 0;
int context = 1;
char *s;
#if OPT_TRACE
int line_no = 0;
#endif
memset(&dummy, 0, sizeof(dummy));
dummy.name = "";
fixed_buffer(&buffer, fixed = length = BUFSIZ);
fixed_buffer(&b_fname, length);
fixed_buffer(&b_temp1, length);
fixed_buffer(&b_temp2, length);
fixed_buffer(&b_temp3, length);
while (get_line(&buffer, &length, fp)) {
if (length > fixed) {
fixed = length;
adjust_buffer(&b_fname, length);
adjust_buffer(&b_temp1, length);
adjust_buffer(&b_temp2, length);
adjust_buffer(&b_temp3, length);
}
for (s = buffer + strlen(buffer); s > buffer; s--) {
if ((UC(s[-1]) == '\n') || (UC(s[-1]) == '\r'))
s[-1] = EOS;
else
break;
}
++line_no;
TRACE(("[%05d] %s\n", line_no, buffer));
if (line_no == 1 && !strncmp(buffer, "@@", 2)) {
unified = 2;
that = find_data(default_name);
ok = begin_data(that);
}
marker = 0;
if (that != &dummy && !strcmp(buffer, only_stars)) {
finish_chunk(that);
TRACE(("** begin context chunk\n"));
context = 2;
} else if (line_no == 1 && !strcmp(buffer, only_stars)) {
TRACE(("** begin context chunk\n"));
context = 2;
that = find_data(default_name);
ok = begin_data(that);
} else if (context == 2 && match(buffer, "*** ")) {
context = 1;
} else if (context == 1 && match(buffer, "--- ")) {
marker = 1;
context = 0;
} else if (match(buffer, "*** ")) {
} else if ((old_unify + new_unify) == 0 && match(buffer, "==== ")) {
finish_chunk(that);
unified = 2;
} else if ((old_unify + new_unify) == 0 && match(buffer, "--- ")) {
finish_chunk(that);
marker = unified = 1;
} else if ((old_unify + new_unify) == 0 && match(buffer, "+++ ")) {
marker = unified = 2;
} else if (unified == 2
|| ((old_unify + new_unify) == 0 && (*buffer == '@'))) {
finish_chunk(that);
unified = 0;
if (*buffer == '@') {
int old_base, new_base, old_size, new_size;
char test_at;
old_unify = new_unify = 0;
if (sscanf(buffer, "@@ -%[0-9,] +%[0-9,] @%c",
b_temp1,
b_temp2,
&test_at) == 3
&& test_at == '@'
&& decode_range(b_temp1, &old_base, &old_size)
&& decode_range(b_temp2, &new_base, &new_size)) {
old_unify = old_size;
new_unify = new_size;
unified = -1;
}
}
} else if (unified == 1 && !context) {
unified = 0;
TRACE(("?? Expected \"+++\" for unified diff\n"));
if (prev != 0
&& prev != that
&& InsOf(that) == 0
&& DelOf(that) == 0
&& strcmp(prev->name, that->name)) {
TRACE(("?? giveup on %ld/%ld %s\n", InsOf(that),
DelOf(that), that->name));
TRACE(("?? revert to %ld/%ld %s\n", InsOf(prev),
DelOf(prev), prev->name));
(void) delink(that);
that = prev;
update_chunk(that, cDelete);
}
} else if (old_unify + new_unify) {
switch (*buffer) {
case '-':
if (old_unify)
--old_unify;
break;
case '+':
if (new_unify)
--new_unify;
break;
case '\0':
case ' ':
if (old_unify)
--old_unify;
if (new_unify)
--new_unify;
break;
case '\\':
if (strstr(buffer, "newline") != 0) {
break;
}
default:
TRACE(("?? expected more in chunk\n"));
old_unify = new_unify = 0;
break;
}
if (!(old_unify + new_unify)) {
expect_unify = 2;
}
} else {
int old_base, new_base;
unified = 0;
if (line_no == 1
&& decode_default(buffer,
&old_base, &old_dft,
&new_base, &new_dft)) {
TRACE(("DFT %d,%d -> %d,%d\n",
old_base, old_base + old_dft - 1,
new_base, new_base + new_dft - 1));
finish_chunk(that);
that = find_data("unknown");
ok = begin_data(that);
}
}
if (expect_unify != 0) {
if (expect_unify-- == 1) {
if (unified == 0) {
TRACE(("?? did not get chunk\n"));
finish_chunk(that);
that = &dummy;
}
}
}
if (marker > 0) {
TRACE(("** have marker=%d, override %s\n", marker, buffer));
(void) strncpy(buffer, "***", 3);
}
switch (*buffer) {
case 'O':
CASE_TRACE();
if (match(buffer, "Only in ")) {
char *path = buffer + 8;
int found = 0;
for (s = path; *s != EOS; s++) {
if (match(s, ": ")) {
found = 1;
*s++ = PATHSEP;
while ((s[0] = s[1]) != EOS)
s++;
break;
}
}
if (found) {
blip('.');
finish_chunk(that);
that = find_data(path);
that->cmt = Only;
ok = HAVE_NOTHING;
}
}
break;
case 'I':
CASE_TRACE();
if (match(buffer, "Index: ")) {
s = strrchr(buffer, BLANK);
s = skip_blanks(s);
dequote(s);
blip('.');
finish_chunk(that);
s = do_merging(that, s, &freed);
that = find_data(s);
ok = begin_data(that);
}
break;
case 'd':
CASE_TRACE();
if (match(buffer, "diff ")
&& *(s = skip_options(buffer + 5)) != '\0') {
s = strrchr(buffer, BLANK);
s = skip_blanks(s);
dequote(s);
blip('.');
finish_chunk(that);
s = do_merging(that, s, &freed);
that = find_data(s);
ok = begin_data(that);
}
break;
case '*':
CASE_TRACE();
if (!(ok & HAVE_PATH)) {
int ddd, hour, minute, second;
int day, month, year;
char yrmon, monday;
if (sscanf(buffer,
"*** %[^\t]\t%[^ ] %[^ ] %d %d:%d:%d %d",
b_fname,
b_temp2, b_temp3, &ddd,
&hour, &minute, &second, &year) == 8
|| (sscanf(buffer,
"*** %[^\t]\t%d%c%d%c%d %d:%d:%d",
b_fname,
&year, &yrmon, &month, &monday, &day,
&hour, &minute, &second) == 9
&& date_delims(yrmon, monday)
&& !version_num(b_fname))
|| sscanf(buffer,
"*** %[^\t ]%[\t ]%[^ ] %[^ ] %d %d:%d:%d %d",
b_fname,
b_temp1,
b_temp2, b_temp3, &ddd,
&hour, &minute, &second, &year) == 9
|| (sscanf(buffer,
"*** %[^\t ]%[\t ]%d%c%d%c%d %d:%d:%d",
b_fname,
b_temp1,
&year, &yrmon, &month, &monday, &day,
&hour, &minute, &second) == 10
&& date_delims(yrmon, monday)
&& !version_num(b_fname))
|| (sscanf(buffer,
"*** %[^\t ]%[\t ]",
b_fname,
b_temp1) >= 1
&& !version_num(b_fname)
&& !contain_any(b_fname, "*")
&& !edit_range(b_fname))
) {
prev = that;
finish_chunk(that);
s = do_merging(that, b_fname, &freed);
if (freed)
prev = 0;
that = find_data(s);
ok = begin_data(that);
TRACE(("** after merge:%d:%s\n", ok, s));
}
}
break;
case '=':
CASE_TRACE();
if (!(ok & HAVE_PATH)) {
int rev;
if (((sscanf(buffer,
"==== %[^\t #]#%d - %[^\t ]",
b_fname,
&rev,
b_temp1) == 3)
|| ((sscanf(buffer,
"==== %[^\t #]#%d (%[^)]) - %[^\t ]",
b_fname,
&rev,
b_temp1,
b_temp2) == 4)))
&& !version_num(b_fname)
&& !contain_any(b_fname, "*")
&& !edit_range(b_fname)) {
TRACE(("** found p4-diff\n"));
prev = that;
finish_chunk(that);
s = do_merging(that, b_fname, &freed);
if (freed)
prev = 0;
that = find_data(s);
ok = begin_data(that);
TRACE(("** after merge:%d:%s\n", ok, s));
}
}
break;
case '+':
case '>':
CASE_TRACE();
if (ok) {
update_chunk(that, cInsert);
}
break;
case '-':
if (!ok) {
CASE_TRACE();
break;
}
if (!unified && !strcmp(buffer, "---")) {
CASE_TRACE();
break;
}
case '<':
CASE_TRACE();
if (ok) {
update_chunk(that, cDelete);
}
break;
case '!':
CASE_TRACE();
if (ok) {
update_chunk(that, cModify);
}
break;
case 'B':
case 'b':
CASE_TRACE();
if (match(buffer + 1, "inary files ")) {
s = strrchr(buffer, BLANK);
if (!strcmp(s, " differ")) {
*s = EOS;
s = strrchr(buffer, BLANK);
blip('.');
finish_chunk(that);
that = find_data(skip_blanks(s));
that->cmt = Binary;
ok = HAVE_NOTHING;
}
}
break;
}
}
blip('\n');
finish_chunk(that);
finish_chunk(&dummy);
if (buffer != 0) {
free(buffer);
free(b_fname);
free(b_temp1);
free(b_temp2);
free(b_temp3);
}
}
static long
plot_bar(long count, int c)
{
long result = count;
while (--count >= 0)
(void) putchar(c);
return result;
}
static long
plot_num(long num_value, int c, long *extra)
{
long product;
long result = 0;
if (num_value) {
product = (plot_width * num_value);
result = ((product + *extra) / plot_scale);
*extra = product - (result * plot_scale) - *extra;
plot_bar(result, c);
}
return result;
}
static long
plot_round1(const long num[MARKS])
{
long result = 0;
long scaled[MARKS];
long remain[MARKS];
long want = 0;
long have = 0;
long half = (plot_scale / 2);
int i, j;
for_each_mark(i) {
long product = (plot_width * num[i]);
scaled[i] = (product / plot_scale);
remain[i] = (product % plot_scale);
want += product;
have += product - remain[i];
}
while (want > have) {
j = -1;
for_each_mark(i) {
if (remain[i] != 0
&& (remain[i] > (j >= 0 ? remain[j] : half))) {
j = i;
}
}
if (j >= 0) {
have += remain[j];
remain[j] = 0;
scaled[j] += 1;
} else {
break;
}
}
for_each_mark(i) {
plot_bar(scaled[i], marks[i]);
result += scaled[i];
}
return result;
}
static long
plot_round2(const long num[MARKS])
{
long result = 0;
long scaled[MARKS];
long remain[MARKS];
long total = 0;
int i;
for (i = 0; i < MARKS; i++)
total += num[i];
if (total == 0)
return result;
total = (total * plot_width + (plot_scale / 2)) / plot_scale;
if (total == 0)
total++;
for_each_mark(i) {
scaled[i] = num[i] * plot_width / plot_scale;
remain[i] = num[i] * plot_width - scaled[i] * plot_scale;
total -= scaled[i];
}
while (total) {
int largest, largest_count;
long max_remain;
largest = largest_count = 0;
max_remain = 0;
for_each_mark(i) {
if (remain[i] > max_remain) {
largest = 1 << i;
largest_count = 1;
max_remain = remain[i];
} else if (remain[i] == max_remain) {
largest |= 1 << i;
largest_count++;
}
}
if (total < largest_count)
break;
for_each_mark(i) {
if (largest & (1 << i)) {
scaled[i]++;
total--;
remain[i] -= plot_width;
}
}
}
for_each_mark(i) {
result += plot_bar(scaled[i], marks[i]);
}
return result;
}
static void
plot_numbers(const DATA * p)
{
long temp = 0;
long used = 0;
int i;
printf("%5ld ", TotalOf(p));
if (format_opt & FMT_VERBOSE) {
printf("%5ld ", InsOf(p));
printf("%5ld ", DelOf(p));
printf("%5ld ", ModOf(p));
if (path_opt)
printf("%5ld ", EqlOf(p));
}
if (format_opt == FMT_CONCISE) {
for_each_mark(i) {
printf("\t%ld %c", p->count[i], marks[i]);
}
} else {
switch (round_opt) {
default:
for_each_mark(i) {
used += plot_num(p->count[i], marks[i], &temp);
}
break;
case 1:
used = plot_round1(p->count);
break;
case 2:
used = plot_round2(p->count);
break;
}
if ((format_opt & FMT_FILLED) != 0) {
if (used > plot_width)
printf("%ld", used - plot_width);
else
plot_bar(plot_width - used, '.');
}
}
}
#define changed(p) (!merge_names \
|| (p)->cmt != Normal \
|| (TotalOf(p)) != 0)
static void
show_data(const DATA * p)
{
char *name = data_filename(p);
int width;
if (!changed(p)) {
;
} else if (p->cmt == Binary && suppress_binary == 1) {
;
} else if (table_opt) {
if (names_only) {
printf("%s\n", name);
} else {
printf("%ld,%ld,%ld,",
InsOf(p),
DelOf(p),
ModOf(p));
if (path_opt)
printf("%ld,", EqlOf(p));
printf("%s\n", name);
}
} else if (names_only) {
printf("%s\n", name);
} else {
printf("%s ", comment_opt);
if (max_name_wide > 0
&& max_name_wide < min_name_wide
&& max_name_wide < ((width = (int) strlen(name)))) {
printf("%.*s", max_name_wide, name + (width - max_name_wide));
} else {
width = ((max_name_wide > 0 && max_name_wide < min_name_wide)
? max_name_wide
: min_name_wide);
printf("%-*.*s", width, width, name);
}
putchar('|');
switch (p->cmt) {
default:
case Normal:
plot_numbers(p);
break;
case Binary:
printf("binary");
break;
case Only:
printf("only");
break;
}
printf("\n");
}
}
#ifdef HAVE_TSEARCH
static void
show_tsearch(const void *nodep, const VISIT which, const int depth)
{
const DATA *p = *(DATA * const *) nodep;
(void) depth;
if (which == postorder || which == leaf)
show_data(p);
}
#endif
static int
ignore_data(DATA * p)
{
return ((!changed(p))
|| (p->cmt == Binary && suppress_binary));
}
static void
summarize(void)
{
DATA *p;
long total_ins = 0;
long total_del = 0;
long total_mod = 0;
long total_eql = 0;
long temp;
int num_files = 0, shortest_name = -1, longest_name = -1;
plot_scale = 0;
for (p = all_data; p; p = p->link) {
int len = (int) strlen(p->name);
if (ignore_data(p))
continue;
if (prefix_opt >= 0) {
if (min_name_wide < (len - p->base))
min_name_wide = (len - p->base);
} else {
if (len < prefix_len || prefix_len < 0)
prefix_len = len;
while (prefix_len > 0) {
if (p->name[prefix_len - 1] != PATHSEP)
prefix_len--;
else if (strncmp(all_data->name, p->name, (size_t) prefix_len))
prefix_len--;
else
break;
}
if (len > longest_name)
longest_name = len;
if (len < shortest_name || shortest_name < 0)
shortest_name = len;
}
}
for (p = all_data; p; p = p->link) {
if (!ignore_data(p)) {
EqlOf(p) = 0;
if (path_opt != 0) {
int count = count_lines(p);
if (count >= 0) {
EqlOf(p) = count - ModOf(p);
if (path_dest) {
EqlOf(p) -= InsOf(p);
InsOf(p) = 0;
} else {
EqlOf(p) -= DelOf(p);
DelOf(p) = 0;
}
if (EqlOf(p) < 0)
EqlOf(p) = 0;
}
}
num_files++;
total_ins += InsOf(p);
total_del += DelOf(p);
total_mod += ModOf(p);
total_eql += EqlOf(p);
temp = TotalOf(p);
if (temp > plot_scale)
plot_scale = temp;
}
}
if (prefix_opt < 0) {
if (prefix_len < 0)
prefix_len = 0;
if ((longest_name - prefix_len) > min_name_wide)
min_name_wide = (longest_name - prefix_len);
}
min_name_wide++;
plot_width = (max_width - min_name_wide - 8);
if (plot_width < 10)
plot_width = 10;
if (plot_scale < plot_width)
plot_scale = plot_width;
if (table_opt) {
if (!names_only) {
printf("INSERTED,DELETED,MODIFIED,");
if (path_opt)
printf("UNCHANGED,");
}
printf("FILENAME\n");
}
#ifdef HAVE_TSEARCH
if (use_tsearch) {
twalk(sorted_data, show_tsearch);
} else
#endif
for (p = all_data; p; p = p->link) {
show_data(p);
}
if (!table_opt && !names_only) {
#define PLURAL(n) n, n != 1 ? "s" : ""
if (num_files > 0 || !quiet) {
printf("%s %d file%s changed", comment_opt, PLURAL(num_files));
if (total_ins)
printf(", %ld insertion%s(+)", PLURAL(total_ins));
if (total_del)
printf(", %ld deletion%s(-)", PLURAL(total_del));
if (total_mod)
printf(", %ld modification%s(!)", PLURAL(total_mod));
if (total_eql && path_opt != 0)
printf(", %ld unchanged line%s(=)", PLURAL(total_eql));
(void) putchar('\n');
}
}
}
#ifdef HAVE_POPEN
static const char *
get_program(char *name, const char *dft)
{
const char *result = getenv(name);
if (result == 0 || *result == '\0')
result = dft;
TRACE(("get_program(%s) = %s\n", name, result));
return result;
}
#define GET_PROGRAM(name) get_program("DIFFSTAT_" #name, name)
static char *
decompressor(Decompress which, const char *name)
{
const char *verb = 0;
const char *opts = "";
char *result = 0;
size_t len = strlen(name);
switch (which) {
case dcBzip:
verb = GET_PROGRAM(BZCAT_PATH);
if (*verb == '\0') {
verb = GET_PROGRAM(BZIP2_PATH);
opts = "-dc";
}
break;
case dcCompress:
verb = GET_PROGRAM(ZCAT_PATH);
if (*verb == '\0') {
verb = GET_PROGRAM(UNCOMPRESS_PATH);
opts = "-c";
if (*verb == '\0') {
verb = GET_PROGRAM(COMPRESS_PATH);
opts = "-dc";
}
}
break;
case dcGzip:
verb = GET_PROGRAM(GZIP_PATH);
opts = "-dc";
break;
case dcLzma:
verb = GET_PROGRAM(LZCAT_PATH);
opts = "-dc";
break;
case dcPack:
verb = GET_PROGRAM(PCAT_PATH);
break;
case dcEmpty:
case dcNone:
break;
}
if (verb != 0 && *verb != '\0') {
result = (char *) xmalloc(strlen(verb) + 10 + len);
sprintf(result, "%s %s", verb, opts);
if (*name != '\0') {
sprintf(result + strlen(result), " \"%s\"", name);
}
}
return result;
}
static char *
is_compressed(const char *name)
{
size_t len = strlen(name);
Decompress which;
if (len > 2 && !strcmp(name + len - 2, ".Z")) {
which = dcCompress;
} else if (len > 2 && !strcmp(name + len - 2, ".z")) {
which = dcPack;
} else if (len > 3 && !strcmp(name + len - 3, ".gz")) {
which = dcGzip;
} else if (len > 4 && !strcmp(name + len - 4, ".bz2")) {
which = dcBzip;
} else if (len > 5 && !strcmp(name + len - 5, ".lzma")) {
which = dcLzma;
} else if (len > 3 && !strcmp(name + len - 3, ".xz")) {
which = dcLzma;
} else {
which = dcNone;
}
return decompressor(which, name);
}
#ifdef HAVE_MKDTEMP
#define MY_MKDTEMP(path) mkdtemp(path)
#else
static char *
my_mkdtemp(char *path)
{
char *result = mktemp(path);
if (result != 0) {
if (MKDIR(result, 0700) < 0) {
result = 0;
}
}
return path;
}
#define MY_MKDTEMP(path) my_mkdtemp(path)
#endif
static char *
copy_stdin(char **dirpath)
{
char *tmp = getenv("TMPDIR");
char *result = 0;
int ch;
FILE *fp;
if (tmp == 0)
tmp = "/tmp/";
*dirpath = xmalloc(strlen(tmp) + 12);
strcpy(*dirpath, tmp);
strcat(*dirpath, "/diffXXXXXX");
if (MY_MKDTEMP(*dirpath) != 0) {
result = xmalloc(strlen(*dirpath) + 10);
sprintf(result, "%s/stdin", *dirpath);
if ((fp = fopen(result, "w")) != 0) {
while ((ch = MY_GETC(stdin)) != EOF) {
fputc(ch, fp);
}
fclose(fp);
} else {
free(result);
result = 0;
}
}
return result;
}
#endif
static void
set_path_opt(char *value, int destination)
{
path_opt = value;
path_dest = destination;
if (*path_opt != 0) {
if (is_dir(path_opt)) {
num_marks = 4;
} else {
fprintf(stderr, "Not a directory:%s\n", path_opt);
exit(EXIT_FAILURE);
}
}
}
static void
usage(FILE *fp)
{
static const char *msg[] =
{
"Usage: diffstat [options] [files]",
"",
"Reads from one or more input files which contain output from 'diff',",
"producing a histogram of total lines changed for each file referenced.",
"If no filename is given on the command line, reads from standard input.",
"",
"Options:",
" -c prefix each line with comment (#)",
#if OPT_TRACE
" -d debug - prints a lot of information",
#endif
" -D PATH specify location of patched files, use for unchanged-count",
" -e FILE redirect standard error to FILE",
" -f NUM format (0=concise, 1=normal, 2=filled, 4=values)",
" -h print this message",
" -k do not merge filenames",
" -l list filenames only",
" -m merge insert/delete data in chunks as modified-lines",
" -n NUM specify minimum width for the filenames (default: auto)",
" -N NUM specify maximum width for the filenames (default: auto)",
" -o FILE redirect standard output to FILE",
" -p NUM specify number of pathname-separators to strip (default: common)",
" -q suppress the \"0 files changed\" message for empty diffs",
" -r NUM specify rounding for histogram (0=none, 1=simple, 2=adjusted)",
" -S PATH specify location of original files, use for unchanged-count",
" -t print a table (comma-separated-values) rather than histogram",
" -u do not sort the input list",
" -v show progress if output is redirected to a file",
" -V prints the version number",
" -w NUM specify maximum width of the output (default: 80)",
};
unsigned j;
for (j = 0; j < sizeof(msg) / sizeof(msg[0]); j++)
fprintf(fp, "%s\n", msg[j]);
}
static int
getopt_helper(int argc, char *const argv[], const char *opts,
int help, int version)
{
if (optind < argc && argv[optind] != NULL) {
if (strcmp(argv[optind], "--help") == 0) {
optind++;
return help;
} else if (strcmp(argv[optind], "--version") == 0) {
optind++;
return version;
}
}
return getopt(argc, argv, opts);
}
int
main(int argc, char *argv[])
{
int j;
char version[80];
max_width = 80;
while ((j = getopt_helper(argc, argv,
"bcdD:e:f:hklmn:N:o:p:qr:S:tuvVw:", 'h', 'V'))
!= -1) {
switch (j) {
case 'b':
suppress_binary = 1;
break;
case 'c':
comment_opt = "#";
break;
#if OPT_TRACE
case 'd':
trace_opt = 1;
break;
#endif
case 'e':
if (freopen(optarg, "w", stderr) == 0)
failed(optarg);
break;
case 'f':
format_opt = atoi(optarg);
break;
case 'h':
usage(stdout);
return (EXIT_SUCCESS);
case 'k':
merge_names = 0;
break;
case 'l':
names_only = 1;
break;
case 'm':
merge_opt = 1;
break;
case 'n':
min_name_wide = atoi(optarg);
break;
case 'N':
max_name_wide = atoi(optarg);
break;
case 'o':
if (freopen(optarg, "w", stdout) == 0)
failed(optarg);
break;
case 'p':
prefix_opt = atoi(optarg);
break;
case 'D':
set_path_opt(optarg, 1);
break;
case 'S':
set_path_opt(optarg, 0);
break;
case 'r':
round_opt = atoi(optarg);
break;
case 't':
table_opt = 1;
break;
case 'u':
sort_names = 0;
break;
case 'v':
verbose = 1;
break;
case 'V':
#ifndef NO_IDENT
if (!sscanf(Id, "%*s %*s %s", version))
#endif
(void) strcpy(version, "?");
printf("diffstat version %s\n", version);
return (EXIT_SUCCESS);
case 'w':
max_width = atoi(optarg);
break;
case 'q':
quiet = 1;
break;
default:
usage(stderr);
return (EXIT_FAILURE);
}
}
if (path_opt)
merge_opt = 1;
show_progress = verbose && (!isatty(fileno(stdout))
&& isatty(fileno(stderr)));
#ifdef HAVE_TSEARCH
use_tsearch = (sort_names && merge_names);
#endif
if (optind < argc) {
while (optind < argc) {
FILE *fp;
char *name = argv[optind++];
#ifdef HAVE_POPEN
char *command = is_compressed(name);
if (command != 0) {
if ((fp = popen(command, "r")) != 0) {
if (show_progress) {
(void) fprintf(stderr, "%s\n", name);
(void) fflush(stderr);
}
do_file(fp, name);
(void) pclose(fp);
}
free(command);
} else
#endif
if ((fp = fopen(name, "rb")) != 0) {
if (show_progress) {
(void) fprintf(stderr, "%s\n", name);
(void) fflush(stderr);
}
do_file(fp, name);
(void) fclose(fp);
} else {
failed(name);
}
}
} else {
#ifdef HAVE_POPEN
FILE *fp;
Decompress which = dcEmpty;
char *stdin_dir = 0;
char *myfile;
char sniff[8];
int ch;
unsigned got = 0;
char *command;
if ((ch = MY_GETC(stdin)) != EOF) {
which = dcNone;
if (ch == 'B') {
sniff[got++] = (char) ch;
while (got < 5) {
if ((ch = MY_GETC(stdin)) == EOF)
break;
sniff[got++] = (char) ch;
}
if (got == 5
&& !strncmp(sniff, "BZh", 3)
&& isdigit((unsigned char) sniff[3])
&& isdigit((unsigned char) sniff[4])) {
which = dcBzip;
}
} else if (ch == ']') {
sniff[got++] = (char) ch;
while (got < 4) {
if ((ch = MY_GETC(stdin)) == EOF)
break;
sniff[got++] = (char) ch;
}
if (got == 4
&& !memcmp(sniff, "]\0\0\200", 4)) {
which = dcLzma;
}
} else if (ch == 0xfd) {
sniff[got++] = (char) ch;
while (got < 6) {
if ((ch = MY_GETC(stdin)) == EOF)
break;
sniff[got++] = (char) ch;
}
if (got == 6
&& !memcmp(sniff, "\3757zXZ\0", 6)) {
which = dcLzma;
}
} else if (ch == '\037') {
sniff[got++] = (char) ch;
if ((ch = MY_GETC(stdin)) != EOF) {
sniff[got++] = (char) ch;
switch (ch) {
case 0213:
which = dcGzip;
break;
case 0235:
which = dcCompress;
break;
case 0036:
which = dcPack;
break;
}
}
} else {
sniff[got++] = (char) ch;
}
}
while (got != 0) {
ungetc(sniff[--got], stdin);
}
if (which != dcNone
&& which != dcEmpty
&& (myfile = copy_stdin(&stdin_dir)) != 0) {
command = decompressor(which, myfile);
if ((fp = popen(command, "r")) != 0) {
do_file(fp, "stdin");
(void) pclose(fp);
}
free(command);
unlink(myfile);
rmdir(stdin_dir);
} else if (which != dcEmpty)
#endif
do_file(stdin, "stdin");
}
summarize();
#if defined(NO_LEAKS)
while (all_data != 0) {
delink(all_data);
}
#endif
return (EXIT_SUCCESS);
}