"GOT", but the "O" is a cute, smiling pufferfish. Index | Thread | Search

From:
Stefan Sperling <stsp@stsp.name>
Subject:
improve diff output for consumption by got patch
To:
gameoftrees@openbsd.org
Date:
Wed, 22 Jun 2022 14:39:30 +0200

Download raw body.

Thread
I would like to add two additional lines to our diff headers at the
beginning of patch, to show the type and IDs of the objects being
compared. The first line of our diff header does not provide this
information consistently. It sometimes shows reference names instead
of object IDs, for example. And it is sometimes unclear what type
of object an object Id shown on the initial 'diff ...' line refers to.

The idea is that got patch can simply look for a line such as:

  commit - abcde1234567...

to find the merge base commit ID. Of course, the blob ID from a
blob - header line is enough to run the actual 3-way merge of a file.
But we use the commit ID in conflict markers if it is available,
which should now always be the case for diffs created by Got.
(Granted, this commit ID is not guaranteed to actually exist in
the local repository. But, in general, people should be sending
out diffs against already published commits.)

When diffing two tree objects directly, the diff header now shows this:

  diff abcdef1234567... 1234567abcdef...
  tree - abcdef1234567...
  tree + 1234567abcdef...

When diffing a work tree, we now see these lines in the diff header:

  diff /path/to/work/tree
  commit - abcdef1234567...
  path + /path/to/work/tree

And a header for staged changes looks like this:

  diff -s /path/to/work/tree
  commit - abcdef1234567...
  path + /path/to/work/tree (staged changes)

Did anyone realize that got log -p actually displayed tree or blob IDs
rather than commit IDs in its diff header? This has been fixed here,
such that log -p now shows commit IDs in a manner which is consistent
with other diff output modes.

'got patch' no longer needs to parse object IDs from initial 'diff ... ...'
line after this change. But, for now, I am keeping support for it in case
someone still has an old-style diff they want to apply. The parser should
probably use the first diff line as an indicator that a got-style patch
might follow, but that is left for later.

ok?
 
diff 9802c41ca727979975e9ee6fbd898dfec7f283c4 21e2721d119981e3aac4c93afcdcbb0889bd6b91
commit - 9802c41ca727979975e9ee6fbd898dfec7f283c4
commit + 21e2721d119981e3aac4c93afcdcbb0889bd6b91
blob - 98f3281d5becb5173121e9f4f0b74eee3b61bebf
blob + 84c5803c7a5fb08b90a92d9a671feacaf659df2f
--- got/got.c
+++ got/got.c
@@ -3704,18 +3704,20 @@ print_patch(struct got_commit_object *commit, struct g
 		    &qid->id);
 		if (err)
 			return err;
+		err = got_object_id_str(&id_str1, &qid->id);
+		if (err)
+			goto done;
 	}
 
+	err = got_object_id_str(&id_str2, id);
+	if (err)
+		goto done;
+
 	if (path && path[0] != '\0') {
 		int obj_type;
 		err = got_object_id_by_path(&obj_id2, repo, commit, path);
 		if (err)
 			goto done;
-		err = got_object_id_str(&id_str2, obj_id2);
-		if (err) {
-			free(obj_id2);
-			goto done;
-		}
 		if (pcommit) {
 			err = got_object_id_by_path(&obj_id1, repo,
 			    pcommit, path);
@@ -3724,12 +3726,6 @@ print_patch(struct got_commit_object *commit, struct g
 					free(obj_id2);
 					goto done;
 				}
-			} else {
-				err = got_object_id_str(&id_str1, obj_id1);
-				if (err) {
-					free(obj_id2);
-					goto done;
-				}
 			}
 		}
 		err = got_object_get_type(&obj_type, repo, obj_id2);
@@ -3739,6 +3735,9 @@ print_patch(struct got_commit_object *commit, struct g
 		}
 		fprintf(outfile,
 		    "diff %s %s\n", id_str1 ? id_str1 : "/dev/null", id_str2);
+		fprintf(outfile, "commit - %s\n",
+		    id_str1 ? id_str1 : "/dev/null");
+		fprintf(outfile, "commit + %s\n", id_str2);
 		switch (obj_type) {
 		case GOT_OBJ_TYPE_BLOB:
 			err = diff_blobs(obj_id1, obj_id2, path, diff_context,
@@ -3756,17 +3755,13 @@ print_patch(struct got_commit_object *commit, struct g
 		free(obj_id2);
 	} else {
 		obj_id2 = got_object_commit_get_tree_id(commit);
-		err = got_object_id_str(&id_str2, obj_id2);
-		if (err)
-			goto done;
-		if (pcommit) {
+		if (pcommit)
 			obj_id1 = got_object_commit_get_tree_id(pcommit);
-			err = got_object_id_str(&id_str1, obj_id1);
-			if (err)
-				goto done;
-		}
 		fprintf(outfile,
 		    "diff %s %s\n", id_str1 ? id_str1 : "/dev/null", id_str2);
+		fprintf(outfile, "commit - %s\n",
+		    id_str1 ? id_str1 : "/dev/null");
+		fprintf(outfile, "commit + %s\n", id_str2);
 		err = diff_trees(obj_id1, obj_id2, "", diff_context, 0, 0,
 		    repo, outfile);
 	}
@@ -4648,7 +4643,10 @@ print_diff(void *arg, unsigned char status, unsigned c
 	}
 
 	if (!a->header_shown) {
-		printf("diff %s %s%s\n", a->id_str,
+		printf("diff %s%s\n", a->diff_staged ? "-s " : "",
+		    got_worktree_get_root_path(a->worktree));
+		printf("commit - %s\n", a->id_str);
+		printf("path + %s%s\n",
 		    got_worktree_get_root_path(a->worktree),
 		    a->diff_staged ? " (staged changes)" : "");
 		a->header_shown = 1;
blob - 63194824b5e659ee7b68160c5975355726baf8d1
blob + 3edacf762b640f876a604c1dbdc18eaae60d8a7a
--- lib/diff.c
+++ lib/diff.c
@@ -875,8 +875,34 @@ done:
 	return err;
 }
 
-const struct got_error *
-got_diff_objects_as_trees(off_t **line_offsets, size_t *nlines,
+static const struct got_error *
+show_object_id(off_t **line_offsets, size_t *nlines, const char *obj_typestr,
+    int c, 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, c, id_str);
+	if (line_offsets != NULL && *line_offsets != NULL) {
+		if (*nlines == 0) {
+			err = add_line_offset(line_offsets, nlines, 0);
+			if (err)
+				return err;
+		} else
+			outoff = (*line_offsets)[*nlines - 1];
+
+		outoff += n;
+		err = add_line_offset(line_offsets, nlines, outoff);
+		if (err)
+			return err;
+	}
+
+	return NULL;
+}
+
+static const struct got_error *
+diff_objects_as_trees(off_t **line_offsets, size_t *nlines,
     FILE *f1, FILE *f2, struct got_object_id *id1, struct got_object_id *id2,
     struct got_pathlist_head *paths,
     char *label1, char *label2, int diff_context, int ignore_whitespace,
@@ -932,6 +958,61 @@ done:
 }
 
 const struct got_error *
+got_diff_objects_as_trees(off_t **line_offsets, size_t *nlines,
+    FILE *f1, FILE *f2, struct got_object_id *id1, struct got_object_id *id2,
+    struct got_pathlist_head *paths,
+    char *label1, char *label2, int diff_context, int ignore_whitespace,
+    int force_text_diff, struct got_repository *repo, FILE *outfile)
+{
+	const struct got_error *err;
+	char *idstr = NULL;
+
+	if (id1 == NULL && id2 == NULL)
+		return got_error(GOT_ERR_NO_OBJ);
+
+	if (id1) {
+		err = got_object_id_str(&idstr, id1);
+		if (err)
+			goto done;
+		err = show_object_id(line_offsets, nlines, "tree", '-',
+		    idstr, outfile);
+		if (err)
+			goto done;
+		free(idstr);
+		idstr = NULL;
+	} else {
+		err = show_object_id(line_offsets, nlines, "tree", '-',
+		    "/dev/null", outfile);
+		if (err)
+			goto done;
+	}
+
+	if (id2) {
+		err = got_object_id_str(&idstr, id2);
+		if (err)
+			goto done;
+		err = show_object_id(line_offsets, nlines, "tree", '+',
+		    idstr, outfile);
+		if (err)
+			goto done;
+		free(idstr);
+		idstr = NULL;
+	} else {
+		err = show_object_id(line_offsets, nlines, "tree", '+',
+		    "/dev/null", outfile);
+		if (err)
+			goto done;
+	}
+
+	err = diff_objects_as_trees(line_offsets, nlines, f1, f2, id1, id2,
+	    paths, label1, label2, diff_context, ignore_whitespace,
+	    force_text_diff, repo, outfile);
+done:
+	free(idstr);
+	return err;
+}
+
+const struct got_error *
 got_diff_objects_as_commits(off_t **line_offsets, size_t *nlines,
     FILE *f1, FILE *f2, struct got_object_id *id1, struct got_object_id *id2,
     struct got_pathlist_head *paths,
@@ -940,6 +1021,7 @@ got_diff_objects_as_commits(off_t **line_offsets, size
 {
 	const struct got_error *err;
 	struct got_commit_object *commit1 = NULL, *commit2 = NULL;
+	char *idstr = NULL;
 
 	if (id2 == NULL)
 		return got_error(GOT_ERR_NO_OBJ);
@@ -948,13 +1030,35 @@ got_diff_objects_as_commits(off_t **line_offsets, size
 		err = got_object_open_as_commit(&commit1, repo, id1);
 		if (err)
 			goto done;
+		err = got_object_id_str(&idstr, id1);
+		if (err)
+			goto done;
+		err = show_object_id(line_offsets, nlines, "commit", '-',
+		    idstr, outfile);
+		if (err)
+			goto done;
+		free(idstr);
+		idstr = NULL;
+	} else {
+		err = show_object_id(line_offsets, nlines, "commit", '-',
+		    "/dev/null", outfile);
+		if (err)
+			goto done;
 	}
 
 	err = got_object_open_as_commit(&commit2, repo, id2);
 	if (err)
 		goto done;
 
-	err = got_diff_objects_as_trees(line_offsets, nlines, f1, f2,
+	err = got_object_id_str(&idstr, id2);
+	if (err)
+		goto done;
+	err = show_object_id(line_offsets, nlines, "commit", '+',
+	    idstr, outfile);
+	if (err)
+		goto done;
+
+	err = diff_objects_as_trees(line_offsets, nlines, f1, f2,
 	    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);
@@ -963,6 +1067,7 @@ done:
 		got_object_commit_close(commit1);
 	if (commit2)
 		got_object_commit_close(commit2);
+	free(idstr);
 	return err;
 }
 
blob - 5f1a2842034c7b2f4296aa5b45ca262a927c4a7a
blob + 05415ddde579ebfde446284ba961406bc37a4255
--- libexec/got-read-patch/got-read-patch.c
+++ libexec/got-read-patch/got-read-patch.c
@@ -191,6 +191,9 @@ find_patch(int *done, FILE *fp)
 		} else if (!git && !strncmp(line, "diff ", 5)) {
 			free(commitid);
 			err = blobid(line + 5, &commitid);
+		} else if (!git && !strncmp(line, "commit - ", 9)) {
+			free(commitid);
+			err = blobid(line + 9, &commitid);
 		}
 
 		if (err)
blob - 1c68ed3345910d0c52232b48edc9b7f237a43c39
blob + ac5a1498212d23ffe99224ddca891f89653de702
--- regress/cmdline/cherrypick.sh
+++ regress/cmdline/cherrypick.sh
@@ -1372,7 +1372,7 @@ EOF
  
 EOF
 	(cd $testroot/wt && got diff |
-		egrep -v '^(diff|blob|file)' > $testroot/diff)
+		egrep -v '^(diff|blob|file|commit|path)' > $testroot/diff)
 	cmp -s $testroot/diff.expected $testroot/diff
 	ret=$?
 	if [ $ret -ne 0 ]; then
blob - e590a17fa75734f18f299a52ca02118739f13b2b
blob + 5eef3d7b20577eb3dacc92be43505fd198bd1463
--- regress/cmdline/commit.sh
+++ regress/cmdline/commit.sh
@@ -428,6 +428,8 @@ test_commit_path_prefix() {
 	fi
 
 	echo "diff $commit1 $commit2" > $testroot/stdout.expected
+	echo "commit - $commit1" >> $testroot/stdout.expected
+	echo "commit + $commit2" >> $testroot/stdout.expected
 	echo -n 'blob - ' >> $testroot/stdout.expected
 	got tree -r $testroot/repo -c $commit1 -i gamma | grep 'delta$' \
 		| cut -d' ' -f 1 >> $testroot/stdout.expected
@@ -470,6 +472,8 @@ test_commit_path_prefix() {
 	fi
 
 	echo "diff $commit2 $commit3" > $testroot/stdout.expected
+	echo "commit - $commit2" >> $testroot/stdout.expected
+	echo "commit + $commit3" >> $testroot/stdout.expected
 	echo -n 'blob - ' >> $testroot/stdout.expected
 	got tree -r $testroot/repo -c $commit2 -i gamma | grep 'delta$' \
 		| cut -d' ' -f 1 | sed -e 's/$/ (mode 644)/' \
blob - 74dae87bde2cac25abaa0af2a1c84df2209884ca
blob + a5ad3625b8799d5d92ac2652db6bc28541dfad27
--- regress/cmdline/diff.sh
+++ regress/cmdline/diff.sh
@@ -32,7 +32,9 @@ test_diff_basic() {
 	echo "new file" > $testroot/wt/new
 	(cd $testroot/wt && got add new >/dev/null)
 
-	echo "diff $head_rev $testroot/wt" > $testroot/stdout.expected
+	echo "diff $testroot/wt" > $testroot/stdout.expected
+	echo "commit - $head_rev" >> $testroot/stdout.expected
+	echo "path + $testroot/wt" >> $testroot/stdout.expected
 	echo -n 'blob - ' >> $testroot/stdout.expected
 	got tree -r $testroot/repo -i | grep 'alpha$' | cut -d' ' -f 1 \
 		>> $testroot/stdout.expected
@@ -115,7 +117,9 @@ test_diff_basic() {
 	echo "modified zeta" > $testroot/wt/epsilon/zeta
 
 	# diff several paths in a work tree
-	echo "diff $head_rev $testroot/wt" > $testroot/stdout.expected
+	echo "diff $testroot/wt" > $testroot/stdout.expected
+	echo "commit - $head_rev" >> $testroot/stdout.expected
+	echo "path + $testroot/wt" >> $testroot/stdout.expected
 	echo -n 'blob - ' >> $testroot/stdout.expected
 	got tree -r $testroot/repo -i | grep 'alpha$' | cut -d' ' -f 1 \
 		>> $testroot/stdout.expected
@@ -205,6 +209,8 @@ test_diff_basic() {
 		return 1
 	fi
 	echo "diff refs/heads/master refs/heads/new" > $testroot/stdout.expected
+	echo "commit - $head_rev" >> $testroot/stdout.expected
+	echo "commit + $head_rev" >> $testroot/stdout.expected
 	# diff between the branches is empty
 	cmp -s $testroot/stdout.expected $testroot/stdout
 	ret=$?
@@ -222,6 +228,8 @@ test_diff_basic() {
 		return 1
 	fi
 	echo "diff refs/heads/master refs/heads/new" > $testroot/stdout.expected
+	echo "commit - $head_rev" >> $testroot/stdout.expected
+	echo "commit + $head_rev" >> $testroot/stdout.expected
 	cmp -s $testroot/stdout.expected $testroot/stdout
 	ret=$?
 	if [ $ret -ne 0 ]; then
@@ -238,6 +246,8 @@ test_diff_basic() {
 		return 1
 	fi
 	echo "diff refs/heads/master refs/heads/new" > $testroot/stdout.expected
+	echo "commit - $head_rev" >> $testroot/stdout.expected
+	echo "commit + $head_rev" >> $testroot/stdout.expected
 	cmp -s $testroot/stdout.expected $testroot/stdout
 	ret=$?
 	if [ $ret -ne 0 ]; then
@@ -254,7 +264,9 @@ test_diff_basic() {
 		test_done "$testroot" "1"
 		return 1
 	fi
-	echo "diff $head_rev $testroot/wt" > $testroot/stdout.expected
+	echo "diff $testroot/wt" > $testroot/stdout.expected
+	echo "commit - $head_rev" >> $testroot/stdout.expected
+	echo "path + $testroot/wt" >> $testroot/stdout.expected
 	echo 'blob - /dev/null' >> $testroot/stdout.expected
 	echo 'file + master' >> $testroot/stdout.expected
 	echo '--- /dev/null' >> $testroot/stdout.expected
@@ -294,7 +306,9 @@ test_diff_basic() {
 	fi
 
 	# a single argument which can be resolved to a path is not ambiguous
-	echo "diff $head_rev $testroot/wt" > $testroot/stdout.expected
+	echo "diff $testroot/wt" > $testroot/stdout.expected
+	echo "commit - $head_rev" >> $testroot/stdout.expected
+	echo "path + $testroot/wt" >> $testroot/stdout.expected
 	echo 'blob - /dev/null' >> $testroot/stdout.expected
 	echo 'file + new' >> $testroot/stdout.expected
 	echo '--- /dev/null' >> $testroot/stdout.expected
@@ -346,7 +360,9 @@ test_diff_basic() {
 		return 1
 	fi
 
-	echo "diff $head_rev $testroot/wt" > $testroot/stdout.expected
+	echo "diff $testroot/wt" > $testroot/stdout.expected
+	echo "commit - $head_rev" >> $testroot/stdout.expected
+	echo "path + $testroot/wt" >> $testroot/stdout.expected
 	echo 'blob - /dev/null' >> $testroot/stdout.expected
 	echo 'file + new' >> $testroot/stdout.expected
 	echo '--- /dev/null' >> $testroot/stdout.expected
@@ -419,7 +435,9 @@ test_diff_shows_conflict() {
 		return 1
 	fi
 
-	echo "diff $head_rev $testroot/wt" > $testroot/stdout.expected
+	echo "diff $testroot/wt" > $testroot/stdout.expected
+	echo "commit - $head_rev" >> $testroot/stdout.expected
+	echo "path + $testroot/wt" >> $testroot/stdout.expected
 	echo -n 'blob - ' >> $testroot/stdout.expected
 	got tree -r $testroot/repo -i | grep 'numbers$' | cut -d' ' -f 1 \
 		>> $testroot/stdout.expected
@@ -482,6 +500,8 @@ test_diff_tag() {
 	(cd $testroot/repo && git tag -m "test" $tag2)
 
 	echo "diff $commit_id0 refs/tags/$tag1" > $testroot/stdout.expected
+	echo "commit - $commit_id0" >> $testroot/stdout.expected
+	echo "commit + $commit_id1" >> $testroot/stdout.expected
 	echo -n 'blob - ' >> $testroot/stdout.expected
 	got tree -r $testroot/repo -c $commit_id0 -i | grep 'alpha$' | \
 		cut -d' ' -f 1 >> $testroot/stdout.expected
@@ -504,6 +524,8 @@ test_diff_tag() {
 	fi
 
 	echo "diff refs/tags/$tag1 refs/tags/$tag2" > $testroot/stdout.expected
+	echo "commit - $commit_id1" >> $testroot/stdout.expected
+	echo "commit + $commit_id2" >> $testroot/stdout.expected
 	echo "blob - /dev/null" >> $testroot/stdout.expected
 	echo -n 'blob + ' >> $testroot/stdout.expected
 	got tree -r $testroot/repo -i -c $commit_id2 | grep 'new$' | \
@@ -543,6 +565,8 @@ test_diff_lightweight_tag() {
 	(cd $testroot/repo && git tag $tag2)
 
 	echo "diff $commit_id0 refs/tags/$tag1" > $testroot/stdout.expected
+	echo "commit - $commit_id0" >> $testroot/stdout.expected
+	echo "commit + $commit_id1" >> $testroot/stdout.expected
 	echo -n 'blob - ' >> $testroot/stdout.expected
 	got tree -r $testroot/repo -c $commit_id0 -i | grep 'alpha$' | \
 		cut -d' ' -f 1 >> $testroot/stdout.expected
@@ -565,6 +589,8 @@ test_diff_lightweight_tag() {
 	fi
 
 	echo "diff refs/tags/$tag1 refs/tags/$tag2" > $testroot/stdout.expected
+	echo "commit - $commit_id1" >> $testroot/stdout.expected
+	echo "commit + $commit_id2" >> $testroot/stdout.expected
 	echo "blob - /dev/null" >> $testroot/stdout.expected
 	echo -n 'blob + ' >> $testroot/stdout.expected
 	got tree -r $testroot/repo -i -c $commit_id2 | grep 'new$' | \
@@ -599,7 +625,9 @@ test_diff_ignore_whitespace() {
 
 	(cd $testroot/wt && got diff -w > $testroot/stdout)
 
-	echo "diff $commit_id0 $testroot/wt" > $testroot/stdout.expected
+	echo "diff $testroot/wt" > $testroot/stdout.expected
+	echo "commit - $commit_id0" >> $testroot/stdout.expected
+	echo "path + $testroot/wt" >> $testroot/stdout.expected
 	echo -n 'blob - ' >> $testroot/stdout.expected
 	got tree -r $testroot/repo -c $commit_id0 -i | grep 'alpha$' | \
 		cut -d' ' -f 1 >> $testroot/stdout.expected
@@ -675,7 +703,9 @@ test_diff_symlinks_in_work_tree() {
 	(cd $testroot/wt && got add zeta.link > /dev/null)
 	(cd $testroot/wt && got diff > $testroot/stdout)
 
-	echo "diff $commit_id1 $testroot/wt" > $testroot/stdout.expected
+	echo "diff $testroot/wt" > $testroot/stdout.expected
+	echo "commit - $commit_id1" >> $testroot/stdout.expected
+	echo "path + $testroot/wt" >> $testroot/stdout.expected
 	echo -n 'blob - ' >> $testroot/stdout.expected
 	got tree -r $testroot/repo -c $commit_id1 -i | \
 		grep 'alpha.link@ -> alpha$' | \
@@ -776,6 +806,8 @@ test_diff_symlinks_in_repo() {
 	got diff -r $testroot/repo $commit_id1 $commit_id2 > $testroot/stdout
 
 	echo "diff $commit_id1 $commit_id2" > $testroot/stdout.expected
+	echo "commit - $commit_id1" >> $testroot/stdout.expected
+	echo "commit + $commit_id2" >> $testroot/stdout.expected
 	echo -n 'blob - ' >> $testroot/stdout.expected
 	got tree -r $testroot/repo -c $commit_id1 -i | \
 		grep 'alpha.link@ -> alpha$' | \
@@ -881,7 +913,9 @@ test_diff_binary_files() {
 	printf '\377\377\0\0\377\377\0\0' > $testroot/wt/foo
 	(cd $testroot/wt && got add foo >/dev/null)
 
-	echo "diff $head_rev $testroot/wt" > $testroot/stdout.expected
+	echo "diff $testroot/wt" > $testroot/stdout.expected
+	echo "commit - $head_rev" >> $testroot/stdout.expected
+	echo "path + $testroot/wt" >> $testroot/stdout.expected
 	echo 'blob - /dev/null' >> $testroot/stdout.expected
 	echo 'file + foo' >> $testroot/stdout.expected
 	echo "Binary files /dev/null and foo differ" \
@@ -896,7 +930,9 @@ test_diff_binary_files() {
 		return 1
 	fi
 
-	echo "diff $head_rev $testroot/wt" > $testroot/stdout.expected
+	echo "diff $testroot/wt" > $testroot/stdout.expected
+	echo "commit - $head_rev" >> $testroot/stdout.expected
+	echo "path + $testroot/wt" >> $testroot/stdout.expected
 	echo 'blob - /dev/null' >> $testroot/stdout.expected
 	echo 'file + foo' >> $testroot/stdout.expected
 	echo '--- /dev/null' >> $testroot/stdout.expected
@@ -919,7 +955,9 @@ test_diff_binary_files() {
 
 	printf '\377\200\0\0\377\200\0\0' > $testroot/wt/foo
 
-	echo "diff $head_rev $testroot/wt" > $testroot/stdout.expected
+	echo "diff $testroot/wt" > $testroot/stdout.expected
+	echo "commit - $head_rev" >> $testroot/stdout.expected
+	echo "path + $testroot/wt" >> $testroot/stdout.expected
 	echo -n 'blob - ' >> $testroot/stdout.expected
 	got tree -r $testroot/repo -i | grep 'foo$' | cut -d' ' -f 1 \
 		>> $testroot/stdout.expected
@@ -965,6 +1003,8 @@ test_diff_commits() {
 	new_id1=`get_blob_id $testroot/repo "" new`
 
 	echo "diff $commit_id0 refs/heads/master" > $testroot/stdout.expected
+	echo "commit - $commit_id0" >> $testroot/stdout.expected
+	echo "commit + $commit_id1" >> $testroot/stdout.expected
 	echo "blob - $alpha_id0" >> $testroot/stdout.expected
 	echo "blob + $alpha_id1" >> $testroot/stdout.expected
 	echo '--- alpha' >> $testroot/stdout.expected
@@ -1007,6 +1047,8 @@ test_diff_commits() {
 
 	# same diff with commit object IDs
 	echo "diff $commit_id0 $commit_id1" > $testroot/stdout.expected
+	echo "commit - $commit_id0" >> $testroot/stdout.expected
+	echo "commit + $commit_id1" >> $testroot/stdout.expected
 	echo "blob - $alpha_id0" >> $testroot/stdout.expected
 	echo "blob + $alpha_id1" >> $testroot/stdout.expected
 	echo '--- alpha' >> $testroot/stdout.expected
@@ -1038,6 +1080,8 @@ test_diff_commits() {
 
 	# same diff, filtered by paths
 	echo "diff $commit_id0 $commit_id1" > $testroot/stdout.expected
+	echo "commit - $commit_id0" >> $testroot/stdout.expected
+	echo "commit + $commit_id1" >> $testroot/stdout.expected
 	echo "blob - $alpha_id0" >> $testroot/stdout.expected
 	echo "blob + $alpha_id1" >> $testroot/stdout.expected
 	echo '--- alpha' >> $testroot/stdout.expected
@@ -1066,6 +1110,8 @@ test_diff_commits() {
 	fi
 
 	echo "diff $commit_id0 $commit_id1" > $testroot/stdout.expected
+	echo "commit - $commit_id0" >> $testroot/stdout.expected
+	echo "commit + $commit_id1" >> $testroot/stdout.expected
 	echo "blob - $beta_id0 (mode 644)" >> $testroot/stdout.expected
 	echo 'blob + /dev/null' >> $testroot/stdout.expected
 	echo '--- beta' >> $testroot/stdout.expected
blob - 5fd35f7af5ce6affa60ec097e6dc2a85866e1596
blob + 920610e37ab899c23ebcb98ee645c7044a456669
--- regress/cmdline/import.sh
+++ regress/cmdline/import.sh
@@ -65,7 +65,9 @@ test_import_basic() {
 	echo " " >> $testroot/stdout.expected
 	echo " init" >> $testroot/stdout.expected
 	echo " " >> $testroot/stdout.expected
-	echo "diff /dev/null $tree_id" >> $testroot/stdout.expected
+	echo "diff /dev/null $head_commit" >> $testroot/stdout.expected
+	echo "commit - /dev/null" >> $testroot/stdout.expected
+	echo "commit + $head_commit" >> $testroot/stdout.expected
 	echo "blob - /dev/null" >> $testroot/stdout.expected
 	echo "blob + $id_alpha (mode 644)" >> $testroot/stdout.expected
 	echo "--- /dev/null" >> $testroot/stdout.expected
blob - 7bdd9c2e0c5b082a18dcf878227aa026335384cf
blob + f322b4b27c50f6f8a96b7c8322063b329f68c34c
--- regress/cmdline/log.sh
+++ regress/cmdline/log.sh
@@ -352,6 +352,8 @@ test_log_patch_added_file() {
 	local commit_id1=`git_show_head $testroot/repo`
 
 	echo "commit $commit_id1 (master)" > $testroot/stdout.expected
+	echo "commit - $commit_id0" >> $testroot/stdout.expected
+	echo "commit + $commit_id1" >> $testroot/stdout.expected
 	# This used to fail with 'got: no such entry found in tree'
 	(cd $testroot/wt && got log -l1 -p new > $testroot/stdout.patch)
 	ret=$?
blob - 53e50a517d427f5e9cae1a7f8809b8098360d3a8
blob + 6af534fcbae888d5dca254aeb7233897e4e7c8fb
--- regress/cmdline/revert.sh
+++ regress/cmdline/revert.sh
@@ -527,7 +527,9 @@ EOF
 		return 1
 	fi
 
-	echo "diff $commit_id $testroot/wt" > $testroot/stdout.expected
+	echo "diff $testroot/wt" > $testroot/stdout.expected
+	echo "commit - $commit_id" >> $testroot/stdout.expected
+	echo "path + $testroot/wt" >> $testroot/stdout.expected
 	echo -n 'blob - ' >> $testroot/stdout.expected
 	got tree -r $testroot/repo -i -c $commit_id \
 		| grep 'numbers$' | cut -d' ' -f 1 \
@@ -625,7 +627,9 @@ EOF
 
 	(cd $testroot/wt && got diff > $testroot/stdout)
 
-	echo "diff $commit_id $testroot/wt" > $testroot/stdout.expected
+	echo "diff $testroot/wt" > $testroot/stdout.expected
+	echo "commit - $commit_id" >> $testroot/stdout.expected
+	echo "path + $testroot/wt" >> $testroot/stdout.expected
 	echo -n 'blob - ' >> $testroot/stdout.expected
 	got tree -r $testroot/repo -i -c $commit_id \
 		| grep 'numbers$' | cut -d' ' -f 1 \
@@ -693,7 +697,9 @@ EOF
 
 	(cd $testroot/wt && got diff > $testroot/stdout)
 
-	echo "diff $commit_id $testroot/wt" > $testroot/stdout.expected
+	echo "diff $testroot/wt" > $testroot/stdout.expected
+	echo "commit - $commit_id" >> $testroot/stdout.expected
+	echo "path + $testroot/wt" >> $testroot/stdout.expected
 	echo -n 'blob - ' >> $testroot/stdout.expected
 	got tree -r $testroot/repo -i -c $commit_id \
 		| grep 'numbers$' | cut -d' ' -f 1 \
blob - de1799daf93b3a96d8c0045ef984aae3eab4b368
blob + a5219920d083205b95e1b41ccd7b95d63d7a7752
--- regress/cmdline/stage.sh
+++ regress/cmdline/stage.sh
@@ -933,7 +933,9 @@ test_stage_diff() {
 
 	(cd $testroot/wt && got diff > $testroot/stdout)
 
-	echo "diff $head_commit $testroot/wt" > $testroot/stdout.expected
+	echo "diff $testroot/wt" > $testroot/stdout.expected
+	echo "commit - $head_commit" >> $testroot/stdout.expected
+	echo "path + $testroot/wt" >> $testroot/stdout.expected
 	echo -n 'blob - ' >> $testroot/stdout.expected
 	(cd $testroot/wt && got stage -l alpha) | cut -d' ' -f 1 | tr -d '\n' \
 		>> $testroot/stdout.expected
@@ -965,8 +967,9 @@ test_stage_diff() {
 
 	(cd $testroot/wt && got diff -s > $testroot/stdout)
 
-	echo "diff $head_commit $testroot/wt (staged changes)" \
-		> $testroot/stdout.expected
+	echo "diff -s $testroot/wt" > $testroot/stdout.expected
+	echo "commit - $head_commit" >> $testroot/stdout.expected
+	echo "path + $testroot/wt (staged changes)" >> $testroot/stdout.expected
 	echo -n 'blob - ' >> $testroot/stdout.expected
 	got tree -r $testroot/repo -i | grep 'alpha$' | cut -d' ' -f 1 \
 		>> $testroot/stdout.expected
@@ -1379,6 +1382,8 @@ test_stage_commit() {
 
 	echo "diff $first_commit $head_commit" \
 		> $testroot/stdout.expected
+	echo "commit - $first_commit" >> $testroot/stdout.expected
+	echo "commit + $head_commit" >> $testroot/stdout.expected
 	echo -n 'blob - ' >> $testroot/stdout.expected
 	got tree -r $testroot/repo -i -c $first_commit | \
 		grep 'alpha$' | cut -d' ' -f 1 \
@@ -1586,8 +1591,9 @@ EOF
 
 	(cd $testroot/wt && got diff -s > $testroot/stdout)
 
-	echo "diff $commit_id $testroot/wt (staged changes)" \
-		> $testroot/stdout.expected
+	echo "diff -s $testroot/wt" > $testroot/stdout.expected
+	echo "commit - $commit_id" >> $testroot/stdout.expected
+	echo "path + $testroot/wt (staged changes)" >> $testroot/stdout.expected
 	echo -n 'blob - ' >> $testroot/stdout.expected
 	got tree -r $testroot/repo -i -c $commit_id \
 		| grep 'numbers$' | cut -d' ' -f 1 \
@@ -1692,8 +1698,9 @@ EOF
 
 	(cd $testroot/wt && got diff -s > $testroot/stdout)
 
-	echo "diff $commit_id $testroot/wt (staged changes)" \
-		> $testroot/stdout.expected
+	echo "diff -s $testroot/wt" > $testroot/stdout.expected
+	echo "commit - $commit_id" >> $testroot/stdout.expected
+	echo "path + $testroot/wt (staged changes)" >> $testroot/stdout.expected
 	echo -n 'blob - ' >> $testroot/stdout.expected
 	got tree -r $testroot/repo -i -c $commit_id \
 		| grep 'numbers$' | cut -d' ' -f 1 \
@@ -1843,8 +1850,9 @@ EOF
 
 	(cd $testroot/wt && got diff -s > $testroot/stdout)
 
-	echo "diff $commit_id $testroot/wt (staged changes)" \
-		> $testroot/stdout.expected
+	echo "diff -s $testroot/wt" > $testroot/stdout.expected
+	echo "commit - $commit_id" >> $testroot/stdout.expected
+	echo "path + $testroot/wt (staged changes)" >> $testroot/stdout.expected
 	echo -n 'blob - ' >> $testroot/stdout.expected
 	got tree -r $testroot/repo -i -c $commit_id \
 		| grep 'numbers$' | cut -d' ' -f 1 \
@@ -1881,7 +1889,9 @@ EOF
 
 	(cd $testroot/wt && got diff > $testroot/stdout)
 
-	echo "diff $commit_id $testroot/wt" > $testroot/stdout.expected
+	echo "diff $testroot/wt" > $testroot/stdout.expected
+	echo "commit - $commit_id" >> $testroot/stdout.expected
+	echo "path + $testroot/wt" >> $testroot/stdout.expected
 	echo -n 'blob - ' >> $testroot/stdout.expected
 	(cd $testroot/wt && got stage -l numbers) | cut -d' ' -f 1 | \
 		tr -d '\n' >> $testroot/stdout.expected
@@ -1946,8 +1956,9 @@ test_stage_patch_added() {
 
 	(cd $testroot/wt && got diff -s > $testroot/stdout)
 
-	echo "diff $commit_id $testroot/wt (staged changes)" \
-		> $testroot/stdout.expected
+	echo "diff -s $testroot/wt" > $testroot/stdout.expected
+	echo "commit - $commit_id" >> $testroot/stdout.expected
+	echo "path + $testroot/wt (staged changes)" >> $testroot/stdout.expected
 	echo 'blob - /dev/null' >> $testroot/stdout.expected
 	echo -n 'blob + ' >> $testroot/stdout.expected
 	(cd $testroot/wt && got stage -l epsilon/new) | cut -d' ' -f 1 \
@@ -2070,8 +2081,9 @@ test_stage_patch_removed() {
 
 	(cd $testroot/wt && got diff -s > $testroot/stdout)
 
-	echo "diff $commit_id $testroot/wt (staged changes)" \
-		> $testroot/stdout.expected
+	echo "diff -s $testroot/wt" > $testroot/stdout.expected
+	echo "commit - $commit_id" >> $testroot/stdout.expected
+	echo "path + $testroot/wt (staged changes)" >> $testroot/stdout.expected
 	echo -n 'blob - ' >> $testroot/stdout.expected
 	(cd $testroot/wt && got stage -l beta) | cut -d' ' -f 1 \
 		>> $testroot/stdout.expected
@@ -2288,8 +2300,9 @@ EOF
 
 	(cd $testroot/wt && got diff -s > $testroot/stdout)
 
-	echo "diff $commit_id $testroot/wt (staged changes)" \
-		> $testroot/stdout.expected
+	echo "diff -s $testroot/wt" > $testroot/stdout.expected
+	echo "commit - $commit_id" >> $testroot/stdout.expected
+	echo "path + $testroot/wt (staged changes)" >> $testroot/stdout.expected
 	echo -n 'blob - ' >> $testroot/stdout.expected
 	got tree -r $testroot/repo -i -c $commit_id \
 		| grep 'numbers$' | cut -d' ' -f 1 \
@@ -2481,8 +2494,9 @@ EOF
 
 	(cd $testroot/wt && got diff -s > $testroot/stdout)
 
-	echo "diff $head_commit $testroot/wt (staged changes)" \
-		> $testroot/stdout.expected
+	echo "diff -s $testroot/wt" > $testroot/stdout.expected
+	echo "commit - $head_commit" >> $testroot/stdout.expected
+	echo "path + $testroot/wt (staged changes)" >> $testroot/stdout.expected
 	echo -n 'blob - ' >> $testroot/stdout.expected
 	got tree -r $testroot/repo -i | grep 'alpha.link@ -> alpha$' | \
 		cut -d' ' -f 1 >> $testroot/stdout.expected
@@ -2805,8 +2819,9 @@ EOF
 
 	(cd $testroot/wt && got diff -s > $testroot/stdout)
 
-	echo "diff $head_commit $testroot/wt (staged changes)" \
-		> $testroot/stdout.expected
+	echo "diff -s $testroot/wt" > $testroot/stdout.expected
+	echo "commit - $head_commit" >> $testroot/stdout.expected
+	echo "path + $testroot/wt (staged changes)" >> $testroot/stdout.expected
 	echo -n 'blob - ' >> $testroot/stdout.expected
 	got tree -r $testroot/repo -i | grep 'alpha.link@ -> alpha$' | \
 		cut -d' ' -f 1 >> $testroot/stdout.expected
blob - d3635a2490f28f8544c57bf9a75a797727642b1f
blob + db343e34a9e2e72ea2922eef2bbc8f19203297ce
--- regress/cmdline/unstage.sh
+++ regress/cmdline/unstage.sh
@@ -336,8 +336,9 @@ EOF
 
 	(cd $testroot/wt && got diff -s > $testroot/stdout)
 
-	echo "diff $commit_id $testroot/wt (staged changes)" \
-		> $testroot/stdout.expected
+	echo "diff -s $testroot/wt" > $testroot/stdout.expected
+	echo "commit - $commit_id" >> $testroot/stdout.expected
+	echo "path + $testroot/wt (staged changes)" >> $testroot/stdout.expected
 	echo -n 'blob - ' >> $testroot/stdout.expected
 	got tree -r $testroot/repo -i -c $commit_id \
 		| grep 'numbers$' | cut -d' ' -f 1 \
@@ -371,7 +372,9 @@ EOF
 	fi
 
 	(cd $testroot/wt && got diff > $testroot/stdout)
-	echo "diff $commit_id $testroot/wt" > $testroot/stdout.expected
+	echo "diff $testroot/wt" > $testroot/stdout.expected
+	echo "commit - $commit_id" >> $testroot/stdout.expected
+	echo "path + $testroot/wt" >> $testroot/stdout.expected
 	echo -n 'blob - ' >> $testroot/stdout.expected
 	(cd $testroot/wt && got stage -l numbers) | cut -d' ' -f 1  | \
 		tr -d '\n' >> $testroot/stdout.expected
@@ -478,8 +481,9 @@ EOF
 
 	(cd $testroot/wt && got diff -s > $testroot/stdout)
 
-	echo "diff $commit_id $testroot/wt (staged changes)" \
-		> $testroot/stdout.expected
+	echo "diff -s $testroot/wt" > $testroot/stdout.expected
+	echo "commit - $commit_id" >> $testroot/stdout.expected
+	echo "path + $testroot/wt (staged changes)" >> $testroot/stdout.expected
 	echo -n 'blob - ' >> $testroot/stdout.expected
 	got tree -r $testroot/repo -i -c $commit_id \
 		| grep 'numbers$' | cut -d' ' -f 1 \
@@ -513,7 +517,9 @@ EOF
 	fi
 
 	(cd $testroot/wt && got diff > $testroot/stdout)
-	echo "diff $commit_id $testroot/wt" > $testroot/stdout.expected
+	echo "diff $testroot/wt" > $testroot/stdout.expected
+	echo "commit - $commit_id" >> $testroot/stdout.expected
+	echo "path + $testroot/wt" >> $testroot/stdout.expected
 	echo -n 'blob - ' >> $testroot/stdout.expected
 	(cd $testroot/wt && got stage -l numbers) | cut -d' ' -f 1 | \
 		tr -d '\n' >> $testroot/stdout.expected
@@ -627,7 +633,9 @@ EOF
 
 	(cd $testroot/wt && got diff > $testroot/stdout)
 
-	echo "diff $commit_id $testroot/wt" > $testroot/stdout.expected
+	echo "diff $testroot/wt" > $testroot/stdout.expected
+	echo "commit - $commit_id" >> $testroot/stdout.expected
+	echo "path + $testroot/wt" >> $testroot/stdout.expected
 	echo -n 'blob - ' >> $testroot/stdout.expected
 	got tree -r $testroot/repo -i -c $commit_id \
 		| grep 'numbers$' | cut -d' ' -f 1 \
@@ -718,7 +726,9 @@ test_unstage_patch_added() {
 
 	(cd $testroot/wt && got diff > $testroot/stdout)
 
-	echo "diff $commit_id $testroot/wt" > $testroot/stdout.expected
+	echo "diff $testroot/wt" > $testroot/stdout.expected
+	echo "commit - $commit_id" >> $testroot/stdout.expected
+	echo "path + $testroot/wt" >> $testroot/stdout.expected
 	echo 'blob - /dev/null' >> $testroot/stdout.expected
 	echo 'file + epsilon/new' >> $testroot/stdout.expected
 	echo "--- /dev/null" >> $testroot/stdout.expected
@@ -784,8 +794,9 @@ test_unstage_patch_removed() {
 
 	(cd $testroot/wt && got diff > $testroot/stdout)
 
-	echo "diff $commit_id $testroot/wt" \
-		> $testroot/stdout.expected
+	echo "diff $testroot/wt" > $testroot/stdout.expected
+	echo "commit - $commit_id" >> $testroot/stdout.expected
+	echo "path + $testroot/wt" >> $testroot/stdout.expected
 	echo -n 'blob - ' >> $testroot/stdout.expected
 	got tree -r $testroot/repo -i | grep 'beta$' | cut -d' ' -f 1 \
 		>> $testroot/stdout.expected
@@ -885,7 +896,9 @@ EOF
 
 	(cd $testroot/wt && got diff > $testroot/stdout)
 
-	echo "diff $commit_id $testroot/wt" > $testroot/stdout.expected
+	echo "diff $testroot/wt" > $testroot/stdout.expected
+	echo "commit - $commit_id" >> $testroot/stdout.expected
+	echo "path + $testroot/wt" >> $testroot/stdout.expected
 	echo -n 'blob - ' >> $testroot/stdout.expected
 	(cd $testroot/wt && got stage -l numbers) | cut -d' ' -f 1 | \
 		tr -d '\n' >> $testroot/stdout.expected
@@ -909,8 +922,9 @@ EOF
 	fi
 
 	(cd $testroot/wt && got diff -s > $testroot/stdout)
-	echo "diff $commit_id $testroot/wt (staged changes)" \
-		> $testroot/stdout.expected
+	echo "diff -s $testroot/wt" > $testroot/stdout.expected
+	echo "commit - $commit_id" >> $testroot/stdout.expected
+	echo "path + $testroot/wt (staged changes)" >> $testroot/stdout.expected
 	echo -n 'blob - ' >> $testroot/stdout.expected
 	got tree -r $testroot/repo -i -c $commit_id \
 		| grep 'numbers$' | cut -d' ' -f 1 \
blob - b3ecc917b19ff28675c5fcf81551231911dbc286
blob + 9f1d03103e3ac9c1cb0474bc04a51cb2ef5e4651
--- regress/cmdline/update.sh
+++ regress/cmdline/update.sh
@@ -1017,7 +1017,9 @@ test_update_conflict_wt_rm_vs_repo_edit() {
 
 	# 'got diff' should show post-update contents of beta being deleted
 	local head_rev=`git_show_head $testroot/repo`
-	echo "diff $head_rev $testroot/wt" > $testroot/stdout.expected
+	echo "diff $testroot/wt" > $testroot/stdout.expected
+	echo "commit - $head_rev"  >> $testroot/stdout.expected
+	echo "path + $testroot/wt" >> $testroot/stdout.expected
 	echo -n 'blob - ' >> $testroot/stdout.expected
 	got tree -r $testroot/repo -i | grep 'beta$' | cut -d' ' -f 1 \
 		>> $testroot/stdout.expected
blob - cb01bcd4c601aae790a8c7ed5467fd7dc9bc7f1b
blob + 22fafd47d331b7d66d0a3f15ad4d8ad866da79e1
--- tog/tog.c
+++ tog/tog.c
@@ -3903,7 +3903,8 @@ open_diff_view(struct tog_view *view, struct got_objec
 			goto done;
 
 		err = add_color(&s->colors,
-		    "^(commit [0-9a-f]|parent [0-9]|(blob|file) [-+] |"
+		    "^(commit [0-9a-f]|parent [0-9]|"
+		    "(blob|file|tree|commit) [-+] |"
 		    "[MDmA]  [^ ])", TOG_COLOR_DIFF_META,
 		    get_color_value("TOG_COLOR_DIFF_META"));
 		if (err)