Commit Diff


commit - 13ff9e901c193017bf37229e261588b7bdbfe446
commit + 927df6b74a3358d1eaa0f2482cac2204a4f8f9fa
blob - 8a6407f100352d662d8260e1d3fcb69ddadc5a8f
blob + 5d11ec962e2aa555e428742810acdc50377403a6
--- got/got.1
+++ got/got.1
@@ -122,7 +122,7 @@ Update the work tree to the specified
 .Ar commit .
 The expected argument is a SHA1 hash which corresponds to a commit object.
 .El
-.It Cm status [ Ar worktree-path ]
+.It Cm status [ Ar path ]
 Show the current modification status of files in a worktree,
 using the following status codes:
 .Bl -column YXZ description
@@ -133,9 +133,9 @@ using the following status codes:
 .Nm
 .El
 .Pp
-If the
-.Ar work tree path
-is omitted, use the current working directory.
+If a
+.Ar path
+is specified, only show modifications within this path.
 .It Cm log [ Fl c Ar commit ] [ Fl C Ar number ] [ Fl f ] [ Fl l Ar N ] [ Fl p ] [ Fl r Ar repository-path ] [ path ]
 Display history of a repository.
 If a
@@ -172,12 +172,16 @@ If this directory is a
 .Nm
 work tree, use the repository path associated with this work tree.
 .El
-.It Cm diff [ Fl C Ar number ] [ Fl r Ar repository-path ] [ Ar object1 Ar object2 ]
-Display differences between blobs in the repository and files in the
-work tree, or between two specified objects in a repository.
-If specified, each
-.Ar object
-argument is a SHA1 hash which corresponds to the object.
+.It Cm diff [ Fl C Ar number ] [ Fl r Ar repository-path ] [ Ar object1 Ar object2 | Ar path ]
+When invoked within a work tree with less than two arguments, display
+uncommitted changes in the work tree.
+If a
+.Ar path
+is specified, only show changes within this path.
+.Pp
+If two arguments are provided, treat each argument as a SHA1 hash which
+corresponds to an object in the repository, and display differences
+between these objects.
 Both objects must be of the same type (blobs, trees, or commits).
 .Pp
 The options for
blob - 38251537f753faed2f94818ddb4bd42c5780eb6d
blob + ca784f2d06a5b67919ef72127efe1ce8215efb01
--- got/got.c
+++ got/got.c
@@ -942,7 +942,7 @@ __dead static void
 usage_diff(void)
 {
 	fprintf(stderr, "usage: %s diff [-C number] [-r repository-path] "
-	    "[object1 object2]\n", getprogname());
+	    "[object1 object2 | path]\n", getprogname());
 	exit(1);
 }
 
@@ -1002,6 +1002,47 @@ done:
 	if (f2)
 		fclose(f2);
 	free(abspath);
+	return err;
+}
+
+static const struct got_error *
+get_status_path(char **status_path, struct got_worktree *worktree,
+    const char *arg)
+{
+	const struct got_error *err = NULL;
+	char *resolved, *path = NULL;
+	size_t len;
+
+	*status_path = NULL;
+
+	resolved = realpath(arg, NULL);
+	if (resolved == NULL)
+		return got_error_from_errno();
+
+	if (strncmp(got_worktree_get_root_path(worktree), resolved,
+	    strlen(got_worktree_get_root_path(worktree)))) {
+		err = got_error(GOT_ERR_BAD_PATH);
+		goto done;
+	}
+
+	path = strdup(resolved + strlen(got_worktree_get_root_path(worktree)));
+	if (path == NULL) {
+		err = got_error_from_errno();
+		goto done;
+	}
+
+	/* XXX status walk can't deal with trailing slash! */
+	len = strlen(path);
+	while (path[len - 1] == '/') {
+		path[len - 1] = '\0';
+		len--;
+	}
+done:
+	free(resolved);
+	if (err == NULL)
+		*status_path = path;
+	else
+		free(path);
 	return err;
 }
 
@@ -1017,6 +1058,7 @@ cmd_diff(int argc, char *argv[])
 	int type1, type2;
 	int diff_context = 3, ch;
 	const char *errstr;
+	char *path = NULL;
 
 #ifndef PROFILE
 	if (pledge("stdio rpath wpath cpath flock proc exec sendfd unveil",
@@ -1050,16 +1092,33 @@ cmd_diff(int argc, char *argv[])
 		error = got_error_from_errno();
 		goto done;
 	}
-	if (argc == 0) {
+	error = got_worktree_open(&worktree, cwd);
+	if (error && error->code != GOT_ERR_NOT_WORKTREE)
+		goto done;
+	if (argc <= 1) {
+		if (worktree == NULL) {
+			error = got_error(GOT_ERR_NOT_WORKTREE);
+			goto done;
+		}
 		if (repo_path)
 			errx(1,
 			    "-r option can't be used when diffing a work tree");
-		error = got_worktree_open(&worktree, cwd);
-		if (error)
-			goto done;
 		repo_path = strdup(got_worktree_get_repo_path(worktree));
-		if (repo_path == NULL)
-			return got_error_from_errno();
+		if (repo_path == NULL) {
+			error = got_error_from_errno();
+			goto done;
+		}
+		if (argc == 1) {
+			error = get_status_path(&path, worktree, argv[0]);
+			if (error)
+				goto done;
+		} else {
+			path = strdup("");
+			if (path == NULL) {
+				error = got_error_from_errno();
+				goto done;
+			}
+		}
 	} else if (argc == 2) {
 		id_str1 = argv[0];
 		id_str2 = argv[1];
@@ -1095,7 +1154,7 @@ cmd_diff(int argc, char *argv[])
 		arg.id_str = id_str;
 		arg.header_shown = 0;
 
-		error = got_worktree_status(worktree, repo, print_diff,
+		error = got_worktree_status(worktree, path, repo, print_diff,
 		    &arg, check_cancelled, NULL);
 		free(id_str);
 		goto done;
@@ -1144,6 +1203,7 @@ cmd_diff(int argc, char *argv[])
 done:
 	free(id1);
 	free(id2);
+	free(path);
 	if (worktree)
 		got_worktree_close(worktree);
 	if (repo) {
@@ -1527,7 +1587,7 @@ done:
 __dead static void
 usage_status(void)
 {
-	fprintf(stderr, "usage: %s status [worktree-path]\n", getprogname());
+	fprintf(stderr, "usage: %s status [path]\n", getprogname());
 	exit(1);
 }
 
@@ -1545,7 +1605,7 @@ cmd_status(int argc, char *argv[])
 	const struct got_error *error = NULL;
 	struct got_repository *repo = NULL;
 	struct got_worktree *worktree = NULL;
-	char *worktree_path = NULL;
+	char *cwd = NULL, *path = NULL;
 	int ch;
 
 	while ((ch = getopt(argc, argv, "")) != -1) {
@@ -1564,25 +1624,29 @@ cmd_status(int argc, char *argv[])
 	    NULL) == -1)
 		err(1, "pledge");
 #endif
+	cwd = getcwd(NULL, 0);
+	if (cwd == NULL) {
+		error = got_error_from_errno();
+		goto done;
+	}
+
+	error = got_worktree_open(&worktree, cwd);
+	if (error != NULL)
+		goto done;
+
 	if (argc == 0) {
-		worktree_path = getcwd(NULL, 0);
-		if (worktree_path == NULL) {
+		path = strdup("");
+		if (path == NULL) {
 			error = got_error_from_errno();
 			goto done;
 		}
 	} else if (argc == 1) {
-		worktree_path = realpath(argv[0], NULL);
-		if (worktree_path == NULL) {
-			error = got_error_from_errno();
+		error = get_status_path(&path, worktree, argv[0]);
+		if (error)
 			goto done;
-		}
 	} else
 		usage_status();
 
-	error = got_worktree_open(&worktree, worktree_path);
-	if (error != NULL)
-		goto done;
-
 	error = got_repo_open(&repo, got_worktree_get_repo_path(worktree));
 	if (error != NULL)
 		goto done;
@@ -1592,9 +1656,10 @@ cmd_status(int argc, char *argv[])
 	if (error)
 		goto done;
 
-	error = got_worktree_status(worktree, repo, print_status, NULL,
+	error = got_worktree_status(worktree, path, repo, print_status, NULL,
 	    check_cancelled, NULL);
 done:
-	free(worktree_path);
+	free(cwd);
+	free(path);
 	return error;
 }
blob - c28e21835670de5ae86292d65d24e0bf7139fada
blob + 3961b6d7873af2a2c046fb30c1497f452d1136f5
--- include/got_worktree.h
+++ include/got_worktree.h
@@ -122,5 +122,6 @@ typedef const struct got_error *(*got_worktree_status_
  * a path, and a corresponding status code.
  */
 const struct got_error *
-got_worktree_status(struct got_worktree *, struct got_repository *,
-    got_worktree_status_cb, void *, got_worktree_cancel_cb cancel_cb, void *);
+got_worktree_status(struct got_worktree *, const char *,
+    struct got_repository *, got_worktree_status_cb, void *,
+    got_worktree_cancel_cb cancel_cb, void *);
blob - 5d501fc0867dd9283c8d954134123191b1548362
blob + 9513f3056f3375a16cb9ccca9ebf8a3dfe294dff
--- lib/fileindex.c
+++ lib/fileindex.c
@@ -844,12 +844,12 @@ done:
 
 const struct got_error *
 got_fileindex_diff_dir(struct got_fileindex *fileindex, DIR *rootdir,
-    const char *rootpath, struct got_repository *repo,
+    const char *rootpath, const char *path, struct got_repository *repo,
     struct got_fileindex_diff_dir_cb *cb, void *cb_arg)
 {
 	struct got_fileindex_entry *min;
 	min = RB_MIN(got_fileindex_tree, &fileindex->entries);
-	return diff_fileindex_dir(fileindex, &min, rootdir, rootpath, "",
+	return diff_fileindex_dir(fileindex, &min, rootdir, rootpath, path,
 	    repo, cb, cb_arg);
 }
 
blob - bb4a77afda033364d37dcdc5dd281795429bad65
blob + aa91c6643eb06a9a52b12801b94593ca38102c5b
--- lib/got_lib_fileindex.h
+++ lib/got_lib_fileindex.h
@@ -141,5 +141,5 @@ struct got_fileindex_diff_dir_cb {
 	got_fileindex_diff_dir_new_cb diff_new;
 };
 const struct got_error *got_fileindex_diff_dir(struct got_fileindex *, DIR *,
-    const char *, struct got_repository *, struct got_fileindex_diff_dir_cb *,
-    void *);
+    const char *, const char *, struct got_repository *,
+    struct got_fileindex_diff_dir_cb *, void *);
blob - 9a55298bbc84d3449a19d3cb40e0b87307690de7
blob + 949678e5c0659175b5f4de710c0d32db3121fbc7
--- lib/worktree.c
+++ lib/worktree.c
@@ -1155,6 +1155,8 @@ done:
 struct diff_dir_cb_arg {
     struct got_fileindex *fileindex;
     struct got_worktree *worktree;
+    const char *status_path;
+    size_t status_path_len;
     struct got_repository *repo;
     got_worktree_status_cb status_cb;
     void *status_arg;
@@ -1163,15 +1165,35 @@ struct diff_dir_cb_arg {
 };
 
 static const struct got_error *
+report_file_status(struct got_fileindex_entry *ie, const char *abspath,
+    got_worktree_status_cb status_cb, void *status_arg,
+    struct got_repository *repo)
+{
+	const struct got_error *err = NULL;
+	unsigned char status = GOT_STATUS_NO_CHANGE;
+	struct stat sb;
+	struct got_object_id id;
+
+	err = get_file_status(&status, &sb, ie, abspath, repo);
+	if (err == NULL && status != GOT_STATUS_NO_CHANGE) {
+		memcpy(id.sha1, ie->blob_sha1, SHA1_DIGEST_LENGTH);
+		err = (*status_cb)(status_arg, status, ie->path, &id);
+	}
+	return err;
+}
+
+static const struct got_error *
 status_old_new(void *arg, struct got_fileindex_entry *ie,
     struct dirent *de, const char *parent_path)
 {
 	const struct got_error *err = NULL;
 	struct diff_dir_cb_arg *a = arg;
 	char *abspath;
-	unsigned char status = GOT_STATUS_NO_CHANGE;
-	struct stat sb;
 
+	if (got_path_cmp(parent_path, a->status_path) != 0 &&
+	    !got_path_is_child(parent_path, a->status_path, a->status_path_len))
+		return NULL;
+
 	if (parent_path[0]) {
 		if (asprintf(&abspath, "%s/%s/%s", a->worktree->root_path,
 		    parent_path, de->d_name) == -1)
@@ -1182,12 +1204,8 @@ status_old_new(void *arg, struct got_fileindex_entry *
 			return got_error_from_errno();
 	}
 
-	err = get_file_status(&status, &sb, ie, abspath, a->repo);
-	if (err == NULL && status != GOT_STATUS_NO_CHANGE) {
-		struct got_object_id id;
-		memcpy(id.sha1, ie->blob_sha1, SHA1_DIGEST_LENGTH);
-		err = (*a->status_cb)(a->status_arg, status, ie->path, &id);
-	}
+	err = report_file_status(ie, abspath, a->status_cb, a->status_arg,
+	    a->repo);
 	free(abspath);
 	return err;
 }
@@ -1197,6 +1215,10 @@ status_old(void *arg, struct got_fileindex_entry *ie, 
 {
 	struct diff_dir_cb_arg *a = arg;
 	struct got_object_id id;
+
+	if (!got_path_is_child(parent_path, a->status_path, a->status_path_len))
+		return NULL;
+
 	memcpy(id.sha1, ie->blob_sha1, SHA1_DIGEST_LENGTH);
 	return (*a->status_cb)(a->status_arg, GOT_STATUS_MISSING, ie->path,
 	    &id);
@@ -1216,6 +1238,9 @@ status_new(void *arg, struct dirent *de, const char *p
 	if (de->d_type == DT_LNK)
 		return NULL;
 
+	if (!got_path_is_child(parent_path, a->status_path, a->status_path_len))
+		return NULL;
+
 	if (parent_path[0]) {
 		if (asprintf(&path, "%s/%s", parent_path, de->d_name) == -1)
 			return got_error_from_errno();
@@ -1231,7 +1256,7 @@ status_new(void *arg, struct dirent *de, const char *p
 }
 
 const struct got_error *
-got_worktree_status(struct got_worktree *worktree,
+got_worktree_status(struct got_worktree *worktree, const char *path,
     struct got_repository *repo, got_worktree_status_cb status_cb,
     void *status_arg, got_worktree_cancel_cb cancel_cb, void *cancel_arg)
 {
@@ -1242,6 +1267,7 @@ got_worktree_status(struct got_worktree *worktree,
 	FILE *index = NULL;
 	struct got_fileindex_diff_dir_cb fdiff_cb;
 	struct diff_dir_cb_arg arg;
+	char *ondisk_path = NULL;
 
 	fileindex = got_fileindex_alloc();
 	if (fileindex == NULL) {
@@ -1269,26 +1295,46 @@ got_worktree_status(struct got_worktree *worktree,
 			goto done;
 	}
 
-	workdir = opendir(worktree->root_path);
-	if (workdir == NULL) {
+	if (asprintf(&ondisk_path, "%s%s%s",
+	    worktree->root_path, path[0] ? "/" : "", path) == -1) {
 		err = got_error_from_errno();
 		goto done;
 	}
+	workdir = opendir(ondisk_path);
+	if (workdir == NULL) {
+		if (errno == ENOTDIR) {
+			struct got_fileindex_entry *ie;
+			ie = got_fileindex_entry_get(fileindex, path);
+			if (ie == NULL) {
+				err = got_error(GOT_ERR_BAD_PATH);
+				goto done;
+			}
+			err = report_file_status(ie, ondisk_path,
+			    status_cb, status_arg, repo);
+			goto done;
+		} else {
+			err = got_error_from_errno();
+			goto done;
+		}
+	}
 	fdiff_cb.diff_old_new = status_old_new;
 	fdiff_cb.diff_old = status_old;
 	fdiff_cb.diff_new = status_new;
 	arg.fileindex = fileindex;
 	arg.worktree = worktree;
+	arg.status_path = path;
+	arg.status_path_len = strlen(path);
 	arg.repo = repo;
 	arg.status_cb = status_cb;
 	arg.status_arg = status_arg;
 	arg.cancel_cb = cancel_cb;
 	arg.cancel_arg = cancel_arg;
 	err = got_fileindex_diff_dir(fileindex, workdir, worktree->root_path,
-	    repo, &fdiff_cb, &arg);
+	    path, repo, &fdiff_cb, &arg);
 done:
 	if (workdir)
 		closedir(workdir);
+	free(ondisk_path);
 	free(fileindex_path);
 	got_fileindex_free(fileindex);
 	return err;