Commit 72ea6654e747de4879e06bd45e50ea7d9f5b7ed6

Stefan Sperling 2019-07-27T11:21:56

add support for multiple path arguments to 'got status'

diff --git a/got/got.1 b/got/got.1
index cb00a11..d3e564d 100644
--- a/got/got.1
+++ b/got/got.1
@@ -212,7 +212,7 @@ branch will be used.
 .It Cm up
 Short alias for
 .Cm update .
-.It Cm status [ Ar path ]
+.It Cm status [ Ar path ... ]
 Show the current modification status of files in a work tree,
 using the following status codes:
 .Bl -column YXZ description
@@ -226,9 +226,10 @@ using the following status codes:
 .Nm
 .El
 .Pp
-If a
+If no
 .Ar path
-is specified, only show modifications within this path.
+is specified, show modifications in the entire work tree.
+Otherwise, show modifications at or within the specified paths.
 .It Cm st
 Short alias for
 .Cm status .
diff --git a/got/got.c b/got/got.c
index 3e915e8..90f2c55 100644
--- a/got/got.c
+++ b/got/got.c
@@ -1802,7 +1802,11 @@ cmd_diff(int argc, char *argv[])
 
 	if (argc <= 1) {
 		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)
@@ -1813,9 +1817,14 @@ cmd_diff(int argc, char *argv[])
 		arg.id_str = id_str;
 		arg.header_shown = 0;
 
-		error = got_worktree_status(worktree, path, repo, print_diff,
+		error = got_pathlist_append(NULL, &paths, path, NULL);
+		if (error)
+			goto done;
+
+		error = got_worktree_status(worktree, &paths, repo, print_diff,
 		    &arg, check_cancelled, NULL);
 		free(id_str);
+		got_pathlist_free(&paths);
 		goto done;
 	}
 
@@ -2291,7 +2300,7 @@ done:
 __dead static void
 usage_status(void)
 {
-	fprintf(stderr, "usage: %s status [path]\n", getprogname());
+	fprintf(stderr, "usage: %s status [path ...]\n", getprogname());
 	exit(1);
 }
 
@@ -2310,7 +2319,10 @@ cmd_status(int argc, char *argv[])
 	struct got_repository *repo = NULL;
 	struct got_worktree *worktree = NULL;
 	char *cwd = NULL, *path = NULL;
-	int ch;
+	struct got_pathlist_head paths;
+	int ch, i;
+
+	TAILQ_INIT(&paths);
 
 	while ((ch = getopt(argc, argv, "")) != -1) {
 		switch (ch) {
@@ -2339,15 +2351,21 @@ cmd_status(int argc, char *argv[])
 		goto done;
 
 	if (argc == 0) {
-		path = strdup("");
-		if (path == NULL) {
-			error = got_error_from_errno("strdup");
-			goto done;
-		}
-	} else if (argc == 1) {
-		error = got_worktree_resolve_path(&path, worktree, argv[0]);
+		error = got_pathlist_append(NULL, &paths, "", NULL);
 		if (error)
 			goto done;
+	} else if (argc >= 1) {
+		for (i = 0; i < argc; i++) {
+			error = got_worktree_resolve_path(&path, worktree,
+			    argv[i]);
+			if (error)
+				goto done;
+			error = got_pathlist_append(NULL, &paths, path, NULL);
+			if (error) {
+				free(path);
+				goto done;
+			}
+		}
 	} else
 		usage_status();
 
@@ -2360,9 +2378,10 @@ cmd_status(int argc, char *argv[])
 	if (error)
 		goto done;
 
-	error = got_worktree_status(worktree, path, repo, print_status, NULL,
+	error = got_worktree_status(worktree, &paths, repo, print_status, NULL,
 	    check_cancelled, NULL);
 done:
+	got_pathlist_free(&paths);
 	free(cwd);
 	free(path);
 	return error;
diff --git a/include/got_path.h b/include/got_path.h
index 588e227..c5070b4 100644
--- a/include/got_path.h
+++ b/include/got_path.h
@@ -78,6 +78,17 @@ TAILQ_HEAD(got_pathlist_head, got_pathlist_entry);
 const struct got_error *got_pathlist_insert(struct got_pathlist_entry **,
     struct got_pathlist_head *, const char *, void *);
 
+/*
+ * Append a path to the list of paths.
+ * The caller should already have initialized the list head. This list stores
+ * the pointer to the path as-is, i.e. the path is not copied internally and
+ * must remain available until the list is freed with got_pathlist_free().
+ * If the first argument is not NULL, set it to a pointer to the newly inserted
+ * element, or to a NULL pointer in case the path was already on the list.
+ */
+const struct got_error *got_pathlist_append(struct got_pathlist_entry **,
+    struct got_pathlist_head *, const char *, void *);
+
 /* Free resources allocated for a path list. */
 void got_pathlist_free(struct got_pathlist_head *);
 
diff --git a/include/got_worktree.h b/include/got_worktree.h
index e9f5a6c..e98f4ee 100644
--- a/include/got_worktree.h
+++ b/include/got_worktree.h
@@ -148,8 +148,8 @@ typedef const struct got_error *(*got_worktree_status_cb)(void *,
  * a path, and a corresponding status code.
  */
 const struct got_error *got_worktree_status(struct got_worktree *,
-    const char *, struct got_repository *, got_worktree_status_cb, void *,
-    got_worktree_cancel_cb cancel_cb, void *);
+    struct got_pathlist_head *, struct got_repository *,
+    got_worktree_status_cb, void *, got_worktree_cancel_cb cancel_cb, void *);
 
 /*
  * Try to resolve a user-provided path to an on-disk path in the work tree.
diff --git a/lib/path.c b/lib/path.c
index 8d2ef33..f3e258f 100644
--- a/lib/path.c
+++ b/lib/path.c
@@ -253,6 +253,23 @@ got_pathlist_insert(struct got_pathlist_entry **inserted,
 	return NULL;
 }
 
+const struct got_error *
+got_pathlist_append(struct got_pathlist_entry **pe,
+    struct got_pathlist_head *pathlist, const char *path, void *data)
+{
+	struct got_pathlist_entry *new;
+
+	new = malloc(sizeof(*new));
+	if (new == NULL)
+		return got_error_from_errno("malloc");
+	new->path = path;
+	new->data = data;
+	TAILQ_INSERT_TAIL(pathlist, new, entry);
+	if (pe)
+		*pe = new;
+	return NULL;
+}
+
 void
 got_pathlist_free(struct got_pathlist_head *pathlist)
 {
diff --git a/lib/worktree.c b/lib/worktree.c
index c15d384..4101d79 100644
--- a/lib/worktree.c
+++ b/lib/worktree.c
@@ -2265,10 +2265,14 @@ worktree_status(struct got_worktree *worktree, const char *path,
 		if (errno == ENOTDIR || errno == ENOENT) {
 			struct got_fileindex_entry *ie;
 			ie = got_fileindex_entry_get(fileindex, path);
-			if (ie == NULL) {
-				err = got_error(GOT_ERR_BAD_PATH);
+			if (ie == NULL)
+				err = (*status_cb)(status_arg,
+				    GOT_STATUS_UNVERSIONED, path, NULL, NULL);
+			else
+				err = report_file_status(ie, ondisk_path,
+				    status_cb, status_arg, repo);
+			if (err)
 				goto done;
-			}
 			err = report_file_status(ie, ondisk_path,
 			    status_cb, status_arg, repo);
 			goto done;
@@ -2299,20 +2303,26 @@ done:
 }
 
 const struct got_error *
-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)
+got_worktree_status(struct got_worktree *worktree,
+    struct got_pathlist_head *paths, struct got_repository *repo,
+    got_worktree_status_cb status_cb, void *status_arg,
+    got_worktree_cancel_cb cancel_cb, void *cancel_arg)
 {
 	const struct got_error *err = NULL;
 	char *fileindex_path = NULL;
 	struct got_fileindex *fileindex = NULL;
+	struct got_pathlist_entry *pe;
 
 	err = open_fileindex(&fileindex, &fileindex_path, worktree);
 	if (err)
 		return err;
 
-	err = worktree_status(worktree, path, fileindex, repo,
-	    status_cb, status_arg, cancel_cb, cancel_arg);
+	TAILQ_FOREACH(pe, paths, entry) {
+		err = worktree_status(worktree, pe->path, fileindex, repo,
+			status_cb, status_arg, cancel_cb, cancel_arg);
+		if (err)
+			break;
+	}
 	free(fileindex_path);
 	got_fileindex_free(fileindex);
 	return err;
diff --git a/regress/cmdline/status.sh b/regress/cmdline/status.sh
index caf49ba..b77e1d3 100755
--- a/regress/cmdline/status.sh
+++ b/regress/cmdline/status.sh
@@ -442,6 +442,44 @@ function test_status_empty_dir_unversioned_file {
 	test_done "$testroot" "$ret"
 }
 
+function test_status_many_paths {
+	local testroot=`test_init status_many_paths`
+
+	got checkout $testroot/repo $testroot/wt > /dev/null
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	echo "modified alpha" > $testroot/wt/alpha
+	(cd $testroot/wt && got rm beta >/dev/null)
+	echo "unversioned file" > $testroot/wt/foo
+	rm $testroot/wt/epsilon/zeta
+	touch $testroot/wt/beta
+	echo "new file" > $testroot/wt/new
+	mkdir $testroot/wt/newdir
+	(cd $testroot/wt && got add new >/dev/null)
+
+	(cd $testroot/wt && got status newdir > $testroot/stdout.expected)
+	(cd $testroot/wt && got status alpha >> $testroot/stdout.expected)
+	(cd $testroot/wt && got status epsilon >> $testroot/stdout.expected)
+	(cd $testroot/wt && got status foo >> $testroot/stdout.expected)
+	(cd $testroot/wt && got status new >> $testroot/stdout.expected)
+	(cd $testroot/wt && got status beta >> $testroot/stdout.expected)
+	(cd $testroot/wt && got status . >> $testroot/stdout.expected)
+
+	(cd $testroot/wt && got status newdir alpha epsilon foo new beta . \
+		> $testroot/stdout)
+
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+	fi
+	test_done "$testroot" "$ret"
+}
+
 run_test test_status_basic
 run_test test_status_subdir_no_mods
 run_test test_status_subdir_no_mods2
@@ -453,3 +491,4 @@ run_test test_status_shows_no_mods_after_complete_merge
 run_test test_status_shows_conflict
 run_test test_status_empty_dir
 run_test test_status_empty_dir_unversioned_file
+run_test test_status_many_paths