Commit c35a7943488e43a4fc6c48a00cf858ba8f410947

Stefan Sperling 2018-07-12T15:04:11

account for line shift in blame; lots of help from tb@

diff --git a/got/Makefile b/got/Makefile
index 1dcf38b..3462401 100644
--- a/got/Makefile
+++ b/got/Makefile
@@ -1,10 +1,10 @@
 .PATH:${.CURDIR}/../lib
 
 PROG=		got
-SRCS=		got.c blame.c commit_graph.c delta.c diff.c diffreg.c error.c \
-		fileindex.c object.c object_idcache.c object_idset.c opentemp.c \
-		path.c pack.c privsep.c reference.c repository.c sha1.c \
-		worktree.c inflate.c
+SRCS=		got.c blame.c commit_graph.c delta.c diff.c diffoffset.c \
+		diffreg.c error.c fileindex.c object.c object_idcache.c \
+		object_idset.c opentemp.c path.c pack.c privsep.c reference.c \
+		repository.c sha1.c worktree.c inflate.c
 
 CPPFLAGS = -I${.CURDIR}/../include -I${.CURDIR}/../lib
 LDADD = -lutil -lz
diff --git a/lib/blame.c b/lib/blame.c
index e5c55ef..67a5c59 100644
--- a/lib/blame.c
+++ b/lib/blame.c
@@ -34,18 +34,65 @@
 #include "got_lib_delta.h"
 #include "got_lib_object.h"
 #include "got_lib_diff.h"
+#include "got_lib_diffoffset.h"
 
 struct got_blame_line {
 	int annotated;
 	struct got_object_id id;
 };
 
+struct got_blame_diff_offsets {
+	struct got_diffoffset_chunks *chunks;
+	struct got_object_id *commit_id;
+	SLIST_ENTRY(got_blame_diff_offsets) entry;
+};
+
+SLIST_HEAD(got_blame_diff_offsets_list, got_blame_diff_offsets);
+
 struct got_blame {
 	FILE *f;
 	size_t nlines;
 	struct got_blame_line *lines; /* one per line */
+	int ncommits;
+	struct got_blame_diff_offsets_list diff_offsets_list;
 };
 
+static void
+free_diff_offsets(struct got_blame_diff_offsets *diff_offsets)
+{
+	if (diff_offsets->chunks)
+		got_diffoffset_free(diff_offsets->chunks);
+	free(diff_offsets->commit_id);
+	free(diff_offsets);
+}
+
+static const struct got_error *
+alloc_diff_offsets(struct got_blame_diff_offsets **diff_offsets,
+    struct got_object_id *commit_id)
+{
+	const struct got_error *err = NULL;
+
+	*diff_offsets = calloc(1, sizeof(**diff_offsets));
+	if (*diff_offsets == NULL)
+		return got_error_from_errno();
+
+	(*diff_offsets)->commit_id = got_object_id_dup(commit_id);
+	if ((*diff_offsets)->commit_id == NULL) {
+		err = got_error_from_errno();
+		free_diff_offsets(*diff_offsets);
+		*diff_offsets = NULL;
+		return err;
+	}
+
+	err = got_diffoffset_alloc(&(*diff_offsets)->chunks);
+	if (err) {
+		free_diff_offsets(*diff_offsets);
+		return err;
+	}
+
+	return NULL;
+}
+
 static const struct got_error *
 annotate_line(struct got_blame *blame, int lineno, struct got_object_id *id,
     const struct got_error *(*cb)(void *, int, int, struct got_object_id *),
@@ -68,6 +115,67 @@ annotate_line(struct got_blame *blame, int lineno, struct got_object_id *id,
 	return err;
 }
 
+static int
+get_blamed_line(struct got_blame_diff_offsets_list *diff_offsets_list,
+    int lineno)
+{
+	struct got_blame_diff_offsets *diff_offsets;
+
+	SLIST_FOREACH(diff_offsets, diff_offsets_list, entry)
+		lineno = got_diffoffset_get(diff_offsets->chunks, lineno);
+
+	return lineno;
+}
+
+static const struct got_error *
+blame_changes(struct got_blame *blame, struct got_diff_changes *changes,
+    struct got_object_id *commit_id,
+    const struct got_error *(*cb)(void *, int, int, struct got_object_id *),
+    void *arg)
+{
+	const struct got_error *err = NULL;
+	struct got_diff_change *change;
+	struct got_blame_diff_offsets *diff_offsets;
+
+	SIMPLEQ_FOREACH(change, &changes->entries, entry) {
+		int c = change->cv.c;
+		int d = change->cv.d;
+		int new_lineno = c;
+		int new_length = (c < d ? d - c + 1 : (c == d ? 1 : 0));
+		int ln;
+
+		for (ln = new_lineno; ln < new_lineno + new_length; ln++) {
+			err = annotate_line(blame,
+			    get_blamed_line(&blame->diff_offsets_list, ln),
+			    commit_id, cb, arg);
+			if (err)
+				return err;
+		}
+	}
+
+	err = alloc_diff_offsets(&diff_offsets, commit_id);
+	if (err)
+		return err;
+	SIMPLEQ_FOREACH(change, &changes->entries, entry) {
+		int a = change->cv.a;
+		int b = change->cv.b;
+		int c = change->cv.c;
+		int d = change->cv.d;
+		int old_lineno = a;
+		int old_length = (a < b ? b - a + 1 : (a == b ? 1 : 0));
+		int new_lineno = c;
+		int new_length = (c < d ? d - c + 1 : (c == d ? 1 : 0));
+
+		err = got_diffoffset_add(diff_offsets->chunks,
+		    old_lineno, old_length, new_lineno, new_length);
+		if (err)
+			return err;
+	}
+	SLIST_INSERT_HEAD(&blame->diff_offsets_list, diff_offsets, entry);
+
+	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,
@@ -119,22 +227,12 @@ blame_commit(struct got_blame *blame, struct got_object_id *id,
 	if (err)
 		goto done;
 
-	err = got_diff_blob_lines_changed(&changes, blob, pblob);
+	err = got_diff_blob_lines_changed(&changes, pblob, blob);
 	if (err)
 		goto done;
 
 	if (changes) {
-		struct got_diff_change *change;
-		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, cb, arg);
-				if (err)
-					goto done;
-			}
-		}
+		err = blame_changes(blame, changes, id, cb, arg);
 	} else if (cb)
 		err = cb(arg, blame->nlines, -1, id);
 done:
@@ -152,9 +250,16 @@ done:
 static void
 blame_close(struct got_blame *blame)
 {
+	struct got_blame_diff_offsets *diff_offsets;
+
 	if (blame->f)
 		fclose(blame->f);
 	free(blame->lines);
+	while (!SLIST_EMPTY(&blame->diff_offsets_list)) {
+		diff_offsets = SLIST_FIRST(&blame->diff_offsets_list);
+		SLIST_REMOVE_HEAD(&blame->diff_offsets_list, entry);
+		free_diff_offsets(diff_offsets);
+	}
 	free(blame);
 }
 
diff --git a/lib/diffoffset.c b/lib/diffoffset.c
new file mode 100644
index 0000000..6d1af1a
--- /dev/null
+++ b/lib/diffoffset.c
@@ -0,0 +1,131 @@
+/*
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <sha1.h>
+#include <zlib.h>
+
+#include "got_object.h"
+
+#include "got_error.h"
+#include "got_lib_delta.h"
+#include "got_lib_inflate.h"
+#include "got_lib_object.h"
+#include "got_lib_diffoffset.h"
+
+/*
+ * A line offset between an old file and a new file, derived from diff chunk
+ * header info @@ -old_lineno,old_length +new_lineno,new_length @@ in a diff
+ * with zero context lines (as in diff -U0 old-file new-file).
+ */
+struct got_diffoffset_chunk {
+	int lineno;	/* first line which has shifted */
+	int offset;	/* applies to subsequent lines until next chunk */
+	SIMPLEQ_ENTRY(got_diffoffset_chunk) entry;
+};
+
+static struct got_diffoffset_chunk *
+alloc_chunk(int lineno, int offset)
+{
+	struct got_diffoffset_chunk *chunk;
+
+	chunk = calloc(1, sizeof(*chunk));
+	if (chunk == NULL)
+		return NULL;
+
+	chunk->lineno = lineno;
+	chunk->offset = offset;
+
+	return chunk;
+}
+
+const struct got_error *
+got_diffoffset_alloc(struct got_diffoffset_chunks **chunks)
+{
+	const struct got_error *err = NULL;
+	struct got_diffoffset_chunk *first;
+
+	first = alloc_chunk(0, 0);
+	if (first == NULL)
+		return got_error_from_errno();
+
+	*chunks = calloc(1, sizeof(**chunks));
+	if (*chunks == NULL) {
+		err = got_error_from_errno();
+		free(first);
+		return err;
+	}
+
+	SIMPLEQ_INIT(*chunks);
+	SIMPLEQ_INSERT_HEAD(*chunks, first, entry);
+
+	return NULL;
+}
+
+void
+got_diffoffset_free(struct got_diffoffset_chunks *chunks)
+{
+	struct got_diffoffset_chunk *chunk;
+
+	while (!SIMPLEQ_EMPTY(chunks)) {
+		chunk = SIMPLEQ_FIRST(chunks);
+		SIMPLEQ_REMOVE_HEAD(chunks, entry);
+		free(chunk);
+	}
+	free(chunks);
+}
+
+const struct got_error *
+got_diffoffset_add(struct got_diffoffset_chunks *chunks,
+    int old_lineno, int old_length, int new_lineno, int new_length)
+{
+	struct got_diffoffset_chunk *chunk1, *chunk2;
+
+	chunk1 = alloc_chunk(old_lineno, new_lineno - old_lineno);
+	if (chunk1 == NULL)
+		return got_error_from_errno();
+
+	chunk2 = alloc_chunk(old_lineno + old_length,
+	    new_lineno - old_lineno + new_length - old_length);
+	if (chunk2 == NULL) {
+		const struct got_error *err = got_error_from_errno();
+		free(chunk1);
+		return err;
+	}
+
+	SIMPLEQ_INSERT_TAIL(chunks, chunk1, entry);
+	SIMPLEQ_INSERT_TAIL(chunks, chunk2, entry);
+	return NULL;
+}
+
+int
+got_diffoffset_get(struct got_diffoffset_chunks *chunks, int lineno)
+{
+	struct got_diffoffset_chunk *chunk, *prev;
+
+	prev = SIMPLEQ_FIRST(chunks);
+	SIMPLEQ_FOREACH(chunk, chunks, entry) {
+		if (chunk->lineno > lineno)
+			break;
+		prev = chunk;
+	}
+
+	return lineno + prev->offset;
+}
diff --git a/lib/got_lib_diffoffset.h b/lib/got_lib_diffoffset.h
new file mode 100644
index 0000000..705d33a
--- /dev/null
+++ b/lib/got_lib_diffoffset.h
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+SIMPLEQ_HEAD(got_diffoffset_chunks, got_diffoffset_chunk);
+
+const struct got_error *got_diffoffset_alloc(struct got_diffoffset_chunks **);
+void got_diffoffset_free(struct got_diffoffset_chunks *);
+const struct got_error *got_diffoffset_add(struct got_diffoffset_chunks *,
+    int, int, int, int);
+int got_diffoffset_get(struct got_diffoffset_chunks *, int);
diff --git a/tog/Makefile b/tog/Makefile
index 06bda66..dbaa942 100644
--- a/tog/Makefile
+++ b/tog/Makefile
@@ -1,10 +1,10 @@
 .PATH:${.CURDIR}/../lib
 
 PROG=		tog
-SRCS=		tog.c blame.c commit_graph.c delta.c diff.c diffreg.c error.c \
-		fileindex.c object.c object_idcache.c object_idset.c \
-		opentemp.c path.c pack.c privsep.c reference.c repository.c \
-		sha1.c worktree.c utf8.c inflate.c
+SRCS=		tog.c blame.c commit_graph.c delta.c diff.c diffoffset.c \
+		diffreg.c error.c fileindex.c object.c object_idcache.c \
+		object_idset.c opentemp.c path.c pack.c privsep.c \
+		reference.c repository.c sha1.c worktree.c utf8.c inflate.c
 
 CPPFLAGS = -I${.CURDIR}/../include -I${.CURDIR}/../lib
 LDADD = -lpanel -lncursesw -lutil -lz -lpthread