@matheusbsbs / matheus-bagliano / commits / 7df7c019c2
Fork 0

Add "--dirstat" for some directory statistics

This adds a new form of overview diffstat output, doing something that I have occasionally ended up doing manually (and badly, because it's actually pretty nasty to do), and that I think is very useful for an project like the kernel that has a fairly deep and well-separated directory structure with semantic meaning. What I mean by that is that it's often interesting to see exactly which sub-directories are impacted by a patch, and to what degree - even if you don't perhaps care so much about the individual files themselves. What makes the concept more interesting is that the "impact" is often hierarchical: in the kernel, for example, something could either have a very localized impact to "fs/ext3/" and then it's interesting to see that such a patch changes mostly that subdirectory, but you could have another patch that changes some generic VFS-layer issue which affects _many_ subdirectories that are all under "fs/", but none - or perhaps just a couple of them - of the individual filesystems are interesting in themselves. So what commonly happens is that you may have big changes in a specific sub-subdirectory, but still also significant separate changes to the subdirectory leading up to that - maybe you have significant VFS-level changes, but *also* changes under that VFS layer in the NFS-specific directories, for example. In that case, you do want the low-level parts that are significant to show up, but then the insignificant ones should show up as under the more generic top-level directory. This patch shows all of that with "--dirstat". The output can be either something simple like commit 81772fe... Author: Thomas Gleixner <tglx@linutronix.de> Date: Sun Feb 10 23:57:36 2008 +0100 x86: remove over noisy debug printk pageattr-test.c contains a noisy debug printk that people reported. The condition under which it prints (randomly tapping into a mem_map[] hole and not being able to c_p_a() there) is valid behavior and not interesting to report. Remove it. Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Acked-by: Ingo Molnar <mingo@elte.hu> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> 100.0% arch/x86/mm/ or something much more complex like commit e231c2e... Author: David Howells <dhowells@redhat.com> Date: Thu Feb 7 00:15:26 2008 -0800 Convert ERR_PTR(PTR_ERR(p)) instances to ERR_CAST(p) 20.5% crypto/ 7.6% fs/afs/ 7.6% fs/fuse/ 7.6% fs/gfs2/ 5.1% fs/jffs2/ 5.1% fs/nfs/ 5.1% fs/nfsd/ 7.6% fs/reiserfs/ 15.3% fs/ 7.6% net/rxrpc/ 10.2% security/keys/ where that latter example is an example of significant work in some individual fs/*/ subdirectories (like the patches to reiserfs accounting for 7.6% of the whole), but then discounting those individual filesystems, there's also 15.3% other "random" things that weren't worth reporting on their oen left over under fs/ in general (either in that directory itself, or in subdirectories of fs/ that didn't have enough changes to be reported individually). I'd like to stress that the "15.3% fs/" mentioned above is the stuff that is under fs/ but that was _not_ significant enough to report on its own. So the above does _not_ mean that 15.3% of the work was under fs/ per se, because that 15.3% does *not* include the already-reported 7.6% of afs, 7.6% of fuse etc. If you want to enable "cumulative" directory statistics, you can use the "--cumulative" flag, which adds up percentages recursively even when they have been already reported for a sub-directory. That cumulative output is disabled if *all* of the changes in one subdirectory come from a deeper subdirectory, to avoid repeating subdirectories all the way to the root. For an example of the cumulative reporting, the above commit becomes commit e231c2e... Author: David Howells <dhowells@redhat.com> Date: Thu Feb 7 00:15:26 2008 -0800 Convert ERR_PTR(PTR_ERR(p)) instances to ERR_CAST(p) 20.5% crypto/ 7.6% fs/afs/ 7.6% fs/fuse/ 7.6% fs/gfs2/ 5.1% fs/jffs2/ 5.1% fs/nfs/ 5.1% fs/nfsd/ 7.6% fs/reiserfs/ 61.5% fs/ 7.6% net/rxrpc/ 10.2% security/keys/ in which the commit percentages now obviously add up to much more than 100%: now the changes that were already reported for the sub-directories under fs/ are then cumulatively included in the whole percentage of fs/ (ie now shows 61.5% as opposed to the 15.3% without the cumulative reporting). The default reporting limit has been arbitrarily set at 3%, which seems to be a pretty good cut-off, but you can specify the cut-off manually by giving it as an option parameter (eg "--dirstat=5" makes the cut-off be at 5% instead) NOTE! The percentages are purely about the total lines added and removed, not anything smarter (or dumber) than that. Also note that you should not generally expect things to add up to 100%: not only does it round down, we don't report leftover scraps (they add up to the top-level change count, but we don't even bother reporting that, it only reports subdirectories). Quite frankly, as a top-level manager this is really convenient for me, but it's going to be very boring for git itself since there are few subdirectories. Also, don't expect things to make tons of sense if you combine this with "-M" and there are cross-directory renames etc. But even for git itself, you can get some fun statistics. Try out git log --dirstat and see the occasional mentions of things like Documentation/, git-gui/, gitweb/ and gitk-git/. Or try out something like git diff --dirstat v1.5.0..v1.5.4 which does kind of git an overview that shows *something*. But in general, the output is more exciting for big projects with deeper structure, and doing a git diff --dirstat v2.6.24..v2.6.25-rc1 on the kernel is what I actually wrote this for! Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Linus Torvalds committed Feb 12, 2008 at 13:26 UTC 7df7c019c2a46672c12a11a45600cdc698e03029
2 files changed +92 -1
diff.c
+89 -1
@@ -990,6 +990,85 @@ static void show_numstat(struct diffstat_t* data, struct diff_options *options)
990 }
991 }
992
993 +struct diffstat_dir {
994 + struct diffstat_file **files;
995 + int nr, percent, cumulative;
996 +};
997 +
998 +static long gather_dirstat(struct diffstat_dir *dir, unsigned long changed, const char *base, int baselen)
999 +{
1000 + unsigned long this_dir = 0;
1001 + unsigned int sources = 0;
1002 +
1003 + while (dir->nr) {
1004 + struct diffstat_file *f = *dir->files;
1005 + int namelen = strlen(f->name);
1006 + unsigned long this;
1007 + char *slash;
1008 +
1009 + if (namelen < baselen)
1010 + break;
1011 + if (memcmp(f->name, base, baselen))
1012 + break;
1013 + slash = strchr(f->name + baselen, '/');
1014 + if (slash) {
1015 + int newbaselen = slash + 1 - f->name;
1016 + this = gather_dirstat(dir, changed, f->name, newbaselen);
1017 + sources++;
1018 + } else {
1019 + this = f->added + f->deleted;
1020 + dir->files++;
1021 + dir->nr--;
1022 + sources += 2;
1023 + }
1024 + this_dir += this;
1025 + }
1026 +
1027 + /*
1028 + * We don't report dirstat's for
1029 + * - the top level
1030 + * - or cases where everything came from a single directory
1031 + * under this directory (sources == 1).
1032 + */
1033 + if (baselen && sources != 1) {
1034 + int permille = this_dir * 1000 / changed;
1035 + if (permille) {
1036 + int percent = permille / 10;
1037 + if (percent >= dir->percent) {
1038 + printf("%4d.%01d%% %.*s\n", percent, permille % 10, baselen, base);
1039 + if (!dir->cumulative)
1040 + return 0;
1041 + }
1042 + }
1043 + }
1044 + return this_dir;
1045 +}
1046 +
1047 +static void show_dirstat(struct diffstat_t *data, struct diff_options *options)
1048 +{
1049 + int i;
1050 + unsigned long changed;
1051 + struct diffstat_dir dir;
1052 +
1053 + /* Calculate total changes */
1054 + changed = 0;
1055 + for (i = 0; i < data->nr; i++) {
1056 + changed += data->files[i]->added;
1057 + changed += data->files[i]->deleted;
1058 + }
1059 +
1060 + /* This can happen even with many files, if everything was renames */
1061 + if (!changed)
1062 + return;
1063 +
1064 + /* Show all directories with more than x% of the changes */
1065 + dir.files = data->files;
1066 + dir.nr = data->nr;
1067 + dir.percent = options->dirstat_percent;
1068 + dir.cumulative = options->output_format & DIFF_FORMAT_CUMULATIVE;
1069 + gather_dirstat(&dir, changed, "", 0);
1070 +}
1071 +
1072 static void free_diffstat_info(struct diffstat_t *diffstat)
1073 {
1074 int i;
@@ -2058,6 +2137,7 @@ void diff_setup(struct diff_options *options)
2137 options->line_termination = '\n';
2138 options->break_opt = -1;
2139 options->rename_limit = -1;
2140 + options->dirstat_percent = 3;
2141 options->context = 3;
2142 options->msg_sep = "";
2143
@@ -2099,6 +2179,7 @@ int diff_setup_done(struct diff_options *options)
2179 DIFF_FORMAT_NUMSTAT |
2180 DIFF_FORMAT_DIFFSTAT |
2181 DIFF_FORMAT_SHORTSTAT |
2182 + DIFF_FORMAT_DIRSTAT |
2183 DIFF_FORMAT_SUMMARY |
2184 DIFF_FORMAT_PATCH);
2185
@@ -2110,6 +2191,7 @@ int diff_setup_done(struct diff_options *options)
2191 DIFF_FORMAT_NUMSTAT |
2192 DIFF_FORMAT_DIFFSTAT |
2193 DIFF_FORMAT_SHORTSTAT |
2194 + DIFF_FORMAT_DIRSTAT |
2195 DIFF_FORMAT_SUMMARY |
2196 DIFF_FORMAT_CHECKDIFF))
2197 DIFF_OPT_SET(options, RECURSIVE);
@@ -2220,6 +2302,10 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
2302 options->output_format |= DIFF_FORMAT_NUMSTAT;
2303 else if (!strcmp(arg, "--shortstat"))
2304 options->output_format |= DIFF_FORMAT_SHORTSTAT;
2305 + else if (opt_arg(arg, 'X', "dirstat", &options->dirstat_percent))
2306 + options->output_format |= DIFF_FORMAT_DIRSTAT;
2307 + else if (!strcmp(arg, "--cumulative"))
2308 + options->output_format |= DIFF_FORMAT_CUMULATIVE;
2309 else if (!strcmp(arg, "--check"))
2310 options->output_format |= DIFF_FORMAT_CHECKDIFF;
2311 else if (!strcmp(arg, "--summary"))
@@ -2938,7 +3024,7 @@ void diff_flush(struct diff_options *options)
3024 separator++;
3025 }
3026
2941 - if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_SHORTSTAT|DIFF_FORMAT_NUMSTAT)) {
3027 + if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_SHORTSTAT|DIFF_FORMAT_NUMSTAT|DIFF_FORMAT_DIRSTAT)) {
3028 struct diffstat_t diffstat;
3029
3030 memset(&diffstat, 0, sizeof(struct diffstat_t));
@@ -2948,6 +3034,8 @@ void diff_flush(struct diff_options *options)
3034 if (check_pair_status(p))
3035 diff_flush_stat(p, options, &diffstat);
3036 }
3037 + if (output_format & DIFF_FORMAT_DIRSTAT)
3038 + show_dirstat(&diffstat, options);
3039 if (output_format & DIFF_FORMAT_NUMSTAT)
3040 show_numstat(&diffstat, options);
3041 if (output_format & DIFF_FORMAT_DIFFSTAT)
diff.h
+3
@@ -30,6 +30,8 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,
30 #define DIFF_FORMAT_SUMMARY 0x0008
31 #define DIFF_FORMAT_PATCH 0x0010
32 #define DIFF_FORMAT_SHORTSTAT 0x0020
33 +#define DIFF_FORMAT_DIRSTAT 0x0040
34 +#define DIFF_FORMAT_CUMULATIVE 0x0080
35
36 /* These override all above */
37 #define DIFF_FORMAT_NAME 0x0100
@@ -80,6 +82,7 @@ struct diff_options {
82 int pickaxe_opts;
83 int rename_score;
84 int rename_limit;
85 + int dirstat_percent;
86 int setup;
87 int abbrev;
88 const char *msg_sep;