commit - 17c726049a0c49099ef0a44190264fa4b6b037fa
commit + 5191b70b5b2e123aadd88aeafe2e2cfc0958c327
blob - e754777a9202afca9a7b80dfd634b3c605d8a5cd
blob + d7f14e7606560bbef80113d081dca440bc8ef372
--- got/got.1
+++ got/got.1
automatically, provided the abbreviation is unique.
If this option is not specified, default to the work tree's current branch
if invoked in a work tree, or to the repository's HEAD reference.
+.It Fl d
+Display diffstat of changes introduced in the commit.
+Cannot be used with the
+.Fl s
+option.
.It Fl l Ar N
Limit history traversal to a given number of commits.
If this option is not specified, a default limit value of zero is used,
blob - ad0fec63ec6030f40937e7bf6836769359bb55ba
blob + aaf34c7a1dabde267171e1e19cd7f49b72525be1
--- got/got.c
+++ got/got.c
static const struct got_error *
get_changed_paths(struct got_pathlist_head *paths,
- struct got_commit_object *commit, struct got_repository *repo)
+ struct got_commit_object *commit, struct got_repository *repo,
+ struct got_diffstat_cb_arg *dsa)
{
const struct got_error *err = NULL;
struct got_object_id *tree_id1 = NULL, *tree_id2 = NULL;
struct got_tree_object *tree1 = NULL, *tree2 = NULL;
struct got_object_qid *qid;
+ got_diff_blob_cb cb = got_diff_tree_collect_changed_paths;
+ FILE *f1 = NULL, *f2 = NULL;
+ int fd1 = -1, fd2 = -1;
+ if (dsa) {
+ cb = got_diff_tree_compute_diffstat;
+
+ f1 = got_opentemp();
+ if (f1 == NULL) {
+ err = got_error_from_errno("got_opentemp");
+ goto done;
+ }
+ f2 = got_opentemp();
+ if (f2 == NULL) {
+ err = got_error_from_errno("got_opentemp");
+ goto done;
+ }
+ fd1 = got_opentempfd();
+ if (fd1 == -1) {
+ err = got_error_from_errno("got_opentempfd");
+ goto done;
+ }
+ fd2 = got_opentempfd();
+ if (fd2 == -1) {
+ err = got_error_from_errno("got_opentempfd");
+ goto done;
+ }
+ }
+
qid = STAILQ_FIRST(got_object_commit_get_parent_ids(commit));
if (qid != NULL) {
struct got_commit_object *pcommit;
if (err)
goto done;
- err = got_diff_tree(tree1, tree2, NULL, NULL, -1, -1, "", "", repo,
- got_diff_tree_collect_changed_paths, paths, 0);
+ err = got_diff_tree(tree1, tree2, f1, f2, fd1, fd2, "", "", repo,
+ cb, dsa ? (void *)dsa : paths, dsa ? 1 : 0);
done:
if (tree1)
got_object_tree_close(tree1);
if (tree2)
got_object_tree_close(tree2);
+ if (fd1 != -1 && close(fd1) == -1 && err == NULL)
+ err = got_error_from_errno("close");
+ if (fd2 != -1 && close(fd2) == -1 && err == NULL)
+ err = got_error_from_errno("close");
+ if (f1 && fclose(f1) == EOF && err == NULL)
+ err = got_error_from_errno("fclose");
+ if (f2 && fclose(f2) == EOF && err == NULL)
+ err = got_error_from_errno("fclose");
free(tree_id1);
return err;
}
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, int show_patch,
- int diff_context, struct got_reflist_object_id_map *refs_idmap,
- const char *custom_refs_str)
+ struct got_pathlist_head *changed_paths, struct got_diffstat_cb_arg *dsa,
+ int show_patch, int diff_context,
+ struct got_reflist_object_id_map *refs_idmap, const char *custom_refs_str)
{
const struct got_error *err = NULL;
char *id_str, *datestr, *logmsg0, *logmsg, *line;
if (changed_paths) {
struct got_pathlist_entry *pe;
+
TAILQ_FOREACH(pe, changed_paths, entry) {
struct got_diff_changed_path *cp = pe->data;
- printf(" %c %s\n", cp->status, pe->path);
+ 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);
}
+ 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) {
static const struct got_error *
print_commits(struct got_object_id *root_id, struct got_object_id *end_id,
struct got_repository *repo, const char *path, int show_changed_paths,
- int show_patch, const char *search_pattern, int diff_context, int limit,
- int log_branches, int reverse_display_order,
+ int show_diffstat, int show_patch, const char *search_pattern,
+ int diff_context, int limit, int log_branches, int reverse_display_order,
struct got_reflist_object_id_map *refs_idmap, int one_line,
FILE *tmpfile)
{
goto done;
for (;;) {
struct got_object_id id;
+ struct got_diffstat_cb_arg dsa = { 0, 0, 0, 0, 0, 0,
+ &changed_paths, 0, 0, GOT_DIFF_ALGORITHM_PATIENCE };
if (sigint_received || sigpipe_received)
break;
if (err)
break;
- if (show_changed_paths && !reverse_display_order) {
- err = get_changed_paths(&changed_paths, commit, repo);
+ if ((show_changed_paths || show_diffstat) &&
+ !reverse_display_order) {
+ err = get_changed_paths(&changed_paths, commit, repo,
+ show_diffstat ? &dsa : NULL);
if (err)
break;
}
repo, refs_idmap);
else
err = print_commit(commit, &id, repo, path,
- show_changed_paths ? &changed_paths : NULL,
- show_patch, diff_context, refs_idmap, NULL);
+ (show_changed_paths || show_diffstat) ?
+ &changed_paths : NULL,
+ show_diffstat ? &dsa : NULL, show_patch,
+ diff_context, refs_idmap, NULL);
got_object_commit_close(commit);
if (err)
break;
}
if (reverse_display_order) {
STAILQ_FOREACH(qid, &reversed_commits, entry) {
+ struct got_diffstat_cb_arg dsa = { 0, 0, 0, 0, 0, 0,
+ &changed_paths, 0, 0, GOT_DIFF_ALGORITHM_PATIENCE };
+
err = got_object_open_as_commit(&commit, repo,
&qid->id);
if (err)
break;
- if (show_changed_paths) {
+ if (show_changed_paths || show_diffstat) {
err = get_changed_paths(&changed_paths,
- commit, repo);
+ commit, repo, show_diffstat ? &dsa : NULL);
if (err)
break;
}
repo, refs_idmap);
else
err = print_commit(commit, &qid->id, repo, path,
- show_changed_paths ? &changed_paths : NULL,
- show_patch, diff_context, refs_idmap, NULL);
+ (show_changed_paths || show_diffstat) ?
+ &changed_paths : NULL,
+ show_diffstat ? &dsa : NULL, show_patch,
+ diff_context, refs_idmap, NULL);
got_object_commit_close(commit);
if (err)
break;
const char *search_pattern = NULL;
int diff_context = -1, ch;
int show_changed_paths = 0, show_patch = 0, limit = 0, log_branches = 0;
- int reverse_display_order = 0, one_line = 0;
+ int show_diffstat = 0, reverse_display_order = 0, one_line = 0;
const char *errstr;
struct got_reflist_head refs;
struct got_reflist_object_id_map *refs_idmap = NULL;
limit = get_default_log_limit();
- while ((ch = getopt(argc, argv, "bC:c:l:PpRr:S:sx:")) != -1) {
+ while ((ch = getopt(argc, argv, "bC:c:dl:PpRr:S:sx:")) != -1) {
switch (ch) {
case 'b':
log_branches = 1;
errx(1, "number of context lines is %s: %s",
errstr, optarg);
break;
+ case 'd':
+ show_diffstat = 1;
+ break;
case 'c':
start_commit = optarg;
break;
else if (!show_patch)
errx(1, "-C requires -p");
- if (one_line && (show_patch || show_changed_paths))
- errx(1, "cannot use -s with -p or -P");
+ if (one_line && (show_patch || show_changed_paths || show_diffstat))
+ errx(1, "cannot use -s with -d, -p or -P");
cwd = getcwd(NULL, 0);
if (cwd == NULL) {
}
error = print_commits(start_id, end_id, repo, path ? path : "",
- show_changed_paths, show_patch, search_pattern, diff_context,
- limit, log_branches, reverse_display_order, refs_idmap, one_line,
- tmpfile);
+ show_changed_paths, show_diffstat, show_patch, search_pattern,
+ diff_context, limit, log_branches, reverse_display_order,
+ refs_idmap, one_line, tmpfile);
done:
free(path);
free(repo_path);
if (asprintf(&custom_refs_str, "formerly %s", branch_name) == -1)
return got_error_from_errno("asprintf");
- err = print_commit(old_commit, old_commit_id, repo, NULL, NULL,
+ err = print_commit(old_commit, old_commit_id, repo, NULL, NULL, NULL,
0, 0, refs_idmap, custom_refs_str);
if (err)
goto done;
blob - 26617c2f087a790bbb5f19d32d57f83246fcb532
blob + 18fe6a19ccc9b5b2450e313f81d9246ce51b630d
--- include/got_diff.h
+++ include/got_diff.h
struct got_repository *, got_diff_blob_cb cb, void *cb_arg, int);
/*
- * A pre-defined implementation of got_diff_blob_cb() which collects a list
- * of file paths that differ between two trees.
+ * Pre-defined implementations of got_diff_blob_cb(): the first of which
+ * collects a list of file paths that differ between two trees; the second
+ * also computes a diffstat of added/removed lines for each collected path
+ * and requires passing an initialized got_diffstat_cb_arg argument.
* The caller must allocate and initialize a got_pathlist_head * argument.
* Data pointers of entries added to the path list will point to a struct
* got_diff_changed_path object.
* entries on the path list.
*/
struct got_diff_changed_path {
+ uint32_t add; /* number of lines added */
+ uint32_t rm; /* number of lines removed */
/*
* The modification status of this path. It can be GOT_STATUS_ADD,
* GOT_STATUS_DELETE, GOT_STATUS_MODIFY, or GOT_STATUS_MODE_CHANGE.
struct got_object_id *, struct got_object_id *,
const char *, const char *, mode_t, mode_t, struct got_repository *);
+struct got_diffstat_cb_arg {
+ size_t max_path_len;
+ uint32_t ins;
+ uint32_t del;
+ int add_cols;
+ int rm_cols;
+ int nfiles;
+ struct got_pathlist_head *paths;
+ int ignore_ws;
+ int force_text;
+ enum got_diff_algorithm diff_algo;
+};
+const struct got_error *got_diff_tree_compute_diffstat(void *,
+ struct got_blob_object *, struct got_blob_object *, FILE *, FILE *,
+ struct got_object_id *, struct got_object_id *, const char *, const char *,
+ mode_t, mode_t, struct got_repository *);
+
/*
* Diff two objects, assuming both objects are blobs. Two const char * diff
* header labels may be provided which will be used to identify each blob in
blob - 58baf2baa359d6bb49f1d7c1b7bd6af3f15bbb36
blob + 2007e46b23e16bc18f223174ce22d2e157bad96e
--- lib/diff.c
+++ lib/diff.c
#include "got_lib_delta.h"
#include "got_lib_inflate.h"
#include "got_lib_object.h"
+
+#ifndef MAX
+#define MAX(_a,_b) ((_a) > (_b) ? (_a) : (_b))
+#endif
static const struct got_error *
add_line_metadata(struct got_diff_line **lines, size_t *nlines,
return cb(cb_arg, NULL, NULL, NULL, NULL, NULL, &te2->id,
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,
+ struct got_object_id *id1, struct got_object_id *id2,
+ const char *label1, const char *label2,
+ mode_t mode1, mode_t mode2, struct got_repository *repo)
+{
+ 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)
+ return got_error_from_errno("malloc");
+
+ change = malloc(sizeof(*change));
+ if (change == NULL) {
+ err = got_error_from_errno("malloc");
+ goto done;
+ }
+
+ change->add = 0;
+ change->rm = 0;
+ change->status = GOT_STATUS_NO_CHANGE;
+ if (id1 == NULL)
+ change->status = GOT_STATUS_ADD;
+ else if (id2 == NULL)
+ change->status = GOT_STATUS_DELETE;
+ else {
+ if (got_object_id_cmp(id1, id2) != 0)
+ change->status = GOT_STATUS_MODIFY;
+ else if (mode1 != mode2)
+ change->status = GOT_STATUS_MODE_CHANGE;
+ }
+
+ if (f1) {
+ err = got_opentemp_truncate(f1);
+ if (err)
+ goto done;
+ }
+ if (f2) {
+ err = got_opentemp_truncate(f2);
+ if (err)
+ goto done;
+ }
+
+ if (blob1) {
+ err = got_object_blob_dump_to_file(NULL, NULL, NULL, f1,
+ blob1);
+ if (err)
+ goto done;
+ }
+ if (blob2) {
+ err = got_object_blob_dump_to_file(NULL, NULL, NULL, f2,
+ blob2);
+ if (err)
+ goto done;
+ }
+
+ err = got_diffreg(&result, f1, f2, a->diff_algo, a->ignore_ws,
+ a->force_text);
+ 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);
+
+ 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;
+
+ free_err = got_diffreg_result_free(result);
+ if (free_err && err == NULL)
+ err = free_err;
+ }
+ if (err) {
+ free(path);
+ free(change);
+ }
+ return err;
+}
+
+const struct got_error *
got_diff_tree_collect_changed_paths(void *arg, struct got_blob_object *blob1,
struct got_blob_object *blob2, FILE *f1, FILE *f2,
struct got_object_id *id1, struct got_object_id *id2,
blob - 2f1436265914a5af2afa20029647cb7236df2288
blob + c032241608751301e7be7c2f440255dec505fecb
--- tog/tog.c
+++ tog/tog.c
static const struct got_error *
get_changed_paths(struct got_pathlist_head *paths,
- struct got_commit_object *commit, struct got_repository *repo)
+ struct got_commit_object *commit, struct got_repository *repo,
+ struct got_diffstat_cb_arg *dsa)
{
const struct got_error *err = NULL;
struct got_object_id *tree_id1 = NULL, *tree_id2 = NULL;
struct got_tree_object *tree1 = NULL, *tree2 = NULL;
struct got_object_qid *qid;
+ FILE *f1 = NULL, *f2 = NULL;
+ int fd1 = -1, fd2 = -1;
+ f1 = got_opentemp();
+ if (f1 == NULL) {
+ err = got_error_from_errno("got_opentemp");
+ goto done;
+ }
+ f2 = got_opentemp();
+ if (f2 == NULL) {
+ err = got_error_from_errno("got_opentemp");
+ goto done;
+ }
+
+ fd1 = got_opentempfd();
+ if (fd1 == -1) {
+ err = got_error_from_errno("got_opentempfd");
+ goto done;
+ }
+ fd2 = got_opentempfd();
+ if (fd2 == -1) {
+ err = got_error_from_errno("got_opentempfd");
+ goto done;
+ }
+
qid = STAILQ_FIRST(got_object_commit_get_parent_ids(commit));
if (qid != NULL) {
struct got_commit_object *pcommit;
if (err)
goto done;
- err = got_diff_tree(tree1, tree2, NULL, NULL, -1, -1, "", "", repo,
- got_diff_tree_collect_changed_paths, paths, 0);
+ err = got_diff_tree(tree1, tree2, f1, f2, fd1, fd2, "", "", repo,
+ got_diff_tree_compute_diffstat, dsa, 1);
done:
if (tree1)
got_object_tree_close(tree1);
if (tree2)
got_object_tree_close(tree2);
+ if (fd1 != -1 && close(fd1) == -1 && err == NULL)
+ err = got_error_from_errno("close");
+ if (fd2 != -1 && close(fd2) == -1 && err == NULL)
+ err = got_error_from_errno("close");
+ if (f1 && fclose(f1) == EOF && err == NULL)
+ err = got_error_from_errno("fclose");
+ if (f2 && fclose(f2) == EOF && err == NULL)
+ err = got_error_from_errno("fclose");
free(tree_id1);
return err;
}
static const struct got_error *
write_commit_info(struct got_diff_line **lines, size_t *nlines,
struct got_object_id *commit_id, struct got_reflist_head *refs,
- struct got_repository *repo, FILE *outfile)
+ struct got_repository *repo, int ignore_ws, int force_text_diff,
+ FILE *outfile)
{
const struct got_error *err = NULL;
char datebuf[26], *datestr;
char *refs_str = NULL;
struct got_pathlist_head changed_paths;
struct got_pathlist_entry *pe;
+ struct got_diffstat_cb_arg dsa = { 0, 0, 0, 0, 0, 0, &changed_paths,
+ ignore_ws, force_text_diff, tog_diff_algo };
off_t outoff = 0;
int n;
goto done;
}
- err = get_changed_paths(&changed_paths, commit, repo);
+ err = get_changed_paths(&changed_paths, commit, repo, &dsa);
if (err)
goto done;
+
TAILQ_FOREACH(pe, &changed_paths, entry) {
struct got_diff_changed_path *cp = pe->data;
- n = fprintf(outfile, "%c %s\n", cp->status, pe->path);
+ int pad = dsa.max_path_len - pe->path_len + 1;
+
+ n = fprintf(outfile, "%c %s%*c | %*d+ %*d-\n", cp->status,
+ pe->path, pad, ' ', dsa.add_cols + 1, cp->add,
+ dsa.rm_cols + 1, cp->rm);
if (n < 0) {
err = got_error_from_errno("fprintf");
goto done;
fputc('\n', outfile);
outoff++;
err = add_line_metadata(lines, nlines, outoff, GOT_DIFF_LINE_NONE);
+ if (err)
+ goto done;
+
+ n = fprintf(outfile,
+ "%d file%s changed, %d insertions(+), %d deletions(-)\n",
+ dsa.nfiles, dsa.nfiles > 1 ? "s" : "", dsa.ins, dsa.del);
+ if (n < 0) {
+ err = got_error_from_errno("fprintf");
+ goto done;
+ }
+ outoff += n;
+ err = add_line_metadata(lines, nlines, outoff, GOT_DIFF_LINE_NONE);
+ if (err)
+ goto done;
+
+ fputc('\n', outfile);
+ outoff++;
+ err = add_line_metadata(lines, nlines, outoff, GOT_DIFF_LINE_NONE);
done:
got_pathlist_free(&changed_paths);
free(id_str);
/* Show commit info if we're diffing to a parent/root commit. */
if (s->id1 == NULL) {
err = write_commit_info(&s->lines, &s->nlines, s->id2,
- refs, s->repo, s->f);
+ refs, s->repo, s->ignore_whitespace,
+ s->force_text_diff, s->f);
if (err)
goto done;
} else {
if (got_object_id_cmp(s->id1, &pid->id) == 0) {
err = write_commit_info(&s->lines,
&s->nlines, s->id2, refs, s->repo,
- s->f);
+ s->ignore_whitespace,
+ s->force_text_diff, s->f);
if (err)
goto done;
break;
case 'w':
if (ch == 'a')
s->force_text_diff = !s->force_text_diff;
- if (ch == 'w')
+ else if (ch == 'w')
s->ignore_whitespace = !s->ignore_whitespace;
err = reset_diff_view(view);
break;