Commit Diff


commit - 2539ccf28b5b0e6be13e9a7f2063a104efa7217b
commit + 30f6c0c64b5c08d79e22cc2c0bd1f1340288a7f5
blob - 607d9e411094bc0ccc199c39ccb264406f3ef169
blob + bdded67d33703dcd1607e51d0887179126d00a83
--- got/got.1
+++ got/got.1
@@ -839,13 +839,13 @@ This option has no effect if the specified
 is never traversed.
 .El
 .Tg di
-.It Cm diff Oo Fl a Oc Oo Fl C Ar number Oc Oo Fl r Ar repository-path Oc Oo Fl s Oc Oo Fl w Oc Op Ar object1 Ar object2 | Ar path
+.It Cm diff Oo Fl a Oc Oo Fl C Ar number Oc Oo Fl r Ar repository-path Oc Oo Fl s Oc Oo Fl P Oc Oo Fl w Oc Op Ar object1 Ar object2 | Ar path ...
 .Dl (alias: Cm di )
-When invoked within a work tree with less than two arguments, display
+When invoked within a work tree without any arguments, display all
 local changes in the work tree.
-If a
+If one or more
 .Ar path
-is specified, only show changes within this path.
+arguments are specified, only show changes within the specified paths.
 .Pp
 If two arguments are provided, treat each argument as a reference, a tag
 name, or an object ID SHA1 hash, and display differences between the
@@ -853,6 +853,12 @@ corresponding objects.
 Both objects must be of the same type (blobs, trees, or commits).
 An abbreviated hash argument will be expanded to a full SHA1 hash
 automatically, provided the abbreviation is unique.
+If none of these interpretations produce a valid result or if the
+.Fl P
+option is used,
+and if
+.Cm got diff
+is running in a work tree, attempt to interpret the two arguments as paths.
 .Pp
 The options for
 .Cm got diff
@@ -874,6 +880,13 @@ work tree, use the repository path associated with thi
 Show changes staged with
 .Cm got stage
 instead of showing local changes in the work tree.
+This option is only valid when
+.Cm got diff
+is invoked in a work tree.
+.It Fl P
+Interpret all arguments as paths only.
+This option can be used to resolve ambiguity in cases where paths
+look like tag names, reference names, or object IDs.
 This option is only valid when
 .Cm got diff
 is invoked in a work tree.
blob - 369d7d7369f7a373f6eef08f611035ae2b846fe5
blob + ad7b886219f18cc55190a65296d733c9de7a8214
--- got/got.c
+++ got/got.c
@@ -4277,7 +4277,7 @@ __dead static void
 usage_diff(void)
 {
 	fprintf(stderr, "usage: %s diff [-a] [-C number] [-r repository-path] "
-	    "[-s] [-w] [object1 object2 | path]\n", getprogname());
+	    "[-s] [-w] [-P] [object1 object2 | path ...]\n", getprogname());
 	exit(1);
 }
 
@@ -4486,17 +4486,18 @@ cmd_diff(int argc, char *argv[])
 	struct got_repository *repo = NULL;
 	struct got_worktree *worktree = NULL;
 	char *cwd = NULL, *repo_path = NULL;
-	struct got_object_id *id1 = NULL, *id2 = NULL;
-	const char *id_str1 = NULL, *id_str2 = NULL;
-	char *label1 = NULL, *label2 = NULL;
+	struct got_object_id *ids[2] = { NULL, NULL };
+	char *labels[2] = { NULL, NULL };
 	int type1, type2;
-	int diff_context = 3, diff_staged = 0, ignore_whitespace = 0, ch;
-	int force_text_diff = 0;
+	int diff_context = 3, diff_staged = 0, ignore_whitespace = 0, ch, i;
+	int force_text_diff = 0, force_path = 0, rflag = 0;
 	const char *errstr;
-	char *path = NULL;
 	struct got_reflist_head refs;
+	struct got_pathlist_head paths;
+	struct got_pathlist_entry *pe;
 
 	TAILQ_INIT(&refs);
+	TAILQ_INIT(&paths);
 
 #ifndef PROFILE
 	if (pledge("stdio rpath wpath cpath flock proc exec sendfd unveil",
@@ -4504,7 +4505,7 @@ cmd_diff(int argc, char *argv[])
 		err(1, "pledge");
 #endif
 
-	while ((ch = getopt(argc, argv, "aC:r:sw")) != -1) {
+	while ((ch = getopt(argc, argv, "aC:r:swP")) != -1) {
 		switch (ch) {
 		case 'a':
 			force_text_diff = 1;
@@ -4521,6 +4522,7 @@ cmd_diff(int argc, char *argv[])
 				return got_error_from_errno2("realpath",
 				    optarg);
 			got_path_strip_trailing_slashes(repo_path);
+			rflag = 1;
 			break;
 		case 's':
 			diff_staged = 1;
@@ -4528,6 +4530,9 @@ cmd_diff(int argc, char *argv[])
 		case 'w':
 			ignore_whitespace = 1;
 			break;
+		case 'P':
+			force_path = 1;
+			break;
 		default:
 			usage_diff();
 			/* NOTREACHED */
@@ -4542,54 +4547,48 @@ cmd_diff(int argc, char *argv[])
 		error = got_error_from_errno("getcwd");
 		goto done;
 	}
-	if (argc <= 1) {
-		if (repo_path)
-			errx(1,
-			    "-r option can't be used when diffing a work tree");
+
+	if (repo_path == NULL) {
 		error = got_worktree_open(&worktree, cwd);
-		if (error) {
-			if (error->code == GOT_ERR_NOT_WORKTREE)
-				error = wrap_not_worktree_error(error, "diff",
-				    cwd);
+		if (error && error->code != GOT_ERR_NOT_WORKTREE)
 			goto done;
-		}
-		repo_path = strdup(got_worktree_get_repo_path(worktree));
-		if (repo_path == NULL) {
-			error = got_error_from_errno("strdup");
-			goto done;
-		}
-		if (argc == 1) {
-			error = got_worktree_resolve_path(&path, worktree,
-			    argv[0]);
-			if (error)
+		else
+			error = NULL;
+		if (worktree) {
+			repo_path =
+			    strdup(got_worktree_get_repo_path(worktree));
+			if (repo_path == NULL) {
+				error = got_error_from_errno("strdup");
 				goto done;
+			}
 		} else {
-			path = strdup("");
-			if (path == NULL) {
+			repo_path = strdup(cwd);
+			if (repo_path == NULL) {
 				error = got_error_from_errno("strdup");
 				goto done;
 			}
 		}
-	} else if (argc == 2) {
-		if (diff_staged)
-			errx(1, "-s option can't be used when diffing "
-			    "objects in repository");
-		id_str1 = argv[0];
-		id_str2 = argv[1];
+	}
+
+	if (worktree) {
+		repo_path = strdup(got_worktree_get_repo_path(worktree));
 		if (repo_path == NULL) {
-			error = got_worktree_open(&worktree, cwd);
-			if (error && error->code != GOT_ERR_NOT_WORKTREE)
-				goto done;
-			repo_path = strdup(worktree ?
-			    got_worktree_get_repo_path(worktree) : cwd);
+			error = got_error_from_errno("strdup");
+			goto done;
+		}
+	} else {
+		if (repo_path == NULL) {
+			repo_path = strdup(cwd);
 			if (repo_path == NULL) {
 				error = got_error_from_errno("strdup");
 				goto done;
 			}
 		}
-	} else
-		usage_diff();
+	}
 
+	if (force_path && (rflag || worktree == NULL))
+		errx(1, "-P option can only be used when diffing a work tree");
+
 	error = got_repo_open(&repo, repo_path, NULL);
 	free(repo_path);
 	if (error != NULL)
@@ -4600,13 +4599,35 @@ cmd_diff(int argc, char *argv[])
 	if (error)
 		goto done;
 
-	if (argc <= 1) {
+	if (!force_path && argc == 2) {
+		error = got_ref_list(&refs, repo, NULL, got_ref_cmp_by_name,
+		    NULL);
+		if (error)
+			goto done;
+		for (i = 0; i < argc; i++) {
+			error = got_repo_match_object_id(&ids[i], &labels[i],
+			    argv[i], GOT_OBJ_TYPE_ANY, &refs, repo);
+			if (error) {
+				if (error->code != GOT_ERR_NOT_REF &&
+				    error->code != GOT_ERR_NO_OBJ)
+					goto done;
+				error = NULL;
+				break;
+			}
+		}
+	}
+
+	if (worktree != NULL && (ids[0] == NULL || ids[1] == NULL)) {
+		error = get_worktree_paths_from_argv(&paths,
+		    argc, argv, worktree);
+		if (error)
+			goto done;
+	}
+
+	if (!TAILQ_EMPTY(&paths)) {
 		struct print_diff_arg arg;
-		struct got_pathlist_head paths;
 		char *id_str;
 
-		TAILQ_INIT(&paths);
-
 		error = got_object_id_str(&id_str,
 		    got_worktree_get_base_commit_id(worktree));
 		if (error)
@@ -4620,39 +4641,34 @@ cmd_diff(int argc, char *argv[])
 		arg.ignore_whitespace = ignore_whitespace;
 		arg.force_text_diff = force_text_diff;
 
-		error = got_pathlist_append(&paths, path, NULL);
-		if (error)
-			goto done;
-
 		error = got_worktree_status(worktree, &paths, repo, 0,
 		    print_diff, &arg, check_cancelled, NULL);
 		free(id_str);
-		got_pathlist_free(&paths);
 		goto done;
 	}
 
-	error = got_ref_list(&refs, repo, NULL, got_ref_cmp_by_name, NULL);
-	if (error)
-		return error;
-
-	error = got_repo_match_object_id(&id1, &label1, id_str1,
-	    GOT_OBJ_TYPE_ANY, &refs, repo);
-	if (error)
-		goto done;
+	if (ids[0] == NULL || ids[1] == NULL) {
+		if (argc == 2) {
+			error = got_error_fmt(GOT_ERR_NO_OBJ, "%s",
+			    ids[0] ? argv[1] : argv[0]);
+			goto done;
+		} if (worktree == NULL) {
+			error = got_error(GOT_ERR_NOT_WORKTREE);
+			goto done;
+		} else
+			usage_diff();
+	}
+	if (diff_staged)
+		errx(1, "-s option can't be used when diffing "
+		    "objects in repository");
 
-	error = got_repo_match_object_id(&id2, &label2, id_str2,
-	    GOT_OBJ_TYPE_ANY, &refs, repo);
+	error = got_object_get_type(&type1, repo, ids[0]);
 	if (error)
 		goto done;
 
-	error = got_object_get_type(&type1, repo, id1);
+	error = got_object_get_type(&type2, repo, ids[1]);
 	if (error)
 		goto done;
-
-	error = got_object_get_type(&type2, repo, id2);
-	if (error)
-		goto done;
-
 	if (type1 != type2) {
 		error = got_error(GOT_ERR_OBJ_TYPE);
 		goto done;
@@ -4660,18 +4676,18 @@ cmd_diff(int argc, char *argv[])
 
 	switch (type1) {
 	case GOT_OBJ_TYPE_BLOB:
-		error = got_diff_objects_as_blobs(NULL, NULL, id1, id2,
+		error = got_diff_objects_as_blobs(NULL, NULL, ids[0], ids[1],
 		    NULL, NULL, diff_context, ignore_whitespace,
 		    force_text_diff, repo, stdout);
 		break;
 	case GOT_OBJ_TYPE_TREE:
-		error = got_diff_objects_as_trees(NULL, NULL, id1, id2,
+		error = got_diff_objects_as_trees(NULL, NULL, ids[0], ids[1],
 		    "", "", diff_context, ignore_whitespace, force_text_diff,
 		    repo, stdout);
 		break;
 	case GOT_OBJ_TYPE_COMMIT:
-		printf("diff %s %s\n", label1, label2);
-		error = got_diff_objects_as_commits(NULL, NULL, id1, id2,
+		printf("diff %s %s\n", labels[0], labels[1]);
+		error = got_diff_objects_as_commits(NULL, NULL, ids[0], ids[1],
 		    diff_context, ignore_whitespace, force_text_diff, repo,
 		    stdout);
 		break;
@@ -4679,11 +4695,10 @@ cmd_diff(int argc, char *argv[])
 		error = got_error(GOT_ERR_OBJ_TYPE);
 	}
 done:
-	free(label1);
-	free(label2);
-	free(id1);
-	free(id2);
-	free(path);
+	free(labels[0]);
+	free(labels[1]);
+	free(ids[0]);
+	free(ids[1]);
 	if (worktree)
 		got_worktree_close(worktree);
 	if (repo) {
@@ -4691,6 +4706,9 @@ done:
 		if (error == NULL)
 			error = close_err;
 	}
+	TAILQ_FOREACH(pe, &paths, entry)
+		free((char *)pe->path);
+	got_pathlist_free(&paths);
 	got_ref_list_free(&refs);
 	return error;
 }
blob - 9987502cc2a56a00f9c9a0960fb32f8ba17770df
blob + 7bc83f4d3575b51f65002d99ae9beedadfea7495
--- regress/cmdline/diff.sh
+++ regress/cmdline/diff.sh
@@ -66,6 +66,29 @@ test_diff_basic() {
 		return 1
 	fi
 
+	# 'got diff' in a repository without any arguments is an error
+	(cd $testroot/repo && got diff 2> $testroot/stderr)
+	echo "got: no got work tree found" > $testroot/stderr.expected
+	cmp -s $testroot/stderr.expected $testroot/stderr
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stderr.expected $testroot/stderr
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	# 'got diff' in a repository with two arguments requires that
+	# both named objects exist
+	(cd $testroot/repo && got diff $head_rev foo 2> $testroot/stderr)
+	echo "got: foo: object not found" > $testroot/stderr.expected
+	cmp -s $testroot/stderr.expected $testroot/stderr
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stderr.expected $testroot/stderr
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
 	# diff non-existent path
 	(cd $testroot/wt && got diff nonexistent > $testroot/stdout \
 		2> $testroot/stderr)
@@ -80,11 +103,304 @@ test_diff_basic() {
 	fi
 
 	echo "got: nonexistent: No such file or directory" \
+		> $testroot/stderr.expected
+	cmp -s $testroot/stderr.expected $testroot/stderr
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stderr.expected $testroot/stderr
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	echo "modified zeta" > $testroot/wt/epsilon/zeta
+
+	# diff several paths in a work tree
+	echo "diff $head_rev $testroot/wt" > $testroot/stdout.expected
+	echo 'blob - /dev/null' >> $testroot/stdout.expected
+	echo 'file + new' >> $testroot/stdout.expected
+	echo '--- /dev/null' >> $testroot/stdout.expected
+	echo '+++ new' >> $testroot/stdout.expected
+	echo '@@ -0,0 +1 @@' >> $testroot/stdout.expected
+	echo '+new file' >> $testroot/stdout.expected
+	echo -n 'blob - ' >> $testroot/stdout.expected
+	got tree -r $testroot/repo -i | grep 'alpha$' | cut -d' ' -f 1 \
+		>> $testroot/stdout.expected
+	echo 'file + alpha' >> $testroot/stdout.expected
+	echo '--- alpha' >> $testroot/stdout.expected
+	echo '+++ alpha' >> $testroot/stdout.expected
+	echo '@@ -1 +1 @@' >> $testroot/stdout.expected
+	echo '-alpha' >> $testroot/stdout.expected
+	echo '+modified alpha' >> $testroot/stdout.expected
+	echo -n 'blob - ' >> $testroot/stdout.expected
+	got tree -r $testroot/repo -i epsilon | grep 'zeta$' | cut -d' ' -f 1 \
+		>> $testroot/stdout.expected
+	echo 'file + epsilon/zeta' >> $testroot/stdout.expected
+	echo '--- epsilon/zeta' >> $testroot/stdout.expected
+	echo '+++ epsilon/zeta' >> $testroot/stdout.expected
+	echo '@@ -1 +1 @@' >> $testroot/stdout.expected
+	echo '-zeta' >> $testroot/stdout.expected
+	echo '+modified zeta' >> $testroot/stdout.expected
+	echo -n 'blob - ' >> $testroot/stdout.expected
+	got tree -r $testroot/repo -i | grep 'beta$' | cut -d' ' -f 1 \
+		>> $testroot/stdout.expected
+	echo 'file + /dev/null' >> $testroot/stdout.expected
+	echo '--- beta' >> $testroot/stdout.expected
+	echo '+++ /dev/null' >> $testroot/stdout.expected
+	echo '@@ -1 +0,0 @@' >> $testroot/stdout.expected
+	echo '-beta' >> $testroot/stdout.expected
+
+	(cd $testroot/wt && got diff new alpha epsilon beta > $testroot/stdout)
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	# a branch 'new' should not collide with path 'new' if more
+	# than two arguments are passed
+	got br -r $testroot/repo -c master new > /dev/null
+	(cd $testroot/wt && got diff new alpha epsilon beta \
+		> $testroot/stdout 2> $testroot/stderr)
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		echo "diff failed unexpectedly" >&2
+		test_done "$testroot" "1"
+		return 1
+	fi
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	# different order of arguments results in different output order
+	echo "diff $head_rev $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
+	echo 'file + alpha' >> $testroot/stdout.expected
+	echo '--- alpha' >> $testroot/stdout.expected
+	echo '+++ alpha' >> $testroot/stdout.expected
+	echo '@@ -1 +1 @@' >> $testroot/stdout.expected
+	echo '-alpha' >> $testroot/stdout.expected
+	echo '+modified alpha' >> $testroot/stdout.expected
+	echo 'blob - /dev/null' >> $testroot/stdout.expected
+	echo 'file + new' >> $testroot/stdout.expected
+	echo '--- /dev/null' >> $testroot/stdout.expected
+	echo '+++ new' >> $testroot/stdout.expected
+	echo '@@ -0,0 +1 @@' >> $testroot/stdout.expected
+	echo '+new file' >> $testroot/stdout.expected
+	echo -n 'blob - ' >> $testroot/stdout.expected
+	got tree -r $testroot/repo -i epsilon | grep 'zeta$' | cut -d' ' -f 1 \
+		>> $testroot/stdout.expected
+	echo 'file + epsilon/zeta' >> $testroot/stdout.expected
+	echo '--- epsilon/zeta' >> $testroot/stdout.expected
+	echo '+++ epsilon/zeta' >> $testroot/stdout.expected
+	echo '@@ -1 +1 @@' >> $testroot/stdout.expected
+	echo '-zeta' >> $testroot/stdout.expected
+	echo '+modified zeta' >> $testroot/stdout.expected
+	echo -n 'blob - ' >> $testroot/stdout.expected
+	got tree -r $testroot/repo -i | grep 'beta$' | cut -d' ' -f 1 \
+		>> $testroot/stdout.expected
+	echo 'file + /dev/null' >> $testroot/stdout.expected
+	echo '--- beta' >> $testroot/stdout.expected
+	echo '+++ /dev/null' >> $testroot/stdout.expected
+	echo '@@ -1 +0,0 @@' >> $testroot/stdout.expected
+	echo '-beta' >> $testroot/stdout.expected
+	(cd $testroot/wt && got diff alpha new epsilon beta \
+		> $testroot/stdout 2> $testroot/stderr)
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		echo "diff failed unexpectedly" >&2
+		test_done "$testroot" "1"
+		return 1
+	fi
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	# Two arguments are interpreted as objects if a colliding path exists
+	echo master > $testroot/wt/master
+	(cd $testroot/wt && got add master > /dev/null)
+	(cd $testroot/wt && got diff master new > $testroot/stdout)
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		echo "diff failed unexpectedly" >&2
+		test_done "$testroot" "1"
+		return 1
+	fi
+	echo "diff refs/heads/master refs/heads/new" > $testroot/stdout.expected
+	# diff between the branches is empty
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+	# same without a work tree
+	(cd $testroot/repo && got diff master new > $testroot/stdout)
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		echo "diff failed unexpectedly" >&2
+		test_done "$testroot" "1"
+		return 1
+	fi
+	echo "diff refs/heads/master refs/heads/new" > $testroot/stdout.expected
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+	# same with -r argument
+	got diff -r $testroot/repo master new > $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		echo "diff failed unexpectedly" >&2
+		test_done "$testroot" "1"
+		return 1
+	fi
+	echo "diff refs/heads/master refs/heads/new" > $testroot/stdout.expected
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	# -P can be used to force use of paths
+	(cd $testroot/wt && got diff -P new master > $testroot/stdout)
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		echo "diff failed unexpectedly" >&2
+		test_done "$testroot" "1"
+		return 1
+	fi
+	echo "diff $head_rev $testroot/wt" > $testroot/stdout.expected
+	echo 'blob - /dev/null' >> $testroot/stdout.expected
+	echo 'file + new' >> $testroot/stdout.expected
+	echo '--- /dev/null' >> $testroot/stdout.expected
+	echo '+++ new' >> $testroot/stdout.expected
+	echo '@@ -0,0 +1 @@' >> $testroot/stdout.expected
+	echo '+new file' >> $testroot/stdout.expected
+	echo 'blob - /dev/null' >> $testroot/stdout.expected
+	echo 'file + master' >> $testroot/stdout.expected
+	echo '--- /dev/null' >> $testroot/stdout.expected
+	echo '+++ master' >> $testroot/stdout.expected
+	echo '@@ -0,0 +1 @@' >> $testroot/stdout.expected
+	echo '+master' >> $testroot/stdout.expected
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	# -P can only be used in a work tree
+	got diff -r $testroot/repo -P new master 2> $testroot/stderr
+	ret="$?"
+	if [ "$ret" == "0" ]; then
+		echo "diff succeeded unexpectedly" >&2
+		test_done "$testroot" "1"
+		return 1
+	fi
+	echo "got: -P option can only be used when diffing a work tree" \
 		> $testroot/stderr.expected
 	cmp -s $testroot/stderr.expected $testroot/stderr
 	ret="$?"
 	if [ "$ret" != "0" ]; then
 		diff -u $testroot/stderr.expected $testroot/stderr
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	# a single argument which can be resolved to a path is not ambiguous
+	echo "diff $head_rev $testroot/wt" > $testroot/stdout.expected
+	echo 'blob - /dev/null' >> $testroot/stdout.expected
+	echo 'file + new' >> $testroot/stdout.expected
+	echo '--- /dev/null' >> $testroot/stdout.expected
+	echo '+++ new' >> $testroot/stdout.expected
+	echo '@@ -0,0 +1 @@' >> $testroot/stdout.expected
+	echo '+new file' >> $testroot/stdout.expected
+	(cd $testroot/wt && got diff new > $testroot/stdout)
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		echo "diff failed unexpectedly" >&2
+		test_done "$testroot" "1"
+		return 1
+	fi
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	# diff with just one object ID argument results in
+	# interpretation of argument as a path
+	(cd $testroot/wt && got diff $head_rev 2> $testroot/stderr)
+	ret="$?"
+	if [ "$ret" = "0" ]; then
+		echo "diff succeeded unexpectedly" >&2
+		test_done "$testroot" "1"
+		return 1
+	fi
+	echo "got: $head_rev: No such file or directory" \
+		> $testroot/stderr.expected
+	cmp -s $testroot/stderr.expected $testroot/stderr
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stderr.expected $testroot/stderr
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	# diff with more than two object arguments results in
+	# interpretation of arguments as paths
+	(cd $testroot/wt && got diff new $head_rev master \
+		> $testroot/stout 2> $testroot/stderr)
+	ret="$?"
+	if [ "$ret" = "0" ]; then
+		echo "diff succeeded unexpectedly" >&2
+		test_done "$testroot" "1"
+		return 1
+	fi
+
+	echo "diff $head_rev $testroot/wt" > $testroot/stdout.expected
+	echo 'blob - /dev/null' >> $testroot/stdout.expected
+	echo 'file + new' >> $testroot/stdout.expected
+	echo '--- /dev/null' >> $testroot/stdout.expected
+	echo '+++ new' >> $testroot/stdout.expected
+	echo '@@ -0,0 +1 @@' >> $testroot/stdout.expected
+	echo '+new file' >> $testroot/stdout.expected
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	echo "got: $head_rev: No such file or directory" \
+		> $testroot/stderr.expected
+	cmp -s $testroot/stderr.expected $testroot/stderr
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stderr.expected $testroot/stderr
+		return 1
 	fi
 	test_done "$testroot" "$ret"
 }