From: Mark Jamsek Subject: Re: got: add diffstat to got diffg To: Game of Trees Date: Mon, 9 Jan 2023 22:38:37 +1100 On 23-01-09 07:21PM, Mark Jamsek wrote: > On 23-01-08 06:01PM, Stefan Sperling wrote: > > On Mon, Jan 09, 2023 at 03:11:01AM +1100, Mark Jamsek wrote: > > > Like the recent 'got log -d' addition, display a diffstat if the -d > > > switch is passed to 'got diff'. > > > > > > > But I am also wondering whether it would make sense to keep showing the > > full diff even with -d, such that we display a diffstat above the full > > diff, like we in tog diff. To me it seems like a diffstat is rarely > > useful in isolation, but maybe I am missing some use case? > > You've given me something to think about. I'm heading off for dinner and > will consider it further. But I think you're right and 'got diff -d' > should always output the diff too. Leave it with me and I'll work on > this when I get back. Thanks, Stefan! As mentioned on irc, you convinced me we should always display the diff with -d, so the below diff makes this change from the previous revision. We now output the diffstat first, which is followed by the diff. One positive result is that, unlike tog and got log, we only compute the diff once, so this improves performance a little. We also now need to use a temp file, though, because we want the diffstat output first, but we need the entire diff to get our field widths and totals. As such, we can't print to stdout as we build the diff like we previously did. But this isn't a big deal. Sorry for the large diff; if you prefer, I can send a diff of changes from the previous as it's a bit smaller. Just let me know! diffstat refs/remotes/origin/main d2a6fcbdbf7aacd6f9e0e0c52a57cfe58ee786ca M got/got.1 | 5+ 1- M got/got.c | 160+ 38- M include/got_diff.h | 13+ 7- M lib/diff.c | 179+ 69- M lib/worktree.c | 3+ 3- M tog/tog.c | 4+ 3- 6 files changed, 364 insertions(+), 121 deletions(-) diff refs/remotes/origin/main d2a6fcbdbf7aacd6f9e0e0c52a57cfe58ee786ca commit - 243a8f150f5f79c7530b415ca9df09bdbebd0cf9 commit + d2a6fcbdbf7aacd6f9e0e0c52a57cfe58ee786ca blob - c86779cca9d917f301a8ac66e723282c6c0bcb57 blob + 3014446b87c1bbbc0c5271ec13deecb93f5ccc0c --- got/got.1 +++ got/got.1 @@ -904,7 +904,7 @@ is never traversed. .Tg di .It Xo .Cm diff -.Op Fl aPsw +.Op Fl adPsw .Op Fl C Ar number .Op Fl c Ar commit .Op Fl r Ar repository-path @@ -962,6 +962,10 @@ option. Cannot be used together with the .Fl P option. +.It Fl d +Display diffstat of changes before the actual diff by annotating each file path +or blob hash being diffed with the total number of lines added and removed. +A summary line will display the total number of changes across all files. .It Fl P Interpret all arguments as paths only. This option can be used to resolve ambiguity in cases where paths blob - 9a89d5b62f1635fb4b385534abdd217ee4a06611 blob + 35eaf23bb157cf65a7f6f79c315fd2f1b9206e5d --- got/got.c +++ got/got.c @@ -3624,7 +3624,8 @@ diff_blobs(struct got_object_id *blob_id1, struct got_ static const struct got_error * diff_blobs(struct got_object_id *blob_id1, struct got_object_id *blob_id2, const char *path, int diff_context, int ignore_whitespace, - int force_text_diff, struct got_repository *repo, FILE *outfile) + int force_text_diff, int show_diffstat, struct got_repository *repo, + FILE *outfile) { const struct got_error *err = NULL; struct got_blob_object *blob1 = NULL, *blob2 = NULL; @@ -3666,7 +3667,7 @@ diff_blobs(struct got_object_id *blob_id1, struct got_ path++; err = got_diff_blob(NULL, NULL, blob1, blob2, f1, f2, path, path, GOT_DIFF_ALGORITHM_PATIENCE, diff_context, ignore_whitespace, - force_text_diff, outfile); + force_text_diff, show_diffstat, NULL, outfile); done: if (fd1 != -1 && close(fd1) == -1 && err == NULL) err = got_error_from_errno("close"); @@ -3727,6 +3728,8 @@ diff_trees(struct got_object_id *tree_id1, struct got_ arg.diff_context = diff_context; arg.ignore_whitespace = ignore_whitespace; arg.force_text_diff = force_text_diff; + arg.show_diffstat = 0; + arg.diffstat = NULL; arg.diff_algo = GOT_DIFF_ALGORITHM_PATIENCE; arg.outfile = outfile; arg.lines = NULL; @@ -3891,7 +3894,7 @@ print_patch(struct got_commit_object *commit, struct g switch (obj_type) { case GOT_OBJ_TYPE_BLOB: err = diff_blobs(obj_id1, obj_id2, path, diff_context, - 0, 0, repo, outfile); + 0, 0, 0, repo, outfile); break; case GOT_OBJ_TYPE_TREE: err = diff_trees(obj_id1, obj_id2, path, diff_context, @@ -4164,6 +4167,31 @@ print_commit(struct got_commit_object *commit, struct } static const struct got_error * +print_diffstat(struct got_diffstat_cb_arg *dsa, struct got_pathlist_head *paths, + const char *header) +{ + struct got_pathlist_entry *pe; + + if (header != NULL) + printf("%s\n", header); + + TAILQ_FOREACH(pe, paths, entry) { + struct got_diff_changed_path *cp = pe->data; + int pad = dsa->max_path_len - pe->path_len + 1; + + printf(" %c %s%*c | %*d+ %*d-\n", cp->status, pe->path, pad, + ' ', dsa->add_cols + 1, cp->add, dsa->rm_cols + 1, cp->rm); + } + printf("\n%d file%s changed, %d insertions(+), %d deletions(-)\n\n", + dsa->nfiles, dsa->nfiles > 1 ? "s" : "", dsa->ins, dsa->del); + + if (fflush(stdout) != 0) + return got_error_from_errno("fflush"); + + return NULL; +} + +static const struct got_error * print_commit(struct got_commit_object *commit, struct got_object_id *id, struct got_repository *repo, const char *path, struct got_pathlist_head *changed_paths, struct got_diffstat_cb_arg *dsa, @@ -4237,31 +4265,18 @@ print_commit(struct got_commit_object *commit, struct } while (line); free(logmsg0); - if (changed_paths) { + if (dsa && changed_paths) { + err = print_diffstat(dsa, changed_paths, NULL); + if (err) + goto done; + } else if (changed_paths) { struct got_pathlist_entry *pe; TAILQ_FOREACH(pe, changed_paths, entry) { struct got_diff_changed_path *cp = pe->data; - char *stat = NULL; - if (dsa) { - int pad = dsa->max_path_len - pe->path_len + 1; - - if (asprintf(&stat, "%*c | %*d+ %*d-", - pad, ' ', dsa->add_cols + 1, cp->add, - dsa->rm_cols + 1, cp->rm) == -1) { - err = got_error_from_errno("asprintf"); - goto done; - } - } - printf(" %c %s%s\n", cp->status, pe->path, - stat ? stat : ""); - free(stat); + printf(" %c %s\n", cp->status, pe->path); } - if (dsa) - printf("\n%d file%s changed, %d insertions(+), " - "%d deletions(-)\n", dsa->nfiles, - dsa->nfiles > 1 ? "s" : "", dsa->ins, dsa->del); printf("\n"); } if (show_patch) { @@ -4727,7 +4742,7 @@ usage_diff(void) __dead static void usage_diff(void) { - fprintf(stderr, "usage: %s diff [-aPsw] [-C number] [-c commit] " + fprintf(stderr, "usage: %s diff [-adPsw] [-C number] [-c commit] " "[-r repository-path] [object1 object2 | path ...]\n", getprogname()); exit(1); @@ -4736,6 +4751,7 @@ struct print_diff_arg { struct print_diff_arg { struct got_repository *repo; struct got_worktree *worktree; + struct got_diffstat_cb_arg *diffstat; int diff_context; const char *id_str; int header_shown; @@ -4743,8 +4759,10 @@ struct print_diff_arg { enum got_diff_algorithm diff_algo; int ignore_whitespace; int force_text_diff; + int show_diffstat; FILE *f1; FILE *f2; + FILE *outfile; }; /* @@ -4836,12 +4854,22 @@ print_diff(void *arg, unsigned char status, unsigned c return got_error_from_errno("got_opentemp_truncate"); if (!a->header_shown) { - printf("diff %s%s\n", a->diff_staged ? "-s " : "", - got_worktree_get_root_path(a->worktree)); - printf("commit - %s\n", a->id_str); - printf("path + %s%s\n", + if (fprintf(a->outfile, "diff %s%s\n", + a->diff_staged ? "-s " : "", + got_worktree_get_root_path(a->worktree)) < 0) { + err = got_error_from_errno("fprintf"); + goto done; + } + if (fprintf(a->outfile, "commit - %s\n", a->id_str) < 0) { + err = got_error_from_errno("fprintf"); + goto done; + } + if (fprintf(a->outfile, "path + %s%s\n", got_worktree_get_root_path(a->worktree), - a->diff_staged ? " (staged changes)" : ""); + a->diff_staged ? " (staged changes)" : "") < 0) { + err = got_error_from_errno("fprintf"); + goto done; + } a->header_shown = 1; } @@ -4874,7 +4902,8 @@ print_diff(void *arg, unsigned char status, unsigned c err = got_diff_objects_as_blobs(NULL, NULL, a->f1, a->f2, fd1, fd2, blob_id, staged_blob_id, label1, label2, a->diff_algo, a->diff_context, a->ignore_whitespace, - a->force_text_diff, a->repo, stdout); + a->force_text_diff, a->show_diffstat, a->diffstat, a->repo, + a->outfile); goto done; } @@ -4964,7 +4993,8 @@ print_diff(void *arg, unsigned char status, unsigned c err = got_diff_blob_file(blob1, a->f1, size1, label1, f2 ? f2 : a->f2, f2_exists, &sb, path, GOT_DIFF_ALGORITHM_PATIENCE, a->diff_context, - a->ignore_whitespace, a->force_text_diff, stdout); + a->ignore_whitespace, a->force_text_diff, a->show_diffstat, + a->diffstat, a->outfile); done: if (fd1 != -1 && close(fd1) == -1 && err == NULL) err = got_error_from_errno("close"); @@ -4981,6 +5011,30 @@ cmd_diff(int argc, char *argv[]) } static const struct got_error * +printfile(FILE *f) +{ + char buf[8192]; + size_t r; + + if (fseeko(f, 0L, SEEK_SET) == -1) + return got_error_from_errno("fseek"); + + for (;;) { + r = fread(buf, 1, sizeof(buf), f); + if (r == 0) { + if (ferror(f)) + return got_error_from_errno("fread"); + if (feof(f)) + break; + } + if (fwrite(buf, 1, r, stdout) != r) + return got_ferror(stdout, GOT_ERR_IO); + } + + return NULL; +} + +static const struct got_error * cmd_diff(int argc, char *argv[]) { const struct got_error *error; @@ -4993,17 +5047,19 @@ cmd_diff(int argc, char *argv[]) char *labels[2] = { NULL, NULL }; int type1 = GOT_OBJ_TYPE_ANY, type2 = GOT_OBJ_TYPE_ANY; int diff_context = 3, diff_staged = 0, ignore_whitespace = 0, ch, i; - int force_text_diff = 0, force_path = 0, rflag = 0; + int force_text_diff = 0, force_path = 0, rflag = 0, show_diffstat = 0; const char *errstr; struct got_reflist_head refs; - struct got_pathlist_head paths; + struct got_pathlist_head diffstat_paths, paths; struct got_pathlist_entry *pe; - FILE *f1 = NULL, *f2 = NULL; + FILE *f1 = NULL, *f2 = NULL, *outfile = NULL; int fd1 = -1, fd2 = -1; int *pack_fds = NULL; + struct got_diffstat_cb_arg dsa; TAILQ_INIT(&refs); TAILQ_INIT(&paths); + TAILQ_INIT(&diffstat_paths); #ifndef PROFILE if (pledge("stdio rpath wpath cpath flock proc exec sendfd unveil", @@ -5011,7 +5067,7 @@ cmd_diff(int argc, char *argv[]) err(1, "pledge"); #endif - while ((ch = getopt(argc, argv, "aC:c:Pr:sw")) != -1) { + while ((ch = getopt(argc, argv, "aC:c:dPr:sw")) != -1) { switch (ch) { case 'a': force_text_diff = 1; @@ -5028,6 +5084,9 @@ cmd_diff(int argc, char *argv[]) errx(1, "too many -c options used"); commit_args[ncommit_args++] = optarg; break; + case 'd': + show_diffstat = 1; + break; case 'P': force_path = 1; break; @@ -5091,6 +5150,14 @@ cmd_diff(int argc, char *argv[]) if (error != NULL) goto done; + if (show_diffstat) { + memset(&dsa, 0, sizeof(dsa)); + dsa.paths = &diffstat_paths; + dsa.force_text = force_text_diff; + dsa.ignore_ws = ignore_whitespace; + dsa.diff_algo = GOT_DIFF_ALGORITHM_PATIENCE; + } + if (rflag || worktree == NULL || ncommit_args > 0) { if (force_path) { error = got_error_msg(GOT_ERR_NOT_IMPL, @@ -5150,6 +5217,12 @@ cmd_diff(int argc, char *argv[]) goto done; } + outfile = got_opentemp(); + if (outfile == NULL) { + error = got_error_from_errno("got_opentemp"); + goto done; + } + if (ncommit_args == 0 && (ids[0] == NULL || ids[1] == NULL)) { struct print_diff_arg arg; char *id_str; @@ -5189,12 +5262,33 @@ cmd_diff(int argc, char *argv[]) arg.diff_staged = diff_staged; arg.ignore_whitespace = ignore_whitespace; arg.force_text_diff = force_text_diff; + arg.show_diffstat = show_diffstat; + arg.diffstat = &dsa; arg.f1 = f1; arg.f2 = f2; + arg.outfile = outfile; error = got_worktree_status(worktree, &paths, repo, 0, print_diff, &arg, check_cancelled, NULL); free(id_str); + if (error) + goto done; + + if (show_diffstat && dsa.nfiles > 0) { + char *header; + + if (asprintf(&header, "diffstat %s%s", + diff_staged ? "-s " : "", + got_worktree_get_root_path(worktree)) == -1) + goto done; + + error = print_diffstat(&dsa, &diffstat_paths, header); + free(header); + if (error) + goto done; + } + + error = printfile(outfile); goto done; } @@ -5329,24 +5423,45 @@ cmd_diff(int argc, char *argv[]) error = got_diff_objects_as_blobs(NULL, NULL, f1, f2, fd1, fd2, ids[0], ids[1], NULL, NULL, GOT_DIFF_ALGORITHM_PATIENCE, diff_context, - ignore_whitespace, force_text_diff, repo, stdout); + ignore_whitespace, force_text_diff, show_diffstat, + show_diffstat ? &dsa : NULL, repo, outfile); break; case GOT_OBJ_TYPE_TREE: error = got_diff_objects_as_trees(NULL, NULL, f1, f2, fd1, fd2, ids[0], ids[1], &paths, "", "", GOT_DIFF_ALGORITHM_PATIENCE, diff_context, - ignore_whitespace, force_text_diff, repo, stdout); + ignore_whitespace, force_text_diff, show_diffstat, + show_diffstat ? &dsa : NULL, repo, outfile); break; case GOT_OBJ_TYPE_COMMIT: - printf("diff %s %s\n", labels[0], labels[1]); + fprintf(outfile, "diff %s %s\n", labels[0], labels[1]); error = got_diff_objects_as_commits(NULL, NULL, f1, f2, fd1, fd2, ids[0], ids[1], &paths, GOT_DIFF_ALGORITHM_PATIENCE, diff_context, - ignore_whitespace, force_text_diff, repo, stdout); + ignore_whitespace, force_text_diff, show_diffstat, + show_diffstat ? &dsa : NULL, repo, outfile); break; default: error = got_error(GOT_ERR_OBJ_TYPE); } + if (error) + goto done; + + if (show_diffstat && dsa.nfiles > 0) { + char *header = NULL; + + if (asprintf(&header, "diffstat %s %s", + labels[0], labels[1]) == -1) + goto done; + + error = print_diffstat(&dsa, &diffstat_paths, header); + free(header); + if (error) + goto done; + } + + error = printfile(outfile); + done: free(labels[0]); free(labels[1]); @@ -5368,7 +5483,14 @@ done: TAILQ_FOREACH(pe, &paths, entry) free((char *)pe->path); got_pathlist_free(&paths); + TAILQ_FOREACH(pe, &diffstat_paths, entry) { + free((char *)pe->path); + free(pe->data); + } + got_pathlist_free(&diffstat_paths); got_ref_list_free(&refs); + if (outfile && fclose(outfile) == EOF && error == NULL) + error = got_error_from_errno("fclose"); if (f1 && fclose(f1) == EOF && error == NULL) error = got_error_from_errno("fclose"); if (f2 && fclose(f2) == EOF && error == NULL) blob - 18fe6a19ccc9b5b2450e313f81d9246ce51b630d blob + 1d85a74b4cff17c1a1603795bdddac204025f5fc --- include/got_diff.h +++ include/got_diff.h @@ -44,6 +44,7 @@ struct got_diff_line { uint8_t type; }; +struct got_diffstat_cb_arg; /* * Compute the differences between two blobs and write unified diff text * to the provided output file. Two open temporary files must be provided @@ -61,8 +62,8 @@ const struct got_error *got_diff_blob(struct got_diff_ */ const struct got_error *got_diff_blob(struct got_diff_line **, size_t *, struct got_blob_object *, struct got_blob_object *, FILE *, FILE *, - const char *, const char *, enum got_diff_algorithm, int, int, int, - FILE *); + const char *, const char *, enum got_diff_algorithm, int, int, int, int, + struct got_diffstat_cb_arg *, FILE *); /* * Compute the differences between a blob and a file and write unified diff @@ -75,7 +76,8 @@ const struct got_error *got_diff_blob_file(struct got_ */ const struct got_error *got_diff_blob_file(struct got_blob_object *, FILE *, off_t, const char *, FILE *, int, struct stat *, const char *, - enum got_diff_algorithm, int, int, int, FILE *); + enum got_diff_algorithm, int, int, int, int, struct got_diffstat_cb_arg *, + FILE *); /* * A callback function invoked to handle the differences between two blobs @@ -105,6 +107,8 @@ struct got_diff_blob_output_unidiff_arg { int diff_context; /* Sets the number of context lines. */ int ignore_whitespace; /* Ignore whitespace differences. */ int force_text_diff; /* Assume text even if binary data detected. */ + int show_diffstat; /* Compute diffstat of changes */ + struct got_diffstat_cb_arg *diffstat; enum got_diff_algorithm diff_algo; /* Diffing algorithm to use. */ /* @@ -204,7 +208,8 @@ const struct got_error *got_diff_objects_as_blobs(stru const struct got_error *got_diff_objects_as_blobs(struct got_diff_line **, size_t *, FILE *, FILE *, int, int, struct got_object_id *, struct got_object_id *, const char *, const char *, enum got_diff_algorithm, - int, int, int, struct got_repository *, FILE *); + int, int, int, int, struct got_diffstat_cb_arg *, struct got_repository *, + FILE *); struct got_pathlist_head; @@ -226,8 +231,8 @@ const struct got_error *got_diff_objects_as_trees(stru const struct got_error *got_diff_objects_as_trees(struct got_diff_line **, size_t *, FILE *, FILE *, int, int, struct got_object_id *, struct got_object_id *, struct got_pathlist_head *, const char *, - const char *, enum got_diff_algorithm, int, int, int, - struct got_repository *, FILE *); + const char *, enum got_diff_algorithm, int, int, int, int, + struct got_diffstat_cb_arg *, struct got_repository *, FILE *); /* * Diff two objects, assuming both objects are commits. @@ -244,6 +249,7 @@ const struct got_error *got_diff_objects_as_commits(st const struct got_error *got_diff_objects_as_commits(struct got_diff_line **, size_t *, FILE *, FILE *, int, int, struct got_object_id *, struct got_object_id *, struct got_pathlist_head *, enum got_diff_algorithm, - int, int, int, struct got_repository *, FILE *); + int, int, int, int, struct got_diffstat_cb_arg *, struct got_repository *, + FILE *); #define GOT_DIFF_MAX_CONTEXT 64 blob - 5e9010e76c3aa5de6de83e6b5cd54a4749c4f51a blob + 709e47798537a5f216664229cc751413b0a66942 --- lib/diff.c +++ lib/diff.c @@ -59,12 +59,75 @@ static const struct got_error * return NULL; } +static void +diffstat_field_width(size_t *maxlen, int *add_cols, int *rm_cols, size_t len, + uint32_t add, uint32_t rm) +{ + int d1 = 1, d2 = 1; + + if (maxlen) + *maxlen = MAX(*maxlen, len); + + while (add /= 10) + ++d1; + *add_cols = MAX(*add_cols, d1); + + while (rm /= 10) + ++d2; + *rm_cols = MAX(*rm_cols, d2); +} + static const struct got_error * +get_diffstat(struct got_diffstat_cb_arg *ds, const char *path, + struct got_diff_changed_path *change, struct diff_result *r, int force_text) +{ + const struct got_error *err; + struct got_pathlist_entry *pe; + int flags = (r->left->atomizer_flags | r->right->atomizer_flags); + int isbin = (flags & DIFF_ATOMIZER_FOUND_BINARY_DATA); + int i; + + change->add = 0; + change->rm = 0; + + if (!isbin || force_text) { + for (i = 0; i < r->chunks.len; ++i) { + struct diff_chunk *c; + int clc, crc; + + c = diff_chunk_get(r, i); + clc = diff_chunk_get_left_count(c); + crc = diff_chunk_get_right_count(c); + + if (crc && !clc) + change->add += crc; + if (clc && !crc) + change->rm += clc; + } + } + + ds->ins += change->add; + ds->del += change->rm; + ++ds->nfiles; + + err = got_pathlist_append(ds->paths, path, change); + if (err) + return err; + + pe = TAILQ_LAST(ds->paths, got_pathlist_head); + diffstat_field_width(&ds->max_path_len, &ds->add_cols, &ds->rm_cols, + pe->path_len, change->add, change->rm); + + return NULL; +} + +static const struct got_error * diff_blobs(struct got_diff_line **lines, size_t *nlines, struct got_diffreg_result **resultp, struct got_blob_object *blob1, struct got_blob_object *blob2, FILE *f1, FILE *f2, const char *label1, const char *label2, mode_t mode1, mode_t mode2, - int diff_context, int ignore_whitespace, int force_text_diff, FILE *outfile, + int diff_context, int ignore_whitespace, int force_text_diff, + int show_diffstat, struct got_diffstat_cb_arg *ds, FILE *outfile, enum got_diff_algorithm diff_algo) { const struct got_error *err = NULL, *free_err; @@ -170,11 +233,60 @@ diff_blobs(struct got_diff_line **lines, size_t *nline free(modestr1); free(modestr2); } + err = got_diffreg(&result, f1, f2, diff_algo, ignore_whitespace, force_text_diff); if (err) goto done; + if (show_diffstat) { + struct got_diff_changed_path *change = NULL; + char *path = NULL; + + if (label1 == NULL && label2 == NULL) { + /* diffstat of blobs, show hash instead of path */ + if (asprintf(&path, "%.10s -> %.10s", + idstr1, idstr2) == -1) { + err = got_error_from_errno("asprintf"); + goto done; + } + } else { + path = strdup(label2 ? label2 : label1); + if (path == NULL) { + err = got_error_from_errno("malloc"); + goto done; + } + } + + change = malloc(sizeof(*change)); + if (change == NULL) { + free(path); + err = got_error_from_errno("malloc"); + goto done; + } + + /* + * Ignore 'm'ode status change: if there's no accompanying + * content change, there'll be no diffstat, and if there + * are actual changes, 'M'odified takes precedence. + */ + change->status = GOT_STATUS_NO_CHANGE; + if (blob1 == NULL) + change->status = GOT_STATUS_ADD; + else if (blob2 == NULL) + change->status = GOT_STATUS_DELETE; + else + change->status = GOT_STATUS_MODIFY; + + err = get_diffstat(ds, path, change, result->result, + force_text_diff); + if (err) { + free(change); + free(path); + goto done; + } + } + if (outfile) { err = got_diffreg_output(lines, nlines, result, blob1 != NULL, blob2 != NULL, @@ -208,7 +320,8 @@ got_diff_blob_output_unidiff(void *arg, struct got_blo return diff_blobs(&a->lines, &a->nlines, NULL, blob1, blob2, f1, f2, label1, label2, mode1, mode2, a->diff_context, - a->ignore_whitespace, a->force_text_diff, a->outfile, a->diff_algo); + a->ignore_whitespace, a->force_text_diff, a->show_diffstat, + a->diffstat, a->outfile, a->diff_algo); } const struct got_error * @@ -216,11 +329,12 @@ got_diff_blob(struct got_diff_line **lines, size_t*nli struct got_blob_object *blob1, struct got_blob_object *blob2, FILE *f1, FILE *f2, const char *label1, const char *label2, enum got_diff_algorithm diff_algo, int diff_context, - int ignore_whitespace, int force_text_diff, FILE *outfile) + int ignore_whitespace, int force_text_diff, int show_diffstat, + struct got_diffstat_cb_arg *ds, FILE *outfile) { return diff_blobs(lines, nlines, NULL, blob1, blob2, f1, f2, label1, label2, 0, 0, diff_context, ignore_whitespace, - force_text_diff, outfile, diff_algo); + force_text_diff, show_diffstat, ds, outfile, diff_algo); } static const struct got_error * @@ -228,7 +342,8 @@ diff_blob_file(struct got_diffreg_result **resultp, struct got_blob_object *blob1, FILE *f1, off_t size1, const char *label1, FILE *f2, int f2_exists, struct stat *sb2, const char *label2, enum got_diff_algorithm diff_algo, int diff_context, int ignore_whitespace, - int force_text_diff, FILE *outfile) + int force_text_diff, int show_diffstat, struct got_diffstat_cb_arg *ds, + FILE *outfile) { const struct got_error *err = NULL, *free_err; char hex1[SHA1_DIGEST_STRING_LENGTH]; @@ -277,6 +392,45 @@ done: goto done; } + if (show_diffstat) { + struct got_diff_changed_path *change = NULL; + char *path = NULL; + + path = strdup(label2 ? label2 : label1); + if (path == NULL) { + err = got_error_from_errno("malloc"); + goto done; + } + + change = malloc(sizeof(*change)); + if (change == NULL) { + free(path); + err = got_error_from_errno("malloc"); + goto done; + } + + /* + * Ignore 'm'ode status change: if there's no accompanying + * content change, there'll be no diffstat, and if there + * are actual changes, 'M'odified takes precedence. + */ + change->status = GOT_STATUS_NO_CHANGE; + if (blob1 == NULL) + change->status = GOT_STATUS_ADD; + else if (!f2_exists) + change->status = GOT_STATUS_DELETE; + else + change->status = GOT_STATUS_MODIFY; + + err = get_diffstat(ds, path, change, result->result, + force_text_diff); + if (err) { + free(change); + free(path); + goto done; + } + } + done: if (resultp && err == NULL) *resultp = result; @@ -292,11 +446,12 @@ got_diff_blob_file(struct got_blob_object *blob1, FILE got_diff_blob_file(struct got_blob_object *blob1, FILE *f1, off_t size1, const char *label1, FILE *f2, int f2_exists, struct stat *sb2, const char *label2, enum got_diff_algorithm diff_algo, int diff_context, - int ignore_whitespace, int force_text_diff, FILE *outfile) + int ignore_whitespace, int force_text_diff, int show_diffstat, + struct got_diffstat_cb_arg *ds, FILE *outfile) { return diff_blob_file(NULL, blob1, f1, size1, label1, f2, f2_exists, sb2, label2, diff_algo, diff_context, ignore_whitespace, - force_text_diff, outfile); + force_text_diff, show_diffstat, ds, outfile); } static const struct got_error * @@ -611,23 +766,6 @@ static void NULL, label2, 0, te2->mode, repo); } -static void -diffstat_field_width(size_t *maxlen, int *add_cols, int *rm_cols, size_t len, - uint32_t add, uint32_t rm) -{ - int d1 = 1, d2 = 1; - - *maxlen = MAX(*maxlen, len); - - while (add /= 10) - ++d1; - *add_cols = MAX(*add_cols, d1); - - while (rm /= 10) - ++d2; - *rm_cols = MAX(*rm_cols, d2); -} - const struct got_error * got_diff_tree_compute_diffstat(void *arg, struct got_blob_object *blob1, struct got_blob_object *blob2, FILE *f1, FILE *f2, @@ -637,12 +775,9 @@ got_diff_tree_compute_diffstat(void *arg, struct got_b { const struct got_error *err = NULL; struct got_diffreg_result *result = NULL; - struct diff_result *r; struct got_diff_changed_path *change = NULL; struct got_diffstat_cb_arg *a = arg; - struct got_pathlist_entry *pe; char *path = NULL; - int i; path = strdup(label2 ? label2 : label1); if (path == NULL) @@ -697,36 +832,8 @@ got_diff_tree_compute_diffstat(void *arg, struct got_b if (err) goto done; - for (i = 0, r = result->result; i < r->chunks.len; ++i) { - int flags = (r->left->atomizer_flags | r->right->atomizer_flags); - int isbin = (flags & DIFF_ATOMIZER_FOUND_BINARY_DATA); + err = get_diffstat(a, path, change, result->result, a->force_text); - if (!isbin || a->force_text) { - struct diff_chunk *c; - int clc, crc; - - c = diff_chunk_get(r, i); - clc = diff_chunk_get_left_count(c); - crc = diff_chunk_get_right_count(c); - - if (clc && !crc) - change->rm += clc; - else if (crc && !clc) - change->add += crc; - } - } - - err = got_pathlist_append(a->paths, path, change); - if (err) - goto done; - - pe = TAILQ_LAST(a->paths, got_pathlist_head); - diffstat_field_width(&a->max_path_len, &a->add_cols, &a->rm_cols, - pe->path_len, change->add, change->rm); - a->ins += change->add; - a->del += change->rm; - ++a->nfiles; - done: if (result) { const struct got_error *free_err; @@ -885,8 +992,8 @@ got_diff_objects_as_blobs(struct got_diff_line **lines struct got_object_id *id1, struct got_object_id *id2, const char *label1, const char *label2, enum got_diff_algorithm diff_algo, int diff_context, - int ignore_whitespace, int force_text_diff, - struct got_repository *repo, FILE *outfile) + int ignore_whitespace, int force_text_diff, int show_diffstat, + struct got_diffstat_cb_arg *ds, struct got_repository *repo, FILE *outfile) { const struct got_error *err; struct got_blob_object *blob1 = NULL, *blob2 = NULL; @@ -906,7 +1013,7 @@ got_diff_objects_as_blobs(struct got_diff_line **lines } err = got_diff_blob(lines, nlines, blob1, blob2, f1, f2, label1, label2, diff_algo, diff_context, ignore_whitespace, force_text_diff, - outfile); + show_diffstat, ds, outfile); done: if (blob1) got_object_blob_close(blob1); @@ -1080,6 +1187,7 @@ diff_objects_as_trees(struct got_diff_line **lines, si struct got_object_id *id1, struct got_object_id *id2, struct got_pathlist_head *paths, const char *label1, const char *label2, int diff_context, int ignore_whitespace, int force_text_diff, + int show_diffstat, struct got_diffstat_cb_arg *dsa, struct got_repository *repo, FILE *outfile, enum got_diff_algorithm diff_algo) { @@ -1106,6 +1214,8 @@ diff_objects_as_trees(struct got_diff_line **lines, si arg.diff_context = diff_context; arg.ignore_whitespace = ignore_whitespace; arg.force_text_diff = force_text_diff; + arg.show_diffstat = show_diffstat; + arg.diffstat = dsa; arg.outfile = outfile; if (want_linemeta) { arg.lines = *lines; @@ -1114,14 +1224,12 @@ diff_objects_as_trees(struct got_diff_line **lines, si arg.lines = NULL; arg.nlines = 0; } - if (paths == NULL || TAILQ_EMPTY(paths)) { - err = got_diff_tree(tree1, tree2, f1, f2, fd1, fd2, - label1, label2, repo, - got_diff_blob_output_unidiff, &arg, 1); - } else { + if (paths == NULL || TAILQ_EMPTY(paths)) + err = got_diff_tree(tree1, tree2, f1, f2, fd1, fd2, label1, + label2, repo, got_diff_blob_output_unidiff, &arg, 1); + else err = diff_paths(tree1, tree2, f1, f2, fd1, fd2, paths, repo, got_diff_blob_output_unidiff, &arg); - } if (want_linemeta) { *lines = arg.lines; /* was likely re-allocated */ *nlines = arg.nlines; @@ -1140,7 +1248,8 @@ got_diff_objects_as_trees(struct got_diff_line **lines struct got_object_id *id1, struct got_object_id *id2, struct got_pathlist_head *paths, const char *label1, const char *label2, enum got_diff_algorithm diff_algo, int diff_context, int ignore_whitespace, - int force_text_diff, struct got_repository *repo, FILE *outfile) + int force_text_diff, int show_diffstat, struct got_diffstat_cb_arg *dsa, + struct got_repository *repo, FILE *outfile) { const struct got_error *err; char *idstr = NULL; @@ -1182,7 +1291,7 @@ got_diff_objects_as_trees(struct got_diff_line **lines err = diff_objects_as_trees(lines, nlines, f1, f2, fd1, fd2, id1, id2, paths, label1, label2, diff_context, ignore_whitespace, - force_text_diff, repo, outfile, diff_algo); + force_text_diff, show_diffstat, dsa, repo, outfile, diff_algo); done: free(idstr); return err; @@ -1194,6 +1303,7 @@ got_diff_objects_as_commits(struct got_diff_line **lin struct got_object_id *id1, struct got_object_id *id2, struct got_pathlist_head *paths, enum got_diff_algorithm diff_algo, int diff_context, int ignore_whitespace, int force_text_diff, + int show_diffstat, struct got_diffstat_cb_arg *dsa, struct got_repository *repo, FILE *outfile) { const struct got_error *err; @@ -1237,8 +1347,8 @@ got_diff_objects_as_commits(struct got_diff_line **lin err = diff_objects_as_trees(lines, nlines, f1, f2, fd1, fd2, commit1 ? got_object_commit_get_tree_id(commit1) : NULL, got_object_commit_get_tree_id(commit2), paths, "", "", - diff_context, ignore_whitespace, force_text_diff, repo, outfile, - diff_algo); + diff_context, ignore_whitespace, force_text_diff, show_diffstat, + dsa, repo, outfile, diff_algo); done: if (commit1) got_object_commit_close(commit1); blob - b40f67cf87889e4cd271c0870db2a357160443ad blob + e92fe5aa6a9932cfb59504fd20073ebc1fb4f6d4 --- lib/worktree.c +++ lib/worktree.c @@ -5083,8 +5083,8 @@ append_ct_diff(struct got_commitable *ct, int *diff_he } err = got_diff_objects_as_blobs(NULL, NULL, f1, f2, fd1, fd2, ct->base_blob_id, ct->staged_blob_id, - label1, label2, GOT_DIFF_ALGORITHM_PATIENCE, 3, 0, 0, - repo, diff_outfile); + label1, label2, GOT_DIFF_ALGORITHM_PATIENCE, 3, 0, 0, 0, + NULL, repo, diff_outfile); goto done; } @@ -5154,7 +5154,7 @@ append_ct_diff(struct got_commitable *ct, int *diff_he err = got_diff_blob_file(blob1, f1, size1, label1, ondisk_file ? ondisk_file : f2, f2_exists, &sb, ct->path, - GOT_DIFF_ALGORITHM_PATIENCE, 3, 0, 0, diff_outfile); + GOT_DIFF_ALGORITHM_PATIENCE, 3, 0, 0, 0, NULL, diff_outfile); done: if (fd1 != -1 && close(fd1) == -1 && err == NULL) err = got_error_from_errno("close"); blob - 7a91723cf327c438d7801b8d486b165bd84ba1ea blob + e05667c5078d9ea47470c917b489eef99e99c0a7 --- tog/tog.c +++ tog/tog.c @@ -4782,13 +4782,14 @@ create_diff(struct tog_diff_view_state *s) err = got_diff_objects_as_blobs(&s->lines, &s->nlines, s->f1, s->f2, s->fd1, s->fd2, s->id1, s->id2, s->label1, s->label2, tog_diff_algo, s->diff_context, - s->ignore_whitespace, s->force_text_diff, s->repo, s->f); + s->ignore_whitespace, s->force_text_diff, 0, NULL, s->repo, + s->f); break; case GOT_OBJ_TYPE_TREE: err = got_diff_objects_as_trees(&s->lines, &s->nlines, s->f1, s->f2, s->fd1, s->fd2, s->id1, s->id2, NULL, "", "", tog_diff_algo, s->diff_context, s->ignore_whitespace, - s->force_text_diff, s->repo, s->f); + s->force_text_diff, 0, NULL, s->repo, s->f); break; case GOT_OBJ_TYPE_COMMIT: { const struct got_object_id_queue *parent_ids; @@ -4826,7 +4827,7 @@ create_diff(struct tog_diff_view_state *s) err = got_diff_objects_as_commits(&s->lines, &s->nlines, s->f1, s->f2, s->fd1, s->fd2, s->id1, s->id2, NULL, tog_diff_algo, s->diff_context, s->ignore_whitespace, - s->force_text_diff, s->repo, s->f); + s->force_text_diff, 0, NULL, s->repo, s->f); break; } default: -- Mark Jamsek GPG: F2FF 13DE 6A06 C471 CA80 E6E2 2930 DC66 86EE CF68