commit - a19581a24bec8161b09eab50d721edc1c8f42be9
commit + 404c43c4fa13923747c649f395f5b2bcb9fcd484
blob - f3bd4030b0a0f870930ffa01cef63c30db0846af
blob + 4ca05ec8487753b03115ac175e8878dac1c78bcb
--- got/Makefile
+++ got/Makefile
.PATH:${.CURDIR}/../lib
PROG= got
-SRCS= got.c commit_graph.c delta.c diff.c diffreg.c error.c \
+SRCS= got.c blame.c commit_graph.c delta.c diff.c diffreg.c error.c \
fileindex.c object.c object_idset.c opentemp.c path.c \
pack.c privsep.c reference.c repository.c sha1.c \
worktree.c zbuf.c
blob - cdf523b6507a59b3fd3dda104d5d87083b7af51c
blob + bbdd8feb7717e3603a45f96b3801a65d6f196eb5
--- got/got.c
+++ got/got.c
#include "got_worktree.h"
#include "got_diff.h"
#include "got_commit_graph.h"
+#include "got_blame.h"
#ifndef nitems
#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
__dead static void usage_checkout(void);
__dead static void usage_log(void);
__dead static void usage_diff(void);
+__dead static void usage_blame(void);
static const struct got_error* cmd_checkout(int, char *[]);
static const struct got_error* cmd_log(int, char *[]);
static const struct got_error* cmd_diff(int, char *[]);
+static const struct got_error* cmd_blame(int, char *[]);
#ifdef notyet
static const struct got_error* cmd_status(int, char *[]);
#endif
"show repository history" },
{ "diff", cmd_diff, usage_diff,
"compare files and directories" },
+ { "blame", cmd_blame, usage_blame,
+ " show when lines in a file were changed" },
#ifdef notyet
{ "status", cmd_status, usage_status,
"show modification status of files" },
if (repo)
got_repo_close(repo);
return error;
+}
+
+__dead static void
+usage_blame(void)
+{
+ fprintf(stderr, "usage: %s blame [-c commit] [repository-path] path\n",
+ getprogname());
+ exit(1);
}
+static const struct got_error *
+cmd_blame(int argc, char *argv[])
+{
+ const struct got_error *error;
+ struct got_repository *repo = NULL;
+ char *repo_path = NULL;
+ char *path = NULL;
+ struct got_object_id *commit_id = NULL;
+ char *commit_id_str = NULL;
+ int ch;
+
+#ifndef PROFILE
+ if (pledge("stdio rpath wpath cpath flock proc", NULL) == -1)
+ err(1, "pledge");
+#endif
+
+ while ((ch = getopt(argc, argv, "c:")) != -1) {
+ switch (ch) {
+ case 'c':
+ commit_id_str = optarg;
+ break;
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc == 0) {
+ usage_blame();
+ } else if (argc == 1) {
+ repo_path = getcwd(NULL, 0);
+ if (repo_path == NULL)
+ return got_error_from_errno();
+ path = argv[0];
+ } else if (argc == 2) {
+ repo_path = realpath(argv[0], NULL);
+ if (repo_path == NULL)
+ return got_error_from_errno();
+ path = argv[1];
+ } else
+ usage_blame();
+
+ error = got_repo_open(&repo, repo_path);
+ free(repo_path);
+ if (error != NULL)
+ goto done;
+
+ if (commit_id_str == NULL) {
+ struct got_reference *head_ref;
+ error = got_ref_open(&head_ref, repo, GOT_REF_HEAD);
+ if (error != NULL)
+ return error;
+ error = got_ref_resolve(&commit_id, repo, head_ref);
+ got_ref_close(head_ref);
+ if (error != NULL)
+ return error;
+ } else {
+ struct got_object *obj;
+ error = got_object_open_by_id_str(&obj, repo, commit_id_str);
+ if (error != NULL)
+ return error;
+ commit_id = got_object_get_id(obj);
+ if (commit_id == NULL)
+ error = got_error_from_errno();
+ got_object_close(obj);
+ }
+
+ error = got_blame(path, commit_id, repo, stdout);
+done:
+ free(commit_id);
+ if (repo)
+ got_repo_close(repo);
+ return error;
+}
+
#ifdef notyet
static const struct got_error *
cmd_status(int argc __unused, char *argv[] __unused)
blob - /dev/null
blob + 0f683c8087cfa06140945242d684704f6e32d761 (mode 644)
--- /dev/null
+++ lib/blame.c
+/*
+ * Copyright (c) 2018 Stefan Sperling <stsp@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/queue.h>
+#include <sys/stat.h>
+
+#include <sha1.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <util.h>
+#include <zlib.h>
+
+#include "got_error.h"
+#include "got_object.h"
+#include "got_blame.h"
+#include "got_opentemp.h"
+
+#include "got_lib_zbuf.h"
+#include "got_lib_delta.h"
+#include "got_lib_object.h"
+#include "got_lib_diff.h"
+
+struct got_blame_line {
+ int annotated;
+ struct got_object_id id; /* one per line */
+};
+
+struct got_blame {
+ FILE *f;
+ size_t nlines;
+ struct got_blame_line *lines; /* one per line */
+};
+
+static const struct got_error *
+dump_blob_and_count_lines(size_t *nlines, FILE *outfile,
+ struct got_blob_object *blob)
+{
+ const struct got_error *err = NULL;
+ size_t len, hdrlen;
+ const uint8_t *buf;
+ int i;
+
+ hdrlen = got_object_blob_get_hdrlen(blob);
+ *nlines = 0;
+ do {
+ err = got_object_blob_read_block(&len, blob);
+ if (err)
+ return err;
+ if (len == 0)
+ break;
+ buf = got_object_blob_get_read_buf(blob);
+ for (i = 0; i < len; i++) {
+ if (buf[i] == '\n')
+ (*nlines)++;
+ }
+ /* Skip blob object header first time around. */
+ fwrite(buf + hdrlen, len - hdrlen, 1, outfile);
+ hdrlen = 0;
+ } while (len != 0);
+
+
+ fflush(outfile);
+ rewind(outfile);
+
+ return NULL;
+}
+
+static const struct got_error *
+annotate_line(struct got_blame *blame, int lineno, struct got_object_id *id)
+{
+ struct got_blame_line *line;
+
+ if (lineno < 1 || lineno > blame->nlines)
+ return got_error(GOT_ERR_RANGE);
+
+ line = &blame->lines[lineno - 1];
+ if (line->annotated)
+ return NULL;
+
+ memcpy(&line->id, id, sizeof(line->id));
+ line->annotated = 1;
+ return NULL;
+}
+
+static const struct got_error *
+blame_commit(struct got_blame *blame, struct got_object_id *id,
+ struct got_object_id *pid, const char *path, struct got_repository *repo)
+{
+ const struct got_error *err = NULL;
+ struct got_object *obj = NULL, *pobj = NULL;
+ struct got_blob_object *blob = NULL, *pblob = NULL;
+ struct got_diff_changes *changes = NULL;
+
+ err = got_object_open_by_path(&obj, repo, id, path);
+ if (err)
+ goto done;
+ if (got_object_get_type(obj) != GOT_OBJ_TYPE_BLOB) {
+ err = got_error(GOT_ERR_OBJ_TYPE);
+ goto done;
+ }
+
+ err = got_object_open_by_path(&pobj, repo, pid, path);
+ if (err) {
+ if (err->code == GOT_ERR_NO_OBJ) {
+ /* Blob's history began in previous commit. */
+ err = got_error(GOT_ERR_ITER_COMPLETED);
+ }
+ goto done;
+ }
+ if (got_object_get_type(pobj) != GOT_OBJ_TYPE_BLOB) {
+ /*
+ * Encountered a non-blob at the path (probably a tree).
+ * Blob's history began in previous commit.
+ */
+ err = got_error(GOT_ERR_ITER_COMPLETED);
+ goto done;
+ }
+
+ /* If blob hashes match then don't bother with diffing. */
+ if (got_object_id_cmp(&obj->id, &pobj->id) == 0)
+ goto done;
+
+ err = got_object_blob_open(&blob, repo, obj, 8192);
+ if (err)
+ goto done;
+
+ err = got_object_blob_open(&pblob, repo, pobj, 8192);
+ if (err)
+ goto done;
+
+ err = got_diff_blob_lines_changed(&changes, blob, pblob);
+ if (err)
+ goto done;
+
+ if (changes) {
+ struct got_diff_change *change;
+ char *id_str;
+ err = got_object_id_str(&id_str, id);
+ if (err)
+ goto done;
+
+ SIMPLEQ_FOREACH(change, &changes->entries, entry) {
+ int a = change->cv.a;
+ int b = change->cv.b;
+ int lineno;
+ for (lineno = a; lineno <= b; lineno++) {
+ err = annotate_line(blame, lineno, id);
+ if (err)
+ goto done;
+ }
+ }
+ free(id_str);
+ }
+done:
+ if (obj)
+ got_object_close(obj);
+ if (pobj)
+ got_object_close(pobj);
+ if (blob)
+ got_object_blob_close(blob);
+ if (pblob)
+ got_object_blob_close(pblob);
+ return err;
+}
+
+static void
+blame_close(struct got_blame *blame)
+{
+ if (blame->f)
+ fclose(blame->f);
+ free(blame->lines);
+ free(blame);
+}
+
+static const struct got_error *
+blame_open(struct got_blame **blamep, const char *path,
+ struct got_object_id *start_commit_id, struct got_repository *repo)
+{
+ const struct got_error *err = NULL;
+ struct got_object *obj = NULL;
+ struct got_blob_object *blob = NULL;
+ struct got_blame *blame = NULL;
+ struct got_commit_object *commit = NULL;
+ struct got_object_id *id = NULL;
+ int lineno;
+
+ *blamep = NULL;
+
+ err = got_object_open_by_path(&obj, repo, start_commit_id, path);
+ if (err)
+ return err;
+ if (got_object_get_type(obj) != GOT_OBJ_TYPE_BLOB) {
+ err = got_error(GOT_ERR_OBJ_TYPE);
+ goto done;
+ }
+
+ err = got_object_blob_open(&blob, repo, obj, 8192);
+ if (err)
+ goto done;
+
+ blame = calloc(1, sizeof(*blame));
+ if (blame == NULL)
+ return got_error_from_errno();
+
+ blame->f = got_opentemp();
+ if (blame->f == NULL) {
+ err = got_error_from_errno();
+ goto done;
+ }
+ err = dump_blob_and_count_lines(&blame->nlines, blame->f, blob);
+ if (err)
+ goto done;
+
+ blame->lines = calloc(blame->nlines, sizeof(*blame->lines));
+ if (blame->lines == NULL) {
+ err = got_error_from_errno();
+ goto done;
+ }
+
+ /* Loop over first-parent history and try to blame commits. */
+ err = got_object_open_as_commit(&commit, repo, start_commit_id);
+ if (err)
+ goto done;
+ id = got_object_id_dup(start_commit_id);
+ if (id == NULL) {
+ err = got_error_from_errno();
+ goto done;
+ }
+ while (1) {
+ struct got_object_qid *pid;
+ struct got_commit_object *pcommit;
+
+ pid = SIMPLEQ_FIRST(&commit->parent_ids);
+ if (pid == NULL)
+ break;
+
+ err = got_object_open_as_commit(&pcommit, repo, pid->id);
+ if (err)
+ break;
+
+ err = blame_commit(blame, id, pid->id, path, repo);
+ if (err) {
+ if (err->code == GOT_ERR_ITER_COMPLETED)
+ err = NULL;
+ got_object_commit_close(pcommit);
+ break;
+ }
+ free(id);
+ id = got_object_id_dup(pid->id);
+ got_object_commit_close(commit);
+ commit = pcommit;
+ if (id == NULL) {
+ err = got_error_from_errno();
+ goto done;
+ }
+ }
+
+ /* Annotate remaining non-annotated lines with last commit. */
+ for (lineno = 1; lineno < blame->nlines; lineno++) {
+ err = annotate_line(blame, lineno, id);
+ if (err)
+ break;
+ }
+
+done:
+ free(id);
+ if (obj)
+ got_object_close(obj);
+ if (blob)
+ got_object_blob_close(blob);
+ if (commit)
+ got_object_commit_close(commit);
+ if (err)
+ blame_close(blame);
+ else
+ *blamep = blame;
+
+ return err;
+}
+
+static const struct got_error *
+blame_line(struct got_object_id **id, struct got_blame *blame, int lineno)
+{
+ if (lineno < 1 || lineno > blame->nlines)
+ return got_error(GOT_ERR_RANGE);
+ *id = &blame->lines[lineno - 1].id;
+ return NULL;
+}
+
+static char *
+parse_next_line(FILE *f, size_t *len)
+{
+ char *line;
+ size_t linelen;
+ size_t lineno;
+ const char delim[3] = { '\0', '\0', '\0'};
+
+ line = fparseln(f, &linelen, &lineno, delim, 0);
+ if (len)
+ *len = linelen;
+ return line;
+}
+
+const struct got_error *
+got_blame(const char *path, struct got_object_id *start_commit_id,
+ struct got_repository *repo, FILE *outfile)
+{
+ const struct got_error *err = NULL;
+ struct got_blame *blame;
+ int lineno;
+ char *abspath;
+
+ if (asprintf(&abspath, "%s%s", path[0] == '/' ? "" : "/", path) == -1)
+ return got_error_from_errno();
+
+ err = blame_open(&blame, abspath, start_commit_id, repo);
+ if (err) {
+ free(abspath);
+ return err;
+ }
+
+ for (lineno = 1; lineno < blame->nlines; lineno++) {
+ struct got_object_id *id;
+ char *line, *id_str;
+
+ line = parse_next_line(blame->f, NULL);
+ if (line == NULL)
+ break;
+
+ err = blame_line(&id, blame, lineno);
+ if (err)
+ break;
+
+ err = got_object_id_str(&id_str, id);
+ if (err) {
+ free(line);
+ break;
+ }
+
+ fprintf(outfile, "%.8s %s\n", id_str, line);
+ free(line);
+ free(id_str);
+ }
+
+ blame_close(blame);
+ free(abspath);
+ return err;
+}
blob - 2fb482f6e9a84df16124b0c3c805b68224bea7b4
blob + e4f7dbb99d52e456c8c1133c3467a5c9f4956ea3
--- lib/diff.c
+++ lib/diff.c
#include "got_lib_diff.h"
#include "got_lib_path.h"
-const struct got_error *
-got_diff_blob(struct got_blob_object *blob1, struct got_blob_object *blob2,
- const char *label1, const char *label2, FILE *outfile)
+static const struct got_error *
+diff_blobs(struct got_blob_object *blob1, struct got_blob_object *blob2,
+ const char *label1, const char *label2, FILE *outfile,
+ struct got_diff_changes *changes)
{
struct got_diff_state ds;
struct got_diff_args args;
args.diff_context = 3;
flags |= D_PROTOTYPE;
- err = got_diffreg(&res, f1, f2, flags, &args, &ds, outfile);
+ err = got_diffreg(&res, f1, f2, flags, &args, &ds, outfile, changes);
done:
if (f1)
fclose(f1);
return err;
}
+const struct got_error *
+got_diff_blob(struct got_blob_object *blob1, struct got_blob_object *blob2,
+ const char *label1, const char *label2, FILE *outfile)
+{
+ return diff_blobs(blob1, blob2, label1, label2, outfile, NULL);
+}
+
+const struct got_error *
+got_diff_blob_lines_changed(struct got_diff_changes **changes,
+ struct got_blob_object *blob1, struct got_blob_object *blob2)
+{
+ const struct got_error *err = NULL;
+
+ *changes = calloc(1, sizeof(**changes));
+ if (*changes == NULL)
+ return got_error_from_errno();
+ SIMPLEQ_INIT(&(*changes)->entries);
+
+ err = diff_blobs(blob1, blob2, NULL, NULL, NULL, *changes);
+ if (err) {
+ got_diff_free_changes(*changes);
+ *changes = NULL;
+ }
+ return err;
+}
+
+void
+got_diff_free_changes(struct got_diff_changes *changes)
+{
+ struct got_diff_change *change;
+ while (!SIMPLEQ_EMPTY(&changes->entries)) {
+ change = SIMPLEQ_FIRST(&changes->entries);
+ SIMPLEQ_REMOVE_HEAD(&changes->entries, entry);
+ free(change);
+ }
+ free(changes);
+}
+
struct got_tree_entry *
match_entry_by_name(struct got_tree_entry *te1, struct got_tree_object *tree2)
{
blob - bae648774b96b2a6c828b2cd39cf1e0f49154897
blob + b2fe3246d9bd07001e60870521c45f5d37e08ac9
--- lib/diffreg.c
+++ lib/diffreg.c
int value;
};
-/*
- * The following struct is used to record change information when
- * doing a "context" or "unified" diff. (see routine "change" to
- * understand the highly mnemonic field names)
- */
-struct context_vec {
- int a; /* start line in old file */
- int b; /* end line in old file */
- int c; /* start line in new file */
- int d; /* end line in new file */
-};
-
static void diff_output(FILE *, const char *, ...);
-static int output(FILE *, struct got_diff_state *, struct got_diff_args *, const char *, FILE *, const char *, FILE *, int);
+static int output(FILE *, struct got_diff_changes *, struct got_diff_state *, struct got_diff_args *, const char *, FILE *, const char *, FILE *, int);
static void check(struct got_diff_state *, FILE *, FILE *, int);
static void uni_range(FILE *, int, int);
-static void dump_unified_vec(FILE *, struct got_diff_state *, struct got_diff_args *, FILE *, FILE *, int);
+static void dump_unified_vec(FILE *, struct got_diff_changes *, struct got_diff_state *, struct got_diff_args *, FILE *, FILE *, int);
static int prepare(struct got_diff_state *, int, FILE *, off_t, int);
static void prune(struct got_diff_state *);
static void equiv(struct line *, int, struct line *, int, int *);
static void unravel(struct got_diff_state *, int);
static int unsort(struct line *, int, int *);
-static int change(FILE *, struct got_diff_state *, struct got_diff_args *, const char *, FILE *, const char *, FILE *, int, int, int, int, int *);
+static int change(FILE *, struct got_diff_changes *, struct got_diff_state *, struct got_diff_args *, const char *, FILE *, const char *, FILE *, int, int, int, int, int *);
static void sort(struct line *, int);
static void print_header(FILE *, struct got_diff_state *, struct got_diff_args *, const char *, const char *);
static int asciifile(FILE *);
{
va_list ap;
+ if (outfile == NULL)
+ return;
+
va_start(ap, fmt);
vfprintf(outfile, fmt, ap);
va_end(ap);
const struct got_error *
got_diffreg(int *rval, FILE *f1, FILE *f2, int flags,
- struct got_diff_args *args, struct got_diff_state *ds, FILE *outfile)
+ struct got_diff_args *args, struct got_diff_state *ds, FILE *outfile,
+ struct got_diff_changes *changes)
{
const struct got_error *err = NULL;
int i, *p;
}
ds->ixnew = lp;
check(ds, f1, f2, flags);
- if (output(outfile, ds, args, args->label[0], f1, args->label[1], f2,
- flags))
+ if (output(outfile, changes, ds, args, args->label[0], f1,
+ args->label[1], f2, flags))
err = got_error_from_errno();
closem:
free(ds->J);
}
static int
-output(FILE *outfile, struct got_diff_state *ds, struct got_diff_args *args,
+output(FILE *outfile, struct got_diff_changes *changes,
+ struct got_diff_state *ds, struct got_diff_args *args,
const char *file1, FILE *f1, const char *file2, FILE *f2, int flags)
{
int m, i0, i1, j0, j1;
i1++;
j1 = ds->J[i1 + 1] - 1;
ds->J[i1] = j1;
- error = change(outfile, ds, args, file1, f1, file2, f2,
+ error = change(outfile, changes, ds, args, file1, f1, file2, f2,
i0, i1, j0, j1, &flags);
if (error)
return (error);
}
if (m == 0) {
- error = change(outfile, ds, args, file1, f1, file2, f2, 1, 0,
- 1, ds->len[1], &flags);
+ error = change(outfile, changes, ds, args, file1, f1, file2, f2,
+ 1, 0, 1, ds->len[1], &flags);
if (error)
return (error);
}
if (ds->anychange != 0)
- dump_unified_vec(outfile, ds, args, f1, f2, flags);
+ dump_unified_vec(outfile, changes, ds, args, f1, f2, flags);
return (0);
}
* lines missing from the to file.
*/
static int
-change(FILE *outfile, struct got_diff_state *ds, struct got_diff_args *args,
+change(FILE *outfile, struct got_diff_changes *changes,
+ struct got_diff_state *ds, struct got_diff_args *args,
const char *file1, FILE *f1, const char *file2, FILE *f2,
int a, int b, int c, int d, int *pflags)
{
* If this change is more than 'diff_context' lines from the
* previous change, dump the record and reset it.
*/
- dump_unified_vec(outfile, ds, args, f1, f2, *pflags);
+ dump_unified_vec(outfile, changes, ds, args, f1, f2,
+ *pflags);
}
ds->context_vec_ptr++;
ds->context_vec_ptr->a = a;
/* dump accumulated "unified" diff changes */
static void
-dump_unified_vec(FILE *outfile, struct got_diff_state *ds, struct got_diff_args *args,
+dump_unified_vec(FILE *outfile, struct got_diff_changes *changes,
+ struct got_diff_state *ds, struct got_diff_args *args,
FILE *f1, FILE *f2, int flags)
{
struct context_vec *cvp = ds->context_vec_start;
* are printed together.
*/
for (; cvp <= ds->context_vec_ptr; cvp++) {
+ if (changes) {
+ struct got_diff_change *change;
+ change = calloc(1, sizeof(*change));
+ if (change) {
+ memcpy(&change->cv, cvp, sizeof(change->cv));
+ SIMPLEQ_INSERT_TAIL(&changes->entries, change,
+ entry);
+ changes->nchanges++;
+ }
+ }
+
a = cvp->a;
b = cvp->b;
c = cvp->c;
blob - c317a77c20a024f9e60756d2993d242bce101ee9
blob + 93a2f44346f67706520e5e9383b00a94dd7244dd
--- lib/got_lib_diff.h
+++ lib/got_lib_diff.h
struct excludes *next;
};
+/*
+ * The following struct is used to record change information when
+ * doing a "context" or "unified" diff. (see routine "change" to
+ * understand the highly mnemonic field names)
+ */
+struct context_vec {
+ int a; /* start line in old file */
+ int b; /* end line in old file */
+ int c; /* start line in new file */
+ int d; /* end line in new file */
+};
+
+struct got_diff_change {
+ SIMPLEQ_ENTRY(got_diff_change) entry;
+ struct context_vec cv;
+};
+
+struct got_diff_changes {
+ size_t nchanges;
+ SIMPLEQ_HEAD(, got_diff_change) entries;
+};
+
struct got_diff_state {
int *J; /* will be overlaid on class */
int *class; /* will be overlaid on file[0] */
};
const struct got_error *got_diffreg(int *, FILE *,
- FILE *, int, struct got_diff_args *, struct got_diff_state *, FILE *);
+ FILE *, int, struct got_diff_args *, struct got_diff_state *, FILE *,
+ struct got_diff_changes *);
+
+const struct got_error *got_diff_blob_lines_changed(struct got_diff_changes **,
+ struct got_blob_object *, struct got_blob_object *);
+void got_diff_free_changes(struct got_diff_changes *);