diff --git a/diff.c b/diff.c index cd8bc4dcc32757dfed9374fa8769599a6a7857ad..dd374d436f0220acb1a42384c047480279e25dde 100644 --- a/diff.c +++ b/diff.c @@ -990,6 +990,85 @@ static void show_numstat(struct diffstat_t* data, struct diff_options *options) } } +struct diffstat_dir { + struct diffstat_file **files; + int nr, percent, cumulative; +}; + +static long gather_dirstat(struct diffstat_dir *dir, unsigned long changed, const char *base, int baselen) +{ + unsigned long this_dir = 0; + unsigned int sources = 0; + + while (dir->nr) { + struct diffstat_file *f = *dir->files; + int namelen = strlen(f->name); + unsigned long this; + char *slash; + + if (namelen < baselen) + break; + if (memcmp(f->name, base, baselen)) + break; + slash = strchr(f->name + baselen, '/'); + if (slash) { + int newbaselen = slash + 1 - f->name; + this = gather_dirstat(dir, changed, f->name, newbaselen); + sources++; + } else { + this = f->added + f->deleted; + dir->files++; + dir->nr--; + sources += 2; + } + this_dir += this; + } + + /* + * We don't report dirstat's for + * - the top level + * - or cases where everything came from a single directory + * under this directory (sources == 1). + */ + if (baselen && sources != 1) { + int permille = this_dir * 1000 / changed; + if (permille) { + int percent = permille / 10; + if (percent >= dir->percent) { + printf("%4d.%01d%% %.*s\n", percent, permille % 10, baselen, base); + if (!dir->cumulative) + return 0; + } + } + } + return this_dir; +} + +static void show_dirstat(struct diffstat_t *data, struct diff_options *options) +{ + int i; + unsigned long changed; + struct diffstat_dir dir; + + /* Calculate total changes */ + changed = 0; + for (i = 0; i < data->nr; i++) { + changed += data->files[i]->added; + changed += data->files[i]->deleted; + } + + /* This can happen even with many files, if everything was renames */ + if (!changed) + return; + + /* Show all directories with more than x% of the changes */ + dir.files = data->files; + dir.nr = data->nr; + dir.percent = options->dirstat_percent; + dir.cumulative = options->output_format & DIFF_FORMAT_CUMULATIVE; + gather_dirstat(&dir, changed, "", 0); +} + static void free_diffstat_info(struct diffstat_t *diffstat) { int i; @@ -2058,6 +2137,7 @@ void diff_setup(struct diff_options *options) options->line_termination = '\n'; options->break_opt = -1; options->rename_limit = -1; + options->dirstat_percent = 3; options->context = 3; options->msg_sep = ""; @@ -2099,6 +2179,7 @@ int diff_setup_done(struct diff_options *options) DIFF_FORMAT_NUMSTAT | DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_SHORTSTAT | + DIFF_FORMAT_DIRSTAT | DIFF_FORMAT_SUMMARY | DIFF_FORMAT_PATCH); @@ -2110,6 +2191,7 @@ int diff_setup_done(struct diff_options *options) DIFF_FORMAT_NUMSTAT | DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_SHORTSTAT | + DIFF_FORMAT_DIRSTAT | DIFF_FORMAT_SUMMARY | DIFF_FORMAT_CHECKDIFF)) DIFF_OPT_SET(options, RECURSIVE); @@ -2220,6 +2302,10 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) options->output_format |= DIFF_FORMAT_NUMSTAT; else if (!strcmp(arg, "--shortstat")) options->output_format |= DIFF_FORMAT_SHORTSTAT; + else if (opt_arg(arg, 'X', "dirstat", &options->dirstat_percent)) + options->output_format |= DIFF_FORMAT_DIRSTAT; + else if (!strcmp(arg, "--cumulative")) + options->output_format |= DIFF_FORMAT_CUMULATIVE; else if (!strcmp(arg, "--check")) options->output_format |= DIFF_FORMAT_CHECKDIFF; else if (!strcmp(arg, "--summary")) @@ -2938,7 +3024,7 @@ void diff_flush(struct diff_options *options) separator++; } - if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_SHORTSTAT|DIFF_FORMAT_NUMSTAT)) { + if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_SHORTSTAT|DIFF_FORMAT_NUMSTAT|DIFF_FORMAT_DIRSTAT)) { struct diffstat_t diffstat; memset(&diffstat, 0, sizeof(struct diffstat_t)); @@ -2948,6 +3034,8 @@ void diff_flush(struct diff_options *options) if (check_pair_status(p)) diff_flush_stat(p, options, &diffstat); } + if (output_format & DIFF_FORMAT_DIRSTAT) + show_dirstat(&diffstat, options); if (output_format & DIFF_FORMAT_NUMSTAT) show_numstat(&diffstat, options); if (output_format & DIFF_FORMAT_DIFFSTAT) diff --git a/diff.h b/diff.h index 073d5cbf1ba97acf527770d912870cda01195489..8c6bb54119507633b5823757d7485925b346a07b 100644 --- a/diff.h +++ b/diff.h @@ -30,6 +30,8 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q, #define DIFF_FORMAT_SUMMARY 0x0008 #define DIFF_FORMAT_PATCH 0x0010 #define DIFF_FORMAT_SHORTSTAT 0x0020 +#define DIFF_FORMAT_DIRSTAT 0x0040 +#define DIFF_FORMAT_CUMULATIVE 0x0080 /* These override all above */ #define DIFF_FORMAT_NAME 0x0100 @@ -80,6 +82,7 @@ struct diff_options { int pickaxe_opts; int rename_score; int rename_limit; + int dirstat_percent; int setup; int abbrev; const char *msg_sep;