Commit Diff


commit - 63581804340e880bf611c6a4a59eda26c503799f
commit + 84451b3ef755f3226d0d79af367632e5f3a830e7
blob - b53ca469a18871cc2f6af334dab25028599c6488
blob + c787aadf05e2afab61bd34976f7349912252e6da
--- include/got_blame.h
+++ include/got_blame.h
@@ -14,5 +14,22 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+/*
+ * Write an annotated version of a file at a given in-repository path,
+ * as found in the commit specified by ID, to the specified output file.
+ */
 const struct got_error *got_blame(const char *, struct got_object_id *,
     struct got_repository *, FILE *);
+
+/*
+ * Like got_blame() but instead of generating an output file invoke
+ * a callback whenever an annotation has been computed for a line.
+ *
+ * The callback receives the provided void * argument, the total number
+ * of lines of the annotated file, a line number, and the ID of the commit
+ * which last changed this line.
+ */
+const struct got_error *got_blame_incremental(const char *,
+    struct got_object_id *, struct got_repository *,
+    const struct got_error *(*cb)(void *, int, int, struct got_object_id *),
+    void *);
blob - 52bd46fe2b3ac6189e515513a49f08371cabec86
blob + 16aa7b925adeefc4dbd6703da310ba7a98900ff4
--- include/got_object.h
+++ include/got_object.h
@@ -190,11 +190,13 @@ const struct got_error *got_object_blob_read_block(siz
 
 /*
  * Read the entire content of a blob and write it to the specified file.
- * Flush and rewind the file as well, and indicate the amount of bytes
- * written in the size_t output argument.
+ * Flush and rewind the file as well. Indicate the amount of bytes
+ * written in the first size_t output argument, and the number of lines
+ * in the file in the second size_t output argument (NULL can be passed
+ * for either output argument).
  */
-const struct got_error *got_object_blob_dump_to_file(size_t *, FILE *,
-    struct got_blob_object *);
+const struct got_error *got_object_blob_dump_to_file(size_t *, size_t *,
+    FILE *, struct got_blob_object *);
 
 const struct got_error *
 got_object_open_as_commit(struct got_commit_object **,
blob - 7f25ad2d36b9f21d0db48ab55c858472cf281a7e
blob + 1a2d03cda24f7afe7ef3cb27b7b9683cbf87fe65
--- lib/blame.c
+++ lib/blame.c
@@ -47,58 +47,32 @@ struct got_blame {
 };
 
 static const struct got_error *
-dump_blob_and_count_lines(size_t *nlines, FILE *outfile,
-    struct got_blob_object *blob)
+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 *),
+    void *arg)
 {
 	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 void
-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;
+		return got_error(GOT_ERR_RANGE);
 	
 	line = &blame->lines[lineno - 1];
 	if (line->annotated)
-		return;
+		return NULL;
 
 	memcpy(&line->id, id, sizeof(line->id));
 	line->annotated = 1;
+	if (cb)
+		err = cb(arg, blame->nlines, lineno, id);
+	return err;
 }
 
 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)
+    struct got_object_id *pid, const char *path, struct got_repository *repo,
+    const struct got_error *(*cb)(void *, int, int, struct got_object_id *),
+    void *arg)
 {
 	const struct got_error *err = NULL;
 	struct got_object *obj = NULL, *pobj = NULL;
@@ -152,8 +126,11 @@ blame_commit(struct got_blame *blame, struct got_objec
 			int a = change->cv.a;
 			int b = change->cv.b;
 			int lineno;
-			for (lineno = a; lineno <= b; lineno++)
-				annotate_line(blame, lineno, id);
+			for (lineno = a; lineno <= b; lineno++) {
+				err = annotate_line(blame, lineno, id, cb, arg);
+				if (err)
+					goto done;
+			}
 		}
 	}
 done:
@@ -179,7 +156,9 @@ blame_close(struct got_blame *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)
+    struct got_object_id *start_commit_id, struct got_repository *repo,
+    const struct got_error *(*cb)(void *, int, int, struct got_object_id *),
+    void *arg)
 {
 	const struct got_error *err = NULL;
 	struct got_object *obj = NULL;
@@ -212,7 +191,8 @@ blame_open(struct got_blame **blamep, const char *path
 		err = got_error_from_errno();
 		goto done;
 	}
-	err = dump_blob_and_count_lines(&blame->nlines, blame->f, blob);
+	err = got_object_blob_dump_to_file(NULL, &blame->nlines, blame->f,
+	    blob);
 	if (err)
 		goto done;
 
@@ -238,7 +218,7 @@ blame_open(struct got_blame **blamep, const char *path
 		if (pid == NULL)
 			break;
 
-		err = blame_commit(blame, id, pid->id, path, repo);
+		err = blame_commit(blame, id, pid->id, path, repo, cb, arg);
 		if (err) {
 			if (err->code == GOT_ERR_ITER_COMPLETED)
 				err = NULL;
@@ -254,12 +234,15 @@ blame_open(struct got_blame **blamep, const char *path
 		got_object_commit_close(commit);
 		err = got_object_open_as_commit(&commit, repo, id);
 		if (err)
-			break;
+			goto done;
 	}
 
 	/* Annotate remaining non-annotated lines with last commit. */
-	for (lineno = 1; lineno <= blame->nlines; lineno++)
-		annotate_line(blame, lineno, id);
+	for (lineno = 1; lineno <= blame->nlines; lineno++) {
+		err = annotate_line(blame, lineno, id, cb, arg);
+		if (err)
+			goto done;
+	}
 
 done:
 	free(id);
@@ -313,7 +296,7 @@ got_blame(const char *path, struct got_object_id *star
 	if (asprintf(&abspath, "%s%s", path[0] == '/' ? "" : "/", path) == -1)
 		return got_error_from_errno();
 
-	err = blame_open(&blame, abspath, start_commit_id, repo);
+	err = blame_open(&blame, abspath, start_commit_id, repo, NULL, NULL);
 	if (err) {
 		free(abspath);
 		return err;
@@ -346,3 +329,22 @@ got_blame(const char *path, struct got_object_id *star
 	free(abspath);
 	return err;
 }
+
+const struct got_error *
+got_blame_incremental(const char *path, struct got_object_id *commit_id,
+    struct got_repository *repo,
+    const struct got_error *(*cb)(void *, int, int, struct got_object_id *),
+    void *arg)
+{
+	const struct got_error *err = NULL;
+	struct got_blame *blame;
+	char *abspath;
+
+	if (asprintf(&abspath, "%s%s", path[0] == '/' ? "" : "/", path) == -1)
+		return got_error_from_errno();
+
+	err = blame_open(&blame, abspath, commit_id, repo, cb, arg);
+	free(abspath);
+	blame_close(blame);
+	return err;
+}
blob - abdee829510822e65a88b390d5b9f2bddb49d525
blob + 8fc337d544e52f440acacbbc75a073499ead13c7
--- lib/diff.c
+++ lib/diff.c
@@ -67,7 +67,7 @@ diff_blobs(struct got_blob_object *blob1, struct got_b
 	size1 = 0;
 	if (blob1) {
 		idstr1 = got_object_blob_id_str(blob1, hex1, sizeof(hex1));
-		err = got_object_blob_dump_to_file(&size1, f1, blob1);
+		err = got_object_blob_dump_to_file(&size1, NULL, f1, blob1);
 		if (err)
 			goto done;
 	} else
@@ -76,7 +76,7 @@ diff_blobs(struct got_blob_object *blob1, struct got_b
 	size2 = 0;
 	if (blob2) {
 		idstr2 = got_object_blob_id_str(blob2, hex2, sizeof(hex2));
-		err = got_object_blob_dump_to_file(&size2, f2, blob2);
+		err = got_object_blob_dump_to_file(&size2, NULL, f2, blob2);
 		if (err)
 			goto done;
 	} else
blob - 4cf62dd8461ada927c3e2522739feadbe7a4c211
blob + 8bc417f477f5c5bb085172a3adf35471edc7f006
--- lib/object.c
+++ lib/object.c
@@ -1438,13 +1438,19 @@ got_object_blob_read_block(size_t *outlenp, struct got
 }
 
 const struct got_error *
-got_object_blob_dump_to_file(size_t *total_len, FILE *outfile,
-    struct got_blob_object *blob)
+got_object_blob_dump_to_file(size_t *total_len, 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;
+
+	if (total_len)
+		*total_len = 0;
+	if (nlines)
+		*nlines = 0;
 
-	*total_len = 0;
 	hdrlen = got_object_blob_get_hdrlen(blob);
 	do {
 		err = got_object_blob_read_block(&len, blob);
@@ -1452,10 +1458,17 @@ got_object_blob_dump_to_file(size_t *total_len, FILE *
 			return err;
 		if (len == 0)
 			break;
-		*total_len += len;
+		if (total_len)
+			*total_len += len;
+		buf = got_object_blob_get_read_buf(blob);
+		if (nlines) {
+			for (i = 0; i < len; i++) {
+				if (buf[i] == '\n')
+					(*nlines)++;
+			}
+		}
 		/* Skip blob object header first time around. */
-		fwrite(got_object_blob_get_read_buf(blob) + hdrlen,
-		    len - hdrlen, 1, outfile);
+		fwrite(buf + hdrlen, len - hdrlen, 1, outfile);
 		hdrlen = 0;
 	} while (len != 0);
 
blob - 76ebd6a1e50dc0e61ce0a0a9ad35306dff853b52
blob + 06bda662dc489c32771945b9cdb976211c884a6e
--- tog/Makefile
+++ tog/Makefile
@@ -7,7 +7,7 @@ SRCS=		tog.c blame.c commit_graph.c delta.c diff.c dif
 		sha1.c worktree.c utf8.c inflate.c
 
 CPPFLAGS = -I${.CURDIR}/../include -I${.CURDIR}/../lib
-LDADD = -lpanel -lncursesw -lutil -lz
+LDADD = -lpanel -lncursesw -lutil -lz -lpthread
 DPADD = ${LIBZ} ${LIBUTIL}
 .if defined(PROFILE)
 CC = gcc
blob - c66824027f131e9b4a8456718212eb87e469c90c
blob + 343f4c6f06df7a59fcff7d6df42e716c0f0b1ea3
--- tog/tog.c
+++ tog/tog.c
@@ -33,6 +33,7 @@
 #include <limits.h>
 #include <wchar.h>
 #include <time.h>
+#include <pthread.h>
 
 #include "got_error.h"
 #include "got_object.h"
@@ -1047,25 +1048,174 @@ usage_blame(void)
 	    getprogname());
 	exit(1);
 }
+
+struct tog_blame_line {
+	int annotated;
+	struct got_object_id *id;
+};
 
 static const struct got_error *
+draw_blame(WINDOW *window, FILE *f, struct tog_blame_line *lines, int nlines,
+    int *first_displayed_line, int *last_displayed_line, int *eof,
+    int max_lines)
+{
+	const struct got_error *err;
+	int lineno = 0, nprinted = 0;
+	char *line;
+	size_t len;
+	wchar_t *wline;
+	int width;
+	struct tog_blame_line *blame_line;
+
+	rewind(f);
+	werase(window);
+
+	*eof = 0;
+	while (nprinted < max_lines) {
+		line = parse_next_line(f, &len);
+		if (line == NULL) {
+			*eof = 1;
+			break;
+		}
+		if (++lineno < *first_displayed_line) {
+			free(line);
+			continue;
+		}
+
+		err = format_line(&wline, &width, line, COLS - 9);
+		if (err) {
+			free(line);
+			return err;
+		}
+
+		blame_line = &lines[lineno - 1];
+		if (blame_line->annotated) {
+			char *id_str;
+			err = got_object_id_str(&id_str, blame_line->id);
+			if (err) {
+				free(line);
+				return err;
+			}
+			wprintw(window, "%.8s ", id_str);
+			free(id_str);
+		} else
+			waddstr(window, "         ");
+
+		waddwstr(window, wline);
+		if (width < COLS - 9)
+			waddch(window, '\n');
+		if (++nprinted == 1)
+			*first_displayed_line = lineno;
+		free(line);
+	}
+	*last_displayed_line = lineno;
+
+	update_panels();
+	doupdate();
+
+	return NULL;
+}
+
+struct tog_blame_cb_args {
+	pthread_mutex_t *mutex;
+	struct tog_blame_line *lines; /* one per line */
+	int nlines;
+
+	FILE *f;
+	WINDOW *window;
+	int *first_displayed_line;
+	int *last_displayed_line;
+};
+
+static const struct got_error *
+blame_cb(void *arg, int nlines, int lineno, struct got_object_id *id)
+{
+	const struct got_error *err = NULL;
+	struct tog_blame_cb_args *a = arg;
+	struct tog_blame_line *line;
+	int eof;
+
+	if (nlines != a->nlines || lineno < 1 || lineno > a->nlines)
+		return got_error(GOT_ERR_RANGE);
+
+	if (pthread_mutex_lock(a->mutex) != 0)
+		return got_error_from_errno();
+
+	line = &a->lines[lineno - 1];
+	line->id = got_object_id_dup(id);
+	if (line->id == NULL) {
+		err = got_error_from_errno();
+		goto done;
+	}
+	line->annotated = 1;
+
+	err = draw_blame(a->window, a->f, a->lines, a->nlines,
+	    a->first_displayed_line, a->last_displayed_line, &eof, LINES);
+done:
+	if (pthread_mutex_unlock(a->mutex) != 0)
+		return got_error_from_errno();
+	return err;
+}
+
+struct tog_blame_thread_args {
+	const char *path;
+	struct got_object_id *commit_id;
+	struct got_repository *repo;
+	void *blame_cb_args;
+};
+
+static void *
+blame_thread(void *arg)
+{
+	struct tog_blame_thread_args *a = arg;
+	return (void *)got_blame_incremental(a->path, a->commit_id, a->repo,
+	    blame_cb, a->blame_cb_args);
+}
+
+static const struct got_error *
 show_blame_view(const char *path, struct got_object_id *commit_id,
     struct got_repository *repo)
 {
 	const struct got_error *err = NULL;
-	FILE *f;
 	int ch, done = 0, first_displayed_line = 1, last_displayed_line = LINES;
 	int eof, i;
+	struct got_object *obj = NULL;
+	struct got_blob_object *blob = NULL;
+	FILE *f = NULL;
+	size_t filesize, nlines;
+	struct tog_blame_line *lines = NULL;
+	pthread_t thread = NULL;
+	pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+	struct tog_blame_cb_args blame_cb_args;
+	struct tog_blame_thread_args blame_thread_args;
 
-	f = got_opentemp();
-	if (f == NULL)
-		return got_error_from_errno();
+	err = got_object_open_by_path(&obj, repo, commit_id, path);
+	if (err)
+		goto done;
+	if (got_object_get_type(obj) != GOT_OBJ_TYPE_BLOB) {
+		err = got_error(GOT_ERR_OBJ_TYPE);
+		got_object_close(obj);
+		goto done;
+	}
 
-	err = got_blame(path, commit_id, repo, f);
+	err = got_object_blob_open(&blob, repo, obj, 8192);
+	got_object_close(obj);
 	if (err)
 		goto done;
+	f = got_opentemp();
+	if (f == NULL) {
+		err = got_error_from_errno();
+		goto done;
+	}
+	err = got_object_blob_dump_to_file(&filesize, &nlines, f, blob);
+	if (err)
+		goto done;
 
-	fflush(f);
+	lines = calloc(nlines, sizeof(*lines));
+	if (lines == NULL) {
+		err = got_error_from_errno();
+		goto done;
+	}
 
 	if (tog_blame_view.window == NULL) {
 		tog_blame_view.window = newwin(0, 0, 0, 0);
@@ -1080,14 +1230,49 @@ show_blame_view(const char *path, struct got_object_id
 	} else
 		show_panel(tog_blame_view.panel);
 
+	if (pthread_mutex_init(&mutex, NULL) != 0) {
+		err = got_error_from_errno();
+		goto done;
+	}
+	blame_cb_args.lines = lines;
+	blame_cb_args.nlines = nlines;
+	blame_cb_args.mutex = &mutex;
+	blame_cb_args.f = f;
+	blame_cb_args.window = tog_blame_view.window;
+	blame_cb_args.first_displayed_line = &first_displayed_line;
+	blame_cb_args.last_displayed_line = &last_displayed_line;
+
+	blame_thread_args.path = path;
+	blame_thread_args.commit_id = commit_id;
+	blame_thread_args.repo = repo;
+	blame_thread_args.blame_cb_args = &blame_cb_args;
+
+	if (pthread_create(&thread, NULL, blame_thread,
+	    &blame_thread_args) != 0) {
+		err = got_error_from_errno();
+		goto done;
+	}
+
 	while (!done) {
-		err = draw_file(tog_blame_view.window, f, &first_displayed_line,
-		    &last_displayed_line, &eof, LINES);
+		if (pthread_mutex_lock(&mutex) != 0) {
+			err = got_error_from_errno();
+			goto done;
+		}
+		err = draw_blame(tog_blame_view.window, f, lines, nlines,
+		    &first_displayed_line, &last_displayed_line, &eof, LINES);
+		if (pthread_mutex_unlock(&mutex) != 0) {
+			err = got_error_from_errno();
+			goto done;
+		}
 		if (err)
 			break;
 		nodelay(stdscr, FALSE);
 		ch = wgetch(tog_blame_view.window);
 		nodelay(stdscr, TRUE);
+		if (pthread_mutex_lock(&mutex) != 0) {
+			err = got_error_from_errno();
+			goto done;
+		}
 		switch (ch) {
 			case 'q':
 				done = 1;
@@ -1123,10 +1308,22 @@ show_blame_view(const char *path, struct got_object_id
 				break;
 			default:
 				break;
+		}
+		if (pthread_mutex_unlock(&mutex) != 0) {
+			err = got_error_from_errno();
+			goto done;
 		}
 	}
 done:
-	fclose(f);
+	if (blob)
+		got_object_blob_close(blob);
+	if (f)
+		fclose(f);
+	free(lines);
+	if (thread) {
+		if (pthread_join(thread, (void **)&err) != 0)
+			err = got_error_from_errno();
+	}
 	return err;
 }