Commit 78695fb724a1e3e628efb42140d48dbd88d562b3

Stefan Sperling 2019-08-12T12:38:08

fix blame with single-commit history and with files without \n

diff --git a/lib/blame.c b/lib/blame.c
index e00d1af..93f2913 100644
--- a/lib/blame.c
+++ b/lib/blame.c
@@ -211,43 +211,45 @@ blame_commit(struct got_blame *blame, struct got_object_id *id,
 		goto done;
 	}
 
-	err = got_object_id_by_path(&pobj_id, repo, pid, path);
-	if (err) {
-		if (err->code == GOT_ERR_NO_TREE_ENTRY) {
-			/* Blob's history began in previous commit. */
-			err = got_error(GOT_ERR_ITER_COMPLETED);
+	if (pid) {
+		err = got_object_id_by_path(&pobj_id, repo, pid, path);
+		if (err) {
+			if (err->code == GOT_ERR_NO_TREE_ENTRY) {
+				/* Blob's history began in previous commit. */
+				err = got_error(GOT_ERR_ITER_COMPLETED);
+			}
+			goto done;
 		}
-		goto done;
-	}
 
-	/* If IDs match then don't bother with diffing. */
-	if (got_object_id_cmp(obj_id, pobj_id) == 0) {
-		if (cb)
-			err = cb(arg, blame->nlines, -1, id);
-		goto done;
-	}
+		/* If IDs match then don't bother with diffing. */
+		if (got_object_id_cmp(obj_id, pobj_id) == 0) {
+			if (cb)
+				err = cb(arg, blame->nlines, -1, id);
+			goto done;
+		}
 
-	err = got_object_open(&pobj, repo, pobj_id);
-	if (err)
-		goto done;
+		err = got_object_open(&pobj, repo, pobj_id);
+		if (err)
+			goto done;
 
-	if (pobj->type != 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 (pobj->type != 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;
+		}
+
+		err = got_object_blob_open(&pblob, repo, pobj, 8192);
+		if (err)
+			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, pblob, blob);
 	if (err)
 		goto done;
@@ -300,7 +302,7 @@ blame_open(struct got_blame **blamep, const char *path,
 	struct got_object_id *obj_id = NULL;
 	struct got_blob_object *blob = NULL;
 	struct got_blame *blame = NULL;
-	struct got_object_id *id = NULL;
+	struct got_object_id *id = NULL, *parent_id = NULL;
 	int lineno;
 	struct got_commit_graph *graph = NULL;
 
@@ -352,12 +354,14 @@ blame_open(struct got_blame **blamep, const char *path,
 
 	id = NULL;
 	for (;;) {
-		struct got_object_id *next_id;
-
-		err = got_commit_graph_iter_next(&next_id, graph);
+		err = got_commit_graph_iter_next(&parent_id, graph);
 		if (err) {
 			if (err->code == GOT_ERR_ITER_COMPLETED) {
-				err = NULL;
+				if (id)
+					err = blame_commit(blame, id,
+					    parent_id, path, repo, cb, arg);
+				else
+					err = NULL;
 				break;
 			}
 			if (err->code != GOT_ERR_ITER_NEED_MORE)
@@ -365,13 +369,10 @@ blame_open(struct got_blame **blamep, const char *path,
 			err = got_commit_graph_fetch_commits(graph, 1, repo);
 			if (err)
 				break;
-			else
-				continue;
+			continue;
 		}
-		if (next_id == NULL)
-			break;
 		if (id) {
-			err = blame_commit(blame, id, next_id, path, repo,
+			err = blame_commit(blame, id, parent_id, path, repo,
 			    cb, arg);
 			if (err) {
 				if (err->code == GOT_ERR_ITER_COMPLETED)
@@ -381,7 +382,7 @@ blame_open(struct got_blame **blamep, const char *path,
 			if (blame->nannotated == blame->nlines)
 				break;
 		}
-		id = next_id;
+		id = parent_id;
 	}
 
 	if (id && blame->nannotated < blame->nlines) {
diff --git a/lib/object.c b/lib/object.c
index 42e0035..90e8331 100644
--- a/lib/object.c
+++ b/lib/object.c
@@ -1161,34 +1161,46 @@ got_object_blob_dump_to_file(size_t *total_len, int *nlines,
 		if (len == 0)
 			break;
 		buf = got_object_blob_get_read_buf(blob);
-		for (i = hdrlen; i < len; i++) {
-			if (buf[i] != '\n')
-				continue;
-			if (nlines)
-				(*nlines)++;
-			if (line_offsets && nlines && noffsets < *nlines) {
-				off_t *o = recallocarray(*line_offsets,
-				    noffsets, *nlines, sizeof(**line_offsets));
-				if (o == NULL) {
-					free(*line_offsets);
-					*line_offsets = NULL;
-					return got_error_from_errno(
-					    "recallocarray");
-				}
-				*line_offsets = o;
-				noffsets = *nlines;
+		if (line_offsets && nlines) {
+			if (*line_offsets == NULL) {
+				/* Have some data but perhaps no '\n'. */
+				noffsets = 1;
+				*nlines = 1;
+				*line_offsets = malloc(sizeof(**line_offsets));
+				if (*line_offsets == NULL)
+					return got_error_from_errno("malloc");
+				(*line_offsets)[0] = 0;
 			}
-			if (line_offsets && nlines && total_len) {
-				(*line_offsets)[*nlines - 1] = off;
-				off = *total_len + i + 1 - got_object_blob_get_hdrlen(blob);
+			/* Scan '\n' offsets in this chunk of data. */
+			for (i = hdrlen; i < len; i++) {
+				if (i > hdrlen && buf[i] == '\n')
+					(*nlines)++;
+				if (noffsets < *nlines) {
+					off_t *o = recallocarray(*line_offsets,
+					    noffsets, *nlines,
+					    sizeof(**line_offsets));
+					if (o == NULL) {
+						free(*line_offsets);
+						*line_offsets = NULL;
+						return got_error_from_errno(
+						    "recallocarray");
+					}
+					*line_offsets = o;
+					noffsets = *nlines;
+				}
+				if (total_len) {
+					(*line_offsets)[*nlines - 1] = off;
+					off = *total_len + i -
+					    got_object_blob_get_hdrlen(blob);
+				}
 			}
 		}
-		if (total_len)
-			*total_len += len;
 		/* Skip blob object header first time around. */
 		n = fwrite(buf + hdrlen, 1, len - hdrlen, outfile);
 		if (n != len - hdrlen)
 			return got_ferror(outfile, GOT_ERR_IO);
+		if (total_len)
+			*total_len += len - hdrlen;
 		hdrlen = 0;
 	} while (len != 0);
 
diff --git a/regress/cmdline/blame.sh b/regress/cmdline/blame.sh
index ccd7980..bd295cf 100755
--- a/regress/cmdline/blame.sh
+++ b/regress/cmdline/blame.sh
@@ -97,5 +97,63 @@ function test_blame_tag {
 	test_done "$testroot" "$ret"
 }
 
+function test_blame_file_single_line {
+	local testroot=`test_init blame_file_single_line`
+
+	got checkout $testroot/repo $testroot/wt > /dev/null
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	echo 1 > $testroot/wt/alpha
+	(cd $testroot/wt && got commit -m "change 1" > /dev/null)
+	local commit1=`git_show_head $testroot/repo`
+
+	(cd $testroot/wt && got blame alpha > $testroot/stdout)
+
+	local short_commit1=`trim_obj_id 32 $commit1`
+
+	echo "$short_commit1 1" > $testroot/stdout.expected
+
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+	fi
+	test_done "$testroot" "$ret"
+}
+
+function test_blame_file_single_line_no_newline {
+	local testroot=`test_init blame_file_single_line_no_newline`
+
+	got checkout $testroot/repo $testroot/wt > /dev/null
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	echo -n 1 > $testroot/wt/alpha
+	(cd $testroot/wt && got commit -m "change 1" > /dev/null)
+	local commit1=`git_show_head $testroot/repo`
+
+	(cd $testroot/wt && got blame alpha > $testroot/stdout)
+
+	local short_commit1=`trim_obj_id 32 $commit1`
+
+	echo "$short_commit1 1" > $testroot/stdout.expected
+
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+	fi
+	test_done "$testroot" "$ret"
+}
+
 run_test test_blame_basic
 run_test test_blame_tag
+run_test test_blame_file_single_line
+run_test test_blame_file_single_line_no_newline