Skip to content

Commit d75f795

Browse files
dschoJunio C Hamano
authored andcommitted
diff-options: add --stat (take 2)
Now, you can say "git diff --stat" (to get an idea how many changes are uncommitted), or "git log --stat". Signed-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de> Signed-off-by: Junio C Hamano <junkio@cox.net>
1 parent f327dbc commit d75f795

File tree

4 files changed

+226
-7
lines changed

4 files changed

+226
-7
lines changed

Documentation/diff-options.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
--patch-with-raw::
88
Generate patch but keep also the default raw diff output.
99

10+
--stat::
11+
Generate a diffstat instead of a patch.
12+
1013
-z::
1114
\0 line termination on output
1215

diff.c

Lines changed: 217 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
#include "quote.h"
99
#include "diff.h"
1010
#include "diffcore.h"
11-
#include "xdiff/xdiff.h"
11+
#include "xdiff-interface.h"
1212

1313
static int use_size_cache;
1414

@@ -195,6 +195,137 @@ static int fn_out(void *priv, mmbuffer_t *mb, int nbuf)
195195
return 0;
196196
}
197197

198+
struct diffstat_t {
199+
struct xdiff_emit_state xm;
200+
201+
int nr;
202+
int alloc;
203+
struct diffstat_file {
204+
char *name;
205+
unsigned int added, deleted;
206+
} **files;
207+
};
208+
209+
static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat,
210+
const char *name)
211+
{
212+
struct diffstat_file *x;
213+
x = xcalloc(sizeof (*x), 1);
214+
if (diffstat->nr == diffstat->alloc) {
215+
diffstat->alloc = alloc_nr(diffstat->alloc);
216+
diffstat->files = xrealloc(diffstat->files,
217+
diffstat->alloc * sizeof(x));
218+
}
219+
diffstat->files[diffstat->nr++] = x;
220+
x->name = strdup(name);
221+
return x;
222+
}
223+
224+
static void diffstat_consume(void *priv, char *line, unsigned long len)
225+
{
226+
struct diffstat_t *diffstat = priv;
227+
struct diffstat_file *x = diffstat->files[diffstat->nr - 1];
228+
229+
if (line[0] == '+')
230+
x->added++;
231+
else if (line[0] == '-')
232+
x->deleted++;
233+
}
234+
235+
static const char pluses[] = "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++";
236+
static const char minuses[]= "----------------------------------------------------------------------";
237+
238+
static void show_stats(struct diffstat_t* data)
239+
{
240+
char *prefix = "";
241+
int i, len, add, del, total, adds = 0, dels = 0;
242+
int max, max_change = 0, max_len = 0;
243+
int total_files = data->nr;
244+
245+
if (data->nr == 0)
246+
return;
247+
248+
printf("---\n");
249+
250+
for (i = 0; i < data->nr; i++) {
251+
struct diffstat_file *file = data->files[i];
252+
253+
if (max_change < file->added + file->deleted)
254+
max_change = file->added + file->deleted;
255+
len = strlen(file->name);
256+
if (max_len < len)
257+
max_len = len;
258+
}
259+
260+
for (i = 0; i < data->nr; i++) {
261+
char *name = data->files[i]->name;
262+
int added = data->files[i]->added;
263+
int deleted = data->files[i]->deleted;
264+
265+
if (0 < (len = quote_c_style(name, NULL, NULL, 0))) {
266+
char *qname = xmalloc(len + 1);
267+
quote_c_style(name, qname, NULL, 0);
268+
free(name);
269+
name = qname;
270+
}
271+
272+
/*
273+
* "scale" the filename
274+
*/
275+
len = strlen(name);
276+
max = max_len;
277+
if (max > 50)
278+
max = 50;
279+
if (len > max) {
280+
char *slash;
281+
prefix = "...";
282+
max -= 3;
283+
name += len - max;
284+
slash = strchr(name, '/');
285+
if (slash)
286+
name = slash;
287+
}
288+
len = max;
289+
290+
/*
291+
* scale the add/delete
292+
*/
293+
max = max_change;
294+
if (max + len > 70)
295+
max = 70 - len;
296+
297+
if (added < 0) {
298+
/* binary file */
299+
printf(" %s%-*s | Bin\n", prefix, len, name);
300+
continue;
301+
} else if (added + deleted == 0) {
302+
total_files--;
303+
continue;
304+
}
305+
306+
add = added;
307+
del = deleted;
308+
total = add + del;
309+
adds += add;
310+
dels += del;
311+
312+
if (max_change > 0) {
313+
total = (total * max + max_change / 2) / max_change;
314+
add = (add * max + max_change / 2) / max_change;
315+
del = total - add;
316+
}
317+
/* TODO: binary */
318+
printf(" %s%-*s |%5d %.*s%.*s\n", prefix,
319+
len, name, added + deleted,
320+
add, pluses, del, minuses);
321+
free(name);
322+
free(data->files[i]);
323+
}
324+
free(data->files);
325+
printf(" %d files changed, %d insertions(+), %d deletions(-)\n",
326+
total_files, adds, dels);
327+
}
328+
198329
#define FIRST_FEW_BYTES 8000
199330
static int mmfile_is_binary(mmfile_t *mf)
200331
{
@@ -286,6 +417,35 @@ static void builtin_diff(const char *name_a,
286417
return;
287418
}
288419

420+
static void builtin_diffstat(const char *name_a, const char *name_b,
421+
struct diff_filespec *one, struct diff_filespec *two,
422+
struct diffstat_t *diffstat)
423+
{
424+
mmfile_t mf1, mf2;
425+
struct diffstat_file *data;
426+
427+
data = diffstat_add(diffstat, name_a ? name_a : name_b);
428+
429+
if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
430+
die("unable to read files to diff");
431+
432+
if (mmfile_is_binary(&mf1) || mmfile_is_binary(&mf2))
433+
data->added = -1;
434+
else {
435+
/* Crazy xdl interfaces.. */
436+
xpparam_t xpp;
437+
xdemitconf_t xecfg;
438+
xdemitcb_t ecb;
439+
440+
xpp.flags = XDF_NEED_MINIMAL;
441+
xecfg.ctxlen = 3;
442+
xecfg.flags = XDL_EMIT_FUNCNAMES;
443+
ecb.outf = xdiff_outf;
444+
ecb.priv = diffstat;
445+
xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
446+
}
447+
}
448+
289449
struct diff_filespec *alloc_filespec(const char *path)
290450
{
291451
int namelen = strlen(path);
@@ -819,6 +979,27 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o)
819979
free(other_munged);
820980
}
821981

982+
static void run_diffstat(struct diff_filepair *p, struct diff_options *o,
983+
struct diffstat_t *diffstat)
984+
{
985+
const char *name;
986+
const char *other;
987+
988+
if (DIFF_PAIR_UNMERGED(p)) {
989+
/* unmerged */
990+
builtin_diffstat(p->one->path, NULL, NULL, NULL, diffstat);
991+
return;
992+
}
993+
994+
name = p->one->path;
995+
other = (strcmp(name, p->two->path) ? p->two->path : NULL);
996+
997+
diff_fill_sha1_info(p->one);
998+
diff_fill_sha1_info(p->two);
999+
1000+
builtin_diffstat(name, other, p->one, p->two, diffstat);
1001+
}
1002+
8221003
void diff_setup(struct diff_options *options)
8231004
{
8241005
memset(options, 0, sizeof(*options));
@@ -866,6 +1047,8 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
8661047
options->output_format = DIFF_FORMAT_PATCH;
8671048
options->with_raw = 1;
8681049
}
1050+
else if (!strcmp(arg, "--stat"))
1051+
options->output_format = DIFF_FORMAT_DIFFSTAT;
8691052
else if (!strcmp(arg, "-z"))
8701053
options->line_termination = 0;
8711054
else if (!strncmp(arg, "-l", 2))
@@ -1160,11 +1343,24 @@ static void diff_flush_patch(struct diff_filepair *p, struct diff_options *o)
11601343

11611344
if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) ||
11621345
(DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode)))
1163-
return; /* no tree diffs in patch format */
1346+
return; /* no tree diffs in patch format */
11641347

11651348
run_diff(p, o);
11661349
}
11671350

1351+
static void diff_flush_stat(struct diff_filepair *p, struct diff_options *o,
1352+
struct diffstat_t *diffstat)
1353+
{
1354+
if (diff_unmodified_pair(p))
1355+
return;
1356+
1357+
if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) ||
1358+
(DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode)))
1359+
return; /* no tree diffs in patch format */
1360+
1361+
run_diffstat(p, o, diffstat);
1362+
}
1363+
11681364
int diff_queue_is_empty(void)
11691365
{
11701366
struct diff_queue_struct *q = &diff_queued_diff;
@@ -1276,7 +1472,8 @@ static void diff_resolve_rename_copy(void)
12761472

12771473
static void flush_one_pair(struct diff_filepair *p,
12781474
int diff_output_format,
1279-
struct diff_options *options)
1475+
struct diff_options *options,
1476+
struct diffstat_t *diffstat)
12801477
{
12811478
int inter_name_termination = '\t';
12821479
int line_termination = options->line_termination;
@@ -1291,6 +1488,9 @@ static void flush_one_pair(struct diff_filepair *p,
12911488
break;
12921489
default:
12931490
switch (diff_output_format) {
1491+
case DIFF_FORMAT_DIFFSTAT:
1492+
diff_flush_stat(p, options, diffstat);
1493+
break;
12941494
case DIFF_FORMAT_PATCH:
12951495
diff_flush_patch(p, options);
12961496
break;
@@ -1316,19 +1516,31 @@ void diff_flush(struct diff_options *options)
13161516
struct diff_queue_struct *q = &diff_queued_diff;
13171517
int i;
13181518
int diff_output_format = options->output_format;
1519+
struct diffstat_t *diffstat = NULL;
1520+
1521+
if (diff_output_format == DIFF_FORMAT_DIFFSTAT) {
1522+
diffstat = xcalloc(sizeof (struct diffstat_t), 1);
1523+
diffstat->xm.consume = diffstat_consume;
1524+
}
13191525

13201526
if (options->with_raw) {
13211527
for (i = 0; i < q->nr; i++) {
13221528
struct diff_filepair *p = q->queue[i];
1323-
flush_one_pair(p, DIFF_FORMAT_RAW, options);
1529+
flush_one_pair(p, DIFF_FORMAT_RAW, options, NULL);
13241530
}
13251531
putchar(options->line_termination);
13261532
}
13271533
for (i = 0; i < q->nr; i++) {
13281534
struct diff_filepair *p = q->queue[i];
1329-
flush_one_pair(p, diff_output_format, options);
1535+
flush_one_pair(p, diff_output_format, options, diffstat);
13301536
diff_free_filepair(p);
13311537
}
1538+
1539+
if (diffstat) {
1540+
show_stats(diffstat);
1541+
free(diffstat);
1542+
}
1543+
13321544
free(q->queue);
13331545
q->queue = NULL;
13341546
q->nr = q->alloc = 0;

diff.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ extern void diffcore_std_no_resolve(struct diff_options *);
119119
" -u synonym for -p.\n" \
120120
" --patch-with-raw\n" \
121121
" output both a patch and the diff-raw format.\n" \
122+
" --stat show diffstat instead of patch.\n" \
122123
" --name-only show only names of changed files.\n" \
123124
" --name-status show names and status of changed files.\n" \
124125
" --full-index show full object name on index lines.\n" \
@@ -142,6 +143,7 @@ extern int diff_queue_is_empty(void);
142143
#define DIFF_FORMAT_NO_OUTPUT 3
143144
#define DIFF_FORMAT_NAME 4
144145
#define DIFF_FORMAT_NAME_STATUS 5
146+
#define DIFF_FORMAT_DIFFSTAT 6
145147

146148
extern void diff_flush(struct diff_options*);
147149

git-diff.sh

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,11 @@ case " $flags " in
3030
cc_or_p=--cc ;;
3131
esac
3232

33-
# If we do not have --name-status, --name-only, -r, or -c default to --cc.
33+
# If we do not have --name-status, --name-only, -r, -c or --stat,
34+
# default to --cc.
3435
case " $flags " in
35-
*" '--name-status' "* | *" '--name-only' "* | *" '-r' "* | *" '-c' "* )
36+
*" '--name-status' "* | *" '--name-only' "* | *" '-r' "* | *" '-c' "* | \
37+
*" '--stat' "*)
3638
;;
3739
*)
3840
flags="$flags'$cc_or_p' " ;;

0 commit comments

Comments
 (0)