Commit Diff


commit - 8da526b59d8479dce1543e22fc5073e639d115c8
commit + 82c78e96f11e4190c3d91f18122df14e30347300
blob - ddfd81fefa159116d100f5e18c9ce0e2eb2914a3
blob + 65551dc9c9ab16277bcd21dddd49e8bd176e6da6
--- got/got.c
+++ got/got.c
@@ -3701,7 +3701,7 @@ diff_trees(struct got_object_id *tree_id1, struct got_
 	arg.force_text_diff = force_text_diff;
 	arg.diff_algo = GOT_DIFF_ALGORITHM_PATIENCE;
 	arg.outfile = outfile;
-	arg.line_offsets = NULL;
+	arg.lines = NULL;
 	arg.nlines = 0;
 	while (path[0] == '/')
 		path++;
blob - 4243be1c5479449f6d3623e760d1979bd58682b8
blob + bc4bc871ee90bf32c4fd2bd3b2b452f29d5822a0
--- include/got_diff.h
+++ include/got_diff.h
@@ -20,6 +20,31 @@ enum got_diff_algorithm {
 };
 
 /*
+ * List of all line types in a diff (including '{got,tog} log' lines).
+ * XXX GOT_DIFF_LINE_HUNK to GOT_DIFF_LINE_NONE inclusive must map to the
+ * DIFF_LINE_* macro counterparts defined in lib/diff_output.h (i.e., 60-64).
+ */
+enum got_diff_line_type {
+	GOT_DIFF_LINE_LOGMSG,
+	GOT_DIFF_LINE_AUTHOR,
+	GOT_DIFF_LINE_DATE,
+	GOT_DIFF_LINE_CHANGES,
+	GOT_DIFF_LINE_META,
+	GOT_DIFF_LINE_BLOB_MIN,
+	GOT_DIFF_LINE_BLOB_PLUS,
+	GOT_DIFF_LINE_HUNK = 60,
+	GOT_DIFF_LINE_MINUS,
+	GOT_DIFF_LINE_PLUS,
+	GOT_DIFF_LINE_CONTEXT,
+	GOT_DIFF_LINE_NONE
+};
+
+struct got_diff_line {
+	off_t	offset;
+	uint8_t	type;
+};
+
+/*
  * Compute the differences between two blobs and write unified diff text
  * to the provided output file. Two open temporary files must be provided
  * for internal use; these files can be obtained from got_opentemp() and
@@ -34,7 +59,7 @@ enum got_diff_algorithm {
  * If not NULL, the two initial output arguments will be populated with an
  * array of line offsets for, and the number of lines in, the unidiff text.
  */
-const struct got_error *got_diff_blob(off_t **, size_t *,
+const struct got_error *got_diff_blob(struct got_diff_line **, size_t *,
     struct got_blob_object *, struct got_blob_object *, FILE *, FILE *,
     const char *, const char *, enum got_diff_algorithm, int, int, int,
     FILE *);
@@ -84,18 +109,18 @@ struct got_diff_blob_output_unidiff_arg {
 
 	/*
 	 * The number of lines contained in produced unidiff text output,
-	 * and an array of byte offsets to each line. May be initialized to
-	 * zero and NULL to ignore line offsets. If not NULL, then the line
-	 * offsets array will be populated. Optionally, the array can be
-	 * pre-populated with line offsets, with nlines > 0 indicating
-	 * the length of the pre-populated array. This is useful if the
-	 * output file already contains some lines of text.
-	 * The array will be grown as needed to accomodate additional line
-	 * offsets, and the last offset found in a pre-populated array will
-	 * be added to all subsequent offsets.
+	 * and an array of got_diff_lines with byte offset and line type to
+	 * each line. May be initialized to zero and NULL to ignore line
+	 * metadata. If not NULL, then the array of line offsets and types will
+	 * be populated. Optionally, the array can be pre-populated with line
+	 * offsets and types, with nlines > 0 indicating the length of the
+	 * pre-populated array.  This is useful if the output file already
+	 * contains some lines of text.  The array will be grown as needed to
+	 * accomodate additional offsets and types, and the last offset found
+	 * in a pre-populated array will be added to all subsequent offsets.
 	 */
 	size_t nlines;
-	off_t *line_offsets;	/* Dispose of with free(3) when done. */
+	struct got_diff_line *lines; /* Dispose of with free(3) when done. */
 };
 const struct got_error *got_diff_blob_output_unidiff(void *,
     struct got_blob_object *, struct got_blob_object *, FILE *, FILE *,
@@ -155,10 +180,10 @@ const struct got_error *got_diff_tree_collect_changed_
  * If not NULL, the two initial output arguments will be populated with an
  * array of line offsets for, and the number of lines in, the unidiff text.
  */
-const struct got_error *got_diff_objects_as_blobs(off_t **, size_t *,
-    FILE *, FILE *, int, int, struct got_object_id *, struct got_object_id *,
-    const char *, const char *, enum got_diff_algorithm, int, int, int,
-    struct got_repository *, FILE *);
+const struct got_error *got_diff_objects_as_blobs(struct got_diff_line **,
+    size_t *, FILE *, FILE *, int, int, struct got_object_id *,
+    struct got_object_id *, const char *, const char *, enum got_diff_algorithm,
+    int, int, int, struct got_repository *, FILE *);
 
 struct got_pathlist_head;
 
@@ -177,10 +202,11 @@ struct got_pathlist_head;
  * If not NULL, the two initial output arguments will be populated with an
  * array of line offsets for, and the number of lines in, the unidiff text.
  */
-const struct got_error *got_diff_objects_as_trees(off_t **, size_t *,
-    FILE *, FILE *, int, int, struct got_object_id *, struct got_object_id *,
-    struct got_pathlist_head *, const char *, const char *,
-    enum got_diff_algorithm, int, int, int, struct got_repository *, FILE *);
+const struct got_error *got_diff_objects_as_trees(struct got_diff_line **,
+    size_t *, FILE *, FILE *, int, int, struct got_object_id *,
+    struct got_object_id *, struct got_pathlist_head *, const char *,
+    const char *, enum got_diff_algorithm, int, int, int,
+    struct got_repository *, FILE *);
 
 /*
  * Diff two objects, assuming both objects are commits.
@@ -194,9 +220,9 @@ const struct got_error *got_diff_objects_as_trees(off_
  * If not NULL, the two initial output arguments will be populated with an
  * array of line offsets for, and the number of lines in, the unidiff text.
  */
-const struct got_error *got_diff_objects_as_commits(off_t **, size_t *,
-    FILE *, FILE *, int, int, struct got_object_id *, struct got_object_id *,
-    struct got_pathlist_head *, enum got_diff_algorithm, int, int, int,
-    struct got_repository *, FILE *);
+const struct got_error *got_diff_objects_as_commits(struct got_diff_line **,
+    size_t *, FILE *, FILE *, int, int, struct got_object_id *,
+    struct got_object_id *, struct got_pathlist_head *, enum got_diff_algorithm,
+    int, int, int, struct got_repository *, FILE *);
 
 #define GOT_DIFF_MAX_CONTEXT	64
blob - 14734de52bef2db3787421c6d6fb25df85a94361
blob + a998453e7e5bc7ff3b9c707e6d83dfc42d959d5d
--- lib/diff.c
+++ lib/diff.c
@@ -40,21 +40,24 @@
 #include "got_lib_object.h"
 
 static const struct got_error *
-add_line_offset(off_t **line_offsets, size_t *nlines, off_t off)
+add_line_metadata(struct got_diff_line **lines, size_t *nlines,
+    off_t off, uint8_t type)
 {
-	off_t *p;
+	struct got_diff_line *p;
 
-	p = reallocarray(*line_offsets, *nlines + 1, sizeof(off_t));
+	p = reallocarray(*lines, *nlines + 1, sizeof(**lines));
 	if (p == NULL)
 		return got_error_from_errno("reallocarray");
-	*line_offsets = p;
-	(*line_offsets)[*nlines] = off;
+	*lines = p;
+	(*lines)[*nlines].offset = off;
+	(*lines)[*nlines].type = type;
 	(*nlines)++;
+
 	return NULL;
 }
 
 static const struct got_error *
-diff_blobs(off_t **line_offsets, size_t *nlines,
+diff_blobs(struct got_diff_line **lines, size_t *nlines,
     struct got_diffreg_result **resultp, struct got_blob_object *blob1,
     struct got_blob_object *blob2, FILE *f1, FILE *f2,
     const char *label1, const char *label2, mode_t mode1, mode_t mode2,
@@ -70,10 +73,10 @@ diff_blobs(off_t **line_offsets, size_t *nlines,
 	off_t outoff = 0;
 	int n;
 
-	if (line_offsets && *line_offsets && *nlines > 0)
-		outoff = (*line_offsets)[*nlines - 1];
-	else if (line_offsets) {
-		err = add_line_offset(line_offsets, nlines, 0);
+	if (lines && *lines && nlines > 0)
+		outoff = (*lines)[*nlines - 1].offset;
+	else if (lines) {
+		err = add_line_metadata(lines, nlines, 0, GOT_DIFF_LINE_NONE);
 		if (err)
 			goto done;
 	}
@@ -142,8 +145,9 @@ diff_blobs(off_t **line_offsets, size_t *nlines,
 		if (n < 0)
 			goto done;
 		outoff += n;
-		if (line_offsets) {
-			err = add_line_offset(line_offsets, nlines, outoff);
+		if (lines) {
+			err = add_line_metadata(lines, nlines, outoff,
+			    GOT_DIFF_LINE_BLOB_MIN);
 			if (err)
 				goto done;
 		}
@@ -153,8 +157,9 @@ diff_blobs(off_t **line_offsets, size_t *nlines,
 		if (n < 0)
 			goto done;
 		outoff += n;
-		if (line_offsets) {
-			err = add_line_offset(line_offsets, nlines, outoff);
+		if (lines) {
+			err = add_line_metadata(lines, nlines, outoff,
+			    GOT_DIFF_LINE_BLOB_PLUS);
 			if (err)
 				goto done;
 		}
@@ -168,7 +173,7 @@ diff_blobs(off_t **line_offsets, size_t *nlines,
 		goto done;
 
 	if (outfile) {
-		err = got_diffreg_output(line_offsets, nlines, result,
+		err = got_diffreg_output(lines, nlines, result,
 		    blob1 != NULL, blob2 != NULL,
 		    label1 ? label1 : idstr1,
 		    label2 ? label2 : idstr2,
@@ -197,19 +202,19 @@ got_diff_blob_output_unidiff(void *arg, struct got_blo
 {
 	struct got_diff_blob_output_unidiff_arg *a = arg;
 
-	return diff_blobs(&a->line_offsets, &a->nlines, NULL,
+	return diff_blobs(&a->lines, &a->nlines, NULL,
 	    blob1, blob2, f1, f2, label1, label2, mode1, mode2, a->diff_context,
 	    a->ignore_whitespace, a->force_text_diff, a->outfile, a->diff_algo);
 }
 
 const struct got_error *
-got_diff_blob(off_t **line_offsets, size_t *nlines,
+got_diff_blob(struct got_diff_line **lines, size_t*nlines,
     struct got_blob_object *blob1, struct got_blob_object *blob2,
     FILE *f1, FILE *f2, const char *label1, const char *label2,
     enum got_diff_algorithm diff_algo, int diff_context,
     int ignore_whitespace, int force_text_diff, FILE *outfile)
 {
-	return diff_blobs(line_offsets, nlines, NULL, blob1, blob2, f1, f2,
+	return diff_blobs(lines, nlines, NULL, blob1, blob2, f1, f2,
 	    label1, label2, 0, 0, diff_context, ignore_whitespace,
 	    force_text_diff, outfile, diff_algo);
 }
@@ -727,7 +732,7 @@ got_diff_tree(struct got_tree_object *tree1, struct go
 }
 
 const struct got_error *
-got_diff_objects_as_blobs(off_t **line_offsets, size_t *nlines,
+got_diff_objects_as_blobs(struct got_diff_line **lines, size_t *nlines,
     FILE *f1, FILE *f2, int fd1, int fd2,
     struct got_object_id *id1, struct got_object_id *id2,
     const char *label1, const char *label2,
@@ -751,9 +756,9 @@ got_diff_objects_as_blobs(off_t **line_offsets, size_t
 		if (err)
 			goto done;
 	}
-	err = got_diff_blob(line_offsets, nlines, blob1, blob2, f1, f2,
-	    label1, label2, diff_algo, diff_context, ignore_whitespace,
-	    force_text_diff, outfile);
+	err = got_diff_blob(lines, nlines, blob1, blob2, f1, f2, label1, label2,
+	    diff_algo, diff_context, ignore_whitespace, force_text_diff,
+	    outfile);
 done:
 	if (blob1)
 		got_object_blob_close(blob1);
@@ -891,24 +896,26 @@ done:
 }
 
 static const struct got_error *
-show_object_id(off_t **line_offsets, size_t *nlines, const char *obj_typestr,
-    int ch, const char *id_str, FILE *outfile)
+show_object_id(struct got_diff_line **lines, size_t *nlines,
+    const char *obj_typestr, int ch, const char *id_str, FILE *outfile)
 {
 	const struct got_error *err;
 	int n;
 	off_t outoff = 0;
 
 	n = fprintf(outfile, "%s %c %s\n", obj_typestr, ch, id_str);
-	if (line_offsets != NULL && *line_offsets != NULL) {
+	if (lines != NULL && *lines != NULL) {
 		if (*nlines == 0) {
-			err = add_line_offset(line_offsets, nlines, 0);
+			err = add_line_metadata(lines, nlines, 0,
+			    GOT_DIFF_LINE_META);
 			if (err)
 				return err;
 		} else
-			outoff = (*line_offsets)[*nlines - 1];
+			outoff = (*lines)[*nlines - 1].offset;
 
 		outoff += n;
-		err = add_line_offset(line_offsets, nlines, outoff);
+		err = add_line_metadata(lines, nlines, outoff,
+		    GOT_DIFF_LINE_META);
 		if (err)
 			return err;
 	}
@@ -917,7 +924,7 @@ show_object_id(off_t **line_offsets, size_t *nlines, c
 }
 
 static const struct got_error *
-diff_objects_as_trees(off_t **line_offsets, size_t *nlines,
+diff_objects_as_trees(struct got_diff_line **lines, size_t *nlines,
     FILE *f1, FILE *f2, int fd1, int fd2,
     struct got_object_id *id1, struct got_object_id *id2,
     struct got_pathlist_head *paths, const char *label1, const char *label2,
@@ -928,7 +935,7 @@ diff_objects_as_trees(off_t **line_offsets, size_t *nl
 	const struct got_error *err;
 	struct got_tree_object *tree1 = NULL, *tree2 = NULL;
 	struct got_diff_blob_output_unidiff_arg arg;
-	int want_lineoffsets = (line_offsets != NULL && *line_offsets != NULL);
+	int want_linemeta = (lines != NULL && *lines != NULL);
 
 	if (id1 == NULL && id2 == NULL)
 		return got_error(GOT_ERR_NO_OBJ);
@@ -949,11 +956,11 @@ diff_objects_as_trees(off_t **line_offsets, size_t *nl
 	arg.ignore_whitespace = ignore_whitespace;
 	arg.force_text_diff = force_text_diff;
 	arg.outfile = outfile;
-	if (want_lineoffsets) {
-		arg.line_offsets = *line_offsets;
+	if (want_linemeta) {
+		arg.lines = *lines;
 		arg.nlines = *nlines;
 	} else {
-		arg.line_offsets = NULL;
+		arg.lines = NULL;
 		arg.nlines = 0;
 	}
 	if (paths == NULL || TAILQ_EMPTY(paths)) {
@@ -964,8 +971,8 @@ diff_objects_as_trees(off_t **line_offsets, size_t *nl
 		err = diff_paths(tree1, tree2, f1, f2, fd1, fd2, paths, repo,
 		    got_diff_blob_output_unidiff, &arg);
 	}
-	if (want_lineoffsets) {
-		*line_offsets = arg.line_offsets; /* was likely re-allocated */
+	if (want_linemeta) {
+		*lines = arg.lines; /* was likely re-allocated */
 		*nlines = arg.nlines;
 	}
 done:
@@ -977,7 +984,7 @@ done:
 }
 
 const struct got_error *
-got_diff_objects_as_trees(off_t **line_offsets, size_t *nlines,
+got_diff_objects_as_trees(struct got_diff_line **lines, size_t *nlines,
     FILE *f1, FILE *f2, int fd1, int fd2,
     struct got_object_id *id1, struct got_object_id *id2,
     struct got_pathlist_head *paths, const char *label1, const char *label2,
@@ -994,15 +1001,14 @@ got_diff_objects_as_trees(off_t **line_offsets, size_t
 		err = got_object_id_str(&idstr, id1);
 		if (err)
 			goto done;
-		err = show_object_id(line_offsets, nlines, "tree", '-',
-		    idstr, outfile);
+		err = show_object_id(lines, nlines, "tree", '-', idstr, outfile);
 		if (err)
 			goto done;
 		free(idstr);
 		idstr = NULL;
 	} else {
-		err = show_object_id(line_offsets, nlines, "tree", '-',
-		    "/dev/null", outfile);
+		err = show_object_id(lines, nlines, "tree", '-', "/dev/null",
+		    outfile);
 		if (err)
 			goto done;
 	}
@@ -1011,21 +1017,20 @@ got_diff_objects_as_trees(off_t **line_offsets, size_t
 		err = got_object_id_str(&idstr, id2);
 		if (err)
 			goto done;
-		err = show_object_id(line_offsets, nlines, "tree", '+',
-		    idstr, outfile);
+		err = show_object_id(lines, nlines, "tree", '+', idstr, outfile);
 		if (err)
 			goto done;
 		free(idstr);
 		idstr = NULL;
 	} else {
-		err = show_object_id(line_offsets, nlines, "tree", '+',
-		    "/dev/null", outfile);
+		err = show_object_id(lines, nlines, "tree", '+', "/dev/null",
+		    outfile);
 		if (err)
 			goto done;
 	}
 
-	err = diff_objects_as_trees(line_offsets, nlines, f1, f2, fd1, fd2,
-	    id1, id2, paths, label1, label2, diff_context, ignore_whitespace,
+	err = diff_objects_as_trees(lines, nlines, f1, f2, fd1, fd2, id1, id2,
+	    paths, label1, label2, diff_context, ignore_whitespace,
 	    force_text_diff, repo, outfile, diff_algo);
 done:
 	free(idstr);
@@ -1033,7 +1038,7 @@ done:
 }
 
 const struct got_error *
-got_diff_objects_as_commits(off_t **line_offsets, size_t *nlines,
+got_diff_objects_as_commits(struct got_diff_line **lines, size_t *nlines,
     FILE *f1, FILE *f2, int fd1, int fd2,
     struct got_object_id *id1, struct got_object_id *id2,
     struct got_pathlist_head *paths, enum got_diff_algorithm diff_algo,
@@ -1054,15 +1059,15 @@ got_diff_objects_as_commits(off_t **line_offsets, size
 		err = got_object_id_str(&idstr, id1);
 		if (err)
 			goto done;
-		err = show_object_id(line_offsets, nlines, "commit", '-',
-		    idstr, outfile);
+		err = show_object_id(lines, nlines, "commit", '-', idstr,
+		    outfile);
 		if (err)
 			goto done;
 		free(idstr);
 		idstr = NULL;
 	} else {
-		err = show_object_id(line_offsets, nlines, "commit", '-',
-		    "/dev/null", outfile);
+		err = show_object_id(lines, nlines, "commit", '-', "/dev/null",
+		    outfile);
 		if (err)
 			goto done;
 	}
@@ -1074,12 +1079,11 @@ got_diff_objects_as_commits(off_t **line_offsets, size
 	err = got_object_id_str(&idstr, id2);
 	if (err)
 		goto done;
-	err = show_object_id(line_offsets, nlines, "commit", '+',
-	    idstr, outfile);
+	err = show_object_id(lines, nlines, "commit", '+', idstr, outfile);
 	if (err)
 		goto done;
 
-	err = diff_objects_as_trees(line_offsets, nlines, f1, f2, fd1, fd2,
+	err = diff_objects_as_trees(lines, nlines, f1, f2, fd1, fd2,
 	    commit1 ? got_object_commit_get_tree_id(commit1) : NULL,
 	    got_object_commit_get_tree_id(commit2), paths, "", "",
 	    diff_context, ignore_whitespace, force_text_diff, repo, outfile,
blob - f50562ea7c4eab62df85e0099e0b4de267333c5b
blob + ee175dcf640d5b8d7770236de9c443a5888891dd
--- lib/diffreg.c
+++ lib/diffreg.c
@@ -252,7 +252,7 @@ done:
 }
 
 const struct got_error *
-got_diffreg_output(off_t **line_offsets, size_t *nlines,
+got_diffreg_output(struct got_diff_line **lines, size_t *nlines,
     struct got_diffreg_result *diff_result, int f1_exists, int f2_exists,
     const char *path1, const char *path2,
     enum got_diff_output_format output_format, int context_lines, FILE *outfile)
@@ -273,13 +273,13 @@ got_diffreg_output(off_t **line_offsets, size_t *nline
 	switch (output_format) {
 	case GOT_DIFF_OUTPUT_UNIDIFF:
 		rc = diff_output_unidiff(
-		    line_offsets ? &output_info : NULL, outfile, &info,
+		    lines ? &output_info : NULL, outfile, &info,
 		    diff_result->result, context_lines);
 		if (rc != DIFF_RC_OK)
 			return got_error_set_errno(rc, "diff_output_unidiff");
 		break;
 	case GOT_DIFF_OUTPUT_EDSCRIPT:
-		rc = diff_output_edscript(line_offsets ? &output_info : NULL,
+		rc = diff_output_edscript(lines ? &output_info : NULL,
 		    outfile, &info, diff_result->result);
 		if (rc != DIFF_RC_OK)
 			return got_error_set_errno(rc, "diff_output_edscript");
@@ -287,29 +287,34 @@ got_diffreg_output(off_t **line_offsets, size_t *nline
 
 	}
 
-	if (line_offsets && *line_offsets) {
+	if (lines && *lines) {
 		if (output_info->line_offsets.len > 0) {
-			off_t prev_offset = 0, *p, *o;
+			struct got_diff_line *p;
+			off_t prev_offset = 0, *o;
+			uint8_t *o2;
 			int i, len;
 			if (*nlines > 0) {
-				prev_offset = (*line_offsets)[*nlines - 1];
+				prev_offset = (*lines)[*nlines - 1].offset;
 				/*
 				 * First line offset is always zero. Skip it
 				 * when appending to a pre-populated array.
 				 */
 				o = &output_info->line_offsets.head[1];
+				o2 = &output_info->line_types.head[1];
 				len = output_info->line_offsets.len - 1;
 			} else {
 				o = &output_info->line_offsets.head[0];
+				o2 = &output_info->line_types.head[0];
 				len = output_info->line_offsets.len;
 			}
-			p = reallocarray(*line_offsets, *nlines + len,
-			    sizeof(off_t));
+			p = reallocarray(*lines, *nlines + len, sizeof(**lines));
 			if (p == NULL)
 				return got_error_from_errno("calloc");
-			for (i = 0; i < len; i++)
-				p[*nlines + i] = o[i] + prev_offset;
-			*line_offsets = p;
+			for (i = 0; i < len; i++) {
+				p[*nlines + i].offset = o[i] + prev_offset;
+				p[*nlines + i].type = o2[i];
+			}
+			*lines = p;
 			*nlines += len;
 		}
 		diff_output_info_free(output_info);
blob - 3ff8d96891722f9a89a5245a2fb9b5e5c8424e83
blob + 43c7096d0a3fe2bb611660d822341235f3223a17
--- lib/got_lib_diff.h
+++ lib/got_lib_diff.h
@@ -44,7 +44,7 @@ const struct got_error *got_diff_prepare_file(FILE *, 
     struct diff_data *, const struct diff_config *, int, int); 
 const struct got_error *got_diffreg(struct got_diffreg_result **, FILE *,
     FILE *, enum got_diff_algorithm, int, int);
-const struct got_error *got_diffreg_output(off_t **, size_t *,
+const struct got_error *got_diffreg_output(struct got_diff_line **, size_t *,
     struct got_diffreg_result *, int, int, const char *, const char *,
     enum got_diff_output_format, int, FILE *);
 const struct got_error *got_diffreg_result_free(struct got_diffreg_result *);
blob - ff238905b35da47efd5533334d11290e37f51345
blob + 7675eb02809eb3f2eb24a7b6e4066ffaefbc42bc
--- tog/tog.1
+++ tog/tog.1
@@ -294,6 +294,14 @@ Scroll up N half pages (default: 1).
 Scroll to the top of the view.
 .It Cm End, G
 Scroll to the bottom of the view.
+.It Cm \&(
+Navigate to the Nth previous file in the diff (default: 1).
+.It Cm \&)
+Navigate to the Nth next file in the diff (default: 1).
+.It Cm \&{
+Navigate to the Nth previous hunk in the diff (default: 1).
+.It Cm \&}
+Navigate to the Nth next hunk in the diff (default: 1).
 .It Cm \&[
 Reduce diff context by N lines (default: 1).
 .It Cm \&]
blob - fe1a8df50fa4464f8dcf2d9e23e1ba930990a656
blob + bba44ebab024533437e4486a69fe0b9f00188cfb
--- tog/tog.c
+++ tog/tog.c
@@ -317,13 +317,13 @@ get_color_value(const char *envvar)
 
 	return default_color_value(envvar);
 }
-
 
 struct tog_diff_view_state {
 	struct got_object_id *id1, *id2;
 	const char *label1, *label2;
 	FILE *f, *f1, *f2;
 	int fd1, fd2;
+	int lineno;
 	int first_displayed_line;
 	int last_displayed_line;
 	int eof;
@@ -332,8 +332,8 @@ struct tog_diff_view_state {
 	int force_text_diff;
 	struct got_repository *repo;
 	struct tog_colors colors;
+	struct got_diff_line *lines;
 	size_t nlines;
-	off_t *line_offsets;
 	int matched_line;
 	int selected_line;
 
@@ -3941,7 +3941,7 @@ draw_file(struct tog_view *view, const char *header)
 	struct tog_diff_view_state *s = &view->state.diff;
 	regmatch_t *regmatch = &view->regmatch;
 	const struct got_error *err;
-	int lineno, nprinted = 0;
+	int nprinted = 0;
 	char *line;
 	size_t linesize = 0;
 	ssize_t linelen;
@@ -3953,8 +3953,8 @@ draw_file(struct tog_view *view, const char *header)
 	off_t line_offset;
 	attr_t attr;
 
-	lineno = s->first_displayed_line - 1;
-	line_offset = s->line_offsets[s->first_displayed_line - 1];
+	s->lineno = s->first_displayed_line - 1;
+	line_offset = s->lines[s->first_displayed_line - 1].offset;
 	if (fseeko(s->f, line_offset, SEEK_SET) == -1)
 		return got_error_from_errno("fseek");
 
@@ -3966,7 +3966,7 @@ draw_file(struct tog_view *view, const char *header)
 	if (header) {
 		int ln = view->gline ? view->gline <= (view->nlines - 3) / 2 ?
 		    1 : view->gline - (view->nlines - 3) / 2 :
-		    lineno + s->selected_line;
+		    s->lineno + s->selected_line;
 
 		if (asprintf(&line, "[%d/%d] %s", ln, nlines, header) == -1)
 			return got_error_from_errno("asprintf");
@@ -4006,11 +4006,11 @@ draw_file(struct tog_view *view, const char *header)
 		}
 
 		attr = 0;
-		if (++lineno < s->first_displayed_line)
+		if (++s->lineno < s->first_displayed_line)
 			continue;
-		if (view->gline && !gotoline(view, &lineno, &nprinted))
+		if (view->gline && !gotoline(view, &s->lineno, &nprinted))
 			continue;
-		if (lineno == view->hiline)
+		if (s->lineno == view->hiline)
 			attr = A_STANDOUT;
 
 		/* Set view->maxx based on full line length. */
@@ -4049,7 +4049,7 @@ draw_file(struct tog_view *view, const char *header)
 			free(wline);
 			wline = NULL;
 		}
-		if (lineno == view->hiline) {
+		if (s->lineno == view->hiline) {
 			/* highlight full gline length */
 			while (width++ < view->ncols)
 				waddch(view->window, ' ');
@@ -4060,7 +4060,7 @@ draw_file(struct tog_view *view, const char *header)
 		if (attr)
 			wattroff(view->window, attr);
 		if (++nprinted == 1)
-			s->first_displayed_line = lineno;
+			s->first_displayed_line = s->lineno;
 	}
 	free(line);
 	if (nprinted >= 1)
@@ -4161,21 +4161,24 @@ done:
 }
 
 static const struct got_error *
-add_line_offset(off_t **line_offsets, size_t *nlines, off_t off)
+add_line_metadata(struct got_diff_line **lines, size_t *nlines,
+    off_t off, uint8_t type)
 {
-	off_t *p;
+	struct got_diff_line *p;
 
-	p = reallocarray(*line_offsets, *nlines + 1, sizeof(off_t));
+	p = reallocarray(*lines, *nlines + 1, sizeof(**lines));
 	if (p == NULL)
 		return got_error_from_errno("reallocarray");
-	*line_offsets = p;
-	(*line_offsets)[*nlines] = off;
+	*lines = p;
+	(*lines)[*nlines].offset = off;
+	(*lines)[*nlines].type = type;
 	(*nlines)++;
+
 	return NULL;
 }
 
 static const struct got_error *
-write_commit_info(off_t **line_offsets, size_t *nlines,
+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)
 {
@@ -4209,7 +4212,7 @@ write_commit_info(off_t **line_offsets, size_t *nlines
 		goto done;
 	}
 
-	err = add_line_offset(line_offsets, nlines, 0);
+	err = add_line_metadata(lines, nlines, 0, GOT_DIFF_LINE_NONE);
 	if (err)
 		goto done;
 
@@ -4220,7 +4223,7 @@ write_commit_info(off_t **line_offsets, size_t *nlines
 		goto done;
 	}
 	outoff += n;
-	err = add_line_offset(line_offsets, nlines, outoff);
+	err = add_line_metadata(lines, nlines, outoff, GOT_DIFF_LINE_META);
 	if (err)
 		goto done;
 
@@ -4231,7 +4234,7 @@ write_commit_info(off_t **line_offsets, size_t *nlines
 		goto done;
 	}
 	outoff += n;
-	err = add_line_offset(line_offsets, nlines, outoff);
+	err = add_line_metadata(lines, nlines, outoff, GOT_DIFF_LINE_AUTHOR);
 	if (err)
 		goto done;
 
@@ -4244,7 +4247,8 @@ write_commit_info(off_t **line_offsets, size_t *nlines
 			goto done;
 		}
 		outoff += n;
-		err = add_line_offset(line_offsets, nlines, outoff);
+		err = add_line_metadata(lines, nlines, outoff,
+		    GOT_DIFF_LINE_DATE);
 		if (err)
 			goto done;
 	}
@@ -4257,7 +4261,8 @@ write_commit_info(off_t **line_offsets, size_t *nlines
 			goto done;
 		}
 		outoff += n;
-		err = add_line_offset(line_offsets, nlines, outoff);
+		err = add_line_metadata(lines, nlines, outoff,
+		    GOT_DIFF_LINE_AUTHOR);
 		if (err)
 			goto done;
 	}
@@ -4276,7 +4281,8 @@ write_commit_info(off_t **line_offsets, size_t *nlines
 				goto done;
 			}
 			outoff += n;
-			err = add_line_offset(line_offsets, nlines, outoff);
+			err = add_line_metadata(lines, nlines, outoff,
+			    GOT_DIFF_LINE_META);
 			if (err)
 				goto done;
 			free(id_str);
@@ -4295,7 +4301,8 @@ write_commit_info(off_t **line_offsets, size_t *nlines
 			goto done;
 		}
 		outoff += n;
-		err = add_line_offset(line_offsets, nlines, outoff);
+		err = add_line_metadata(lines, nlines, outoff,
+		    GOT_DIFF_LINE_LOGMSG);
 		if (err)
 			goto done;
 	}
@@ -4311,7 +4318,8 @@ write_commit_info(off_t **line_offsets, size_t *nlines
 			goto done;
 		}
 		outoff += n;
-		err = add_line_offset(line_offsets, nlines, outoff);
+		err = add_line_metadata(lines, nlines, outoff,
+		    GOT_DIFF_LINE_CHANGES);
 		if (err)
 			goto done;
 		free((char *)pe->path);
@@ -4320,7 +4328,7 @@ write_commit_info(off_t **line_offsets, size_t *nlines
 
 	fputc('\n', outfile);
 	outoff++;
-	err = add_line_offset(line_offsets, nlines, outoff);
+	err = add_line_metadata(lines, nlines, outoff, GOT_DIFF_LINE_NONE);
 done:
 	got_pathlist_free(&changed_paths);
 	free(id_str);
@@ -4328,8 +4336,8 @@ done:
 	free(refs_str);
 	got_object_commit_close(commit);
 	if (err) {
-		free(*line_offsets);
-		*line_offsets = NULL;
+		free(*lines);
+		*lines = NULL;
 		*nlines = 0;
 	}
 	return err;
@@ -4342,9 +4350,9 @@ create_diff(struct tog_diff_view_state *s)
 	FILE *f = NULL;
 	int obj_type;
 
-	free(s->line_offsets);
-	s->line_offsets = malloc(sizeof(off_t));
-	if (s->line_offsets == NULL)
+	free(s->lines);
+	s->lines = malloc(sizeof(*s->lines));
+	if (s->lines == NULL)
 		return got_error_from_errno("malloc");
 	s->nlines = 0;
 
@@ -4368,13 +4376,13 @@ create_diff(struct tog_diff_view_state *s)
 
 	switch (obj_type) {
 	case GOT_OBJ_TYPE_BLOB:
-		err = got_diff_objects_as_blobs(&s->line_offsets, &s->nlines,
+		err = got_diff_objects_as_blobs(&s->lines, &s->nlines,
 		    s->f1, s->f2, s->fd1, s->fd2, s->id1, s->id2,
 		    s->label1, s->label2, tog_diff_algo, s->diff_context,
 		    s->ignore_whitespace, s->force_text_diff, s->repo, s->f);
 		break;
 	case GOT_OBJ_TYPE_TREE:
-		err = got_diff_objects_as_trees(&s->line_offsets, &s->nlines,
+		err = got_diff_objects_as_trees(&s->lines, &s->nlines,
 		    s->f1, s->f2, s->fd1, s->fd2, s->id1, s->id2, NULL, "", "",
 		    tog_diff_algo, s->diff_context, s->ignore_whitespace,
 		    s->force_text_diff, s->repo, s->f);
@@ -4391,17 +4399,17 @@ create_diff(struct tog_diff_view_state *s)
 		refs = got_reflist_object_id_map_lookup(tog_refs_idmap, s->id2);
 		/* Show commit info if we're diffing to a parent/root commit. */
 		if (s->id1 == NULL) {
-			err = write_commit_info(&s->line_offsets, &s->nlines,
-			    s->id2, refs, s->repo, s->f);
+			err = write_commit_info(&s->lines, &s->nlines, s->id2,
+			    refs, s->repo, s->f);
 			if (err)
 				goto done;
 		} else {
 			parent_ids = got_object_commit_get_parent_ids(commit2);
 			STAILQ_FOREACH(pid, parent_ids, entry) {
 				if (got_object_id_cmp(s->id1, &pid->id) == 0) {
-					err = write_commit_info(
-					    &s->line_offsets, &s->nlines,
-					    s->id2, refs, s->repo, s->f);
+					err = write_commit_info(&s->lines,
+					    &s->nlines, s->id2, refs, s->repo,
+					    s->f);
 					if (err)
 						goto done;
 					break;
@@ -4410,7 +4418,7 @@ create_diff(struct tog_diff_view_state *s)
 		}
 		got_object_commit_close(commit2);
 
-		err = got_diff_objects_as_commits(&s->line_offsets, &s->nlines,
+		err = got_diff_objects_as_commits(&s->lines, &s->nlines,
 		    s->f1, s->f2, s->fd1, s->fd2, s->id1, s->id2, NULL,
 		    tog_diff_algo, s->diff_context, s->ignore_whitespace,
 		    s->force_text_diff, s->repo, s->f);
@@ -4420,8 +4428,6 @@ create_diff(struct tog_diff_view_state *s)
 		err = got_error(GOT_ERR_OBJ_TYPE);
 		break;
 	}
-	if (err)
-		goto done;
 done:
 	if (s->f && fflush(s->f) != 0 && err == NULL)
 		err = got_error_from_errno("fflush");
@@ -4483,7 +4489,7 @@ search_next_diff_view(struct tog_view *view)
 				lineno = s->nlines;
 		}
 
-		offset = s->line_offsets[lineno - 1];
+		offset = s->lines[lineno - 1].offset;
 		if (fseeko(s->f, offset, SEEK_SET) != 0) {
 			free(line);
 			return got_error_from_errno("fseeko");
@@ -4544,8 +4550,8 @@ close_diff_view(struct tog_view *view)
 		err = got_error_from_errno("close");
 	s->fd2 = -1;
 	free_colors(&s->colors);
-	free(s->line_offsets);
-	s->line_offsets = NULL;
+	free(s->lines);
+	s->lines = NULL;
 	s->nlines = 0;
 	return err;
 }
@@ -4759,8 +4765,44 @@ reset_diff_view(struct tog_view *view)
 	s->matched_line = 0;
 	diff_view_indicate_progress(view);
 	return create_diff(s);
+}
+
+static void
+diff_prev_index(struct tog_diff_view_state *s, enum got_diff_line_type type)
+{
+	int start, i;
+
+	i = start = s->first_displayed_line - 1;
+
+	while (s->lines[i].type != type) {
+		if (i == 0)
+			i = s->nlines - 1;
+		if (--i == start)
+			return; /* do nothing, requested type not in file */
+	}
+
+	s->selected_line = 1;
+	s->first_displayed_line = i;
 }
 
+static void
+diff_next_index(struct tog_diff_view_state *s, enum got_diff_line_type type)
+{
+	int start, i;
+
+	i = start = s->first_displayed_line + 1;
+
+	while (s->lines[i].type != type) {
+		if (i == s->nlines - 1)
+			i = 0;
+		if (++i == start)
+			return; /* do nothing, requested type not in file */
+	}
+
+	s->selected_line = 1;
+	s->first_displayed_line = i;
+}
+
 static struct got_object_id *get_selected_commit_id(struct tog_blame_line *,
     int, int, int);
 static struct got_object_id *get_annotation_for_line(struct tog_blame_line *,
@@ -4778,6 +4820,8 @@ input_diff_view(struct tog_view **new_view, struct tog
 	ssize_t linelen;
 	int i, nscroll = view->nlines - 1, up = 0;
 
+	s->lineno = s->first_displayed_line - 1 + s->selected_line;
+
 	switch (ch) {
 	case '0':
 		view->x = 0;
@@ -4878,6 +4922,18 @@ input_diff_view(struct tog_view **new_view, struct tog
 		}
 		free(line);
 		break;
+	case '(':
+		diff_prev_index(s, GOT_DIFF_LINE_BLOB_MIN);
+		break;
+	case ')':
+		diff_next_index(s, GOT_DIFF_LINE_BLOB_MIN);
+		break;
+	case '{':
+		diff_prev_index(s, GOT_DIFF_LINE_HUNK);
+		break;
+	case '}':
+		diff_next_index(s, GOT_DIFF_LINE_HUNK);
+		break;
 	case '[':
 		if (s->diff_context > 0) {
 			s->diff_context--;