Commit Diff


commit - 54817d72bf743bcd8d0313b5af1bd9899bbda5e9
commit + f2ea84fab96c77d352fe460a37f2722beb6225d7
blob - 50fbe74615a591d44b3910961c12101fd22bb7c3
blob + 5d1c026749d5a0bc5909270c81cfd0bb9b78a61f
--- got/got.1
+++ got/got.1
@@ -158,7 +158,7 @@ will be checked out.
 .It Cm co
 Short alias for
 .Cm checkout .
-.It Cm update [ Fl b Ar branch ] [ Fl c Ar commit ] [ Ar path ]
+.It Cm update [ Fl b Ar branch ] [ Fl c Ar commit ] [ Ar path ... ]
 Update an existing work tree to a different commit.
 Show the status of each affected file, using the following status codes:
 .Bl -column YXZ description
@@ -171,12 +171,14 @@ Show the status of each affected file, using the follo
 .It ! Ta a missing versioned file was restored
 .El
 .Pp
-If a
+If no
 .Ar path
-is specified, restrict the update operation to files at or within this path.
-The path is required to exist in the update operation's target commit.
-Files in the work tree outside this path will remain unchanged and will
-retain their previously recorded base commit.
+is specified, update the entire work tree.
+Otherwise, restrict the update operation to files at or within the
+specified paths.
+Each path is required to exist in the update operation's target commit.
+Files in the work tree outside specified paths will remain unchanged and
+will retain their previously recorded base commit.
 Some
 .Nm
 commands may refuse to run while the work tree contains files from
blob - 85b26414edc56d7edf0c862b9e09a154770b418d
blob + 0d710deb3ccd4d641958db6c06a8983ca97b8c24
--- got/got.c
+++ got/got.c
@@ -791,6 +791,9 @@ cmd_checkout(int argc, char *argv[])
 	const char *branch_name = GOT_REF_HEAD;
 	char *commit_id_str = NULL;
 	int ch, same_path_prefix;
+	struct got_pathlist_head paths;
+
+	TAILQ_INIT(&paths);
 
 	while ((ch = getopt(argc, argv, "b:c:p:")) != -1) {
 		switch (ch) {
@@ -940,7 +943,10 @@ cmd_checkout(int argc, char *argv[])
 			goto done;
 	}
 
-	error = got_worktree_checkout_files(worktree, "", repo,
+	error = got_pathlist_append(NULL, &paths, "", NULL);
+	if (error)
+		goto done;
+	error = got_worktree_checkout_files(worktree, &paths, repo,
 	    checkout_progress, worktree_path, check_cancelled, NULL);
 	if (error != NULL)
 		goto done;
@@ -948,6 +954,7 @@ cmd_checkout(int argc, char *argv[])
 	printf("Now shut up and hack\n");
 
 done:
+	got_pathlist_free(&paths);
 	free(commit_id_str);
 	free(repo_path);
 	free(worktree_path);
@@ -957,7 +964,7 @@ done:
 __dead static void
 usage_update(void)
 {
-	fprintf(stderr, "usage: %s update [-b branch] [-c commit] [path]\n",
+	fprintf(stderr, "usage: %s update [-b branch] [-c commit] [path ...]\n",
 	    getprogname());
 	exit(1);
 }
@@ -1078,13 +1085,17 @@ cmd_update(int argc, char *argv[])
 	const struct got_error *error = NULL;
 	struct got_repository *repo = NULL;
 	struct got_worktree *worktree = NULL;
-	char *worktree_path = NULL, *path = NULL;
+	char *worktree_path = NULL;
 	struct got_object_id *commit_id = NULL;
 	char *commit_id_str = NULL;
 	const char *branch_name = NULL;
 	struct got_reference *head_ref = NULL;
+	struct got_pathlist_head paths;
+	struct got_pathlist_entry *pe;
 	int ch, did_something = 0;
 
+	TAILQ_INIT(&paths);
+
 	while ((ch = getopt(argc, argv, "b:c:")) != -1) {
 		switch (ch) {
 		case 'b':
@@ -1122,18 +1133,9 @@ cmd_update(int argc, char *argv[])
 	if (error)
 		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]);
-		if (error)
-			goto done;
-	} else
-		usage_update();
+	error = get_worktree_paths_from_argv(&paths, argc, argv, worktree);
+	if (error)
+		goto done;
 
 	error = got_repo_open(&repo, got_worktree_get_repo_path(worktree));
 	if (error != NULL)
@@ -1168,12 +1170,12 @@ cmd_update(int argc, char *argv[])
 
 	if (branch_name) {
 		struct got_object_id *head_commit_id;
-		if (strlen(path) != 0) {
-			fprintf(stderr, "%s: switching between branches "
-			    "requires that the entire work tree "
-			    "gets updated, not just '%s'\n",
-			    getprogname(), path);
-			error = got_error(GOT_ERR_BAD_PATH);
+		TAILQ_FOREACH(pe, &paths, entry) {
+			if (strlen(pe->path) == 0)
+				continue;
+			error = got_error_msg(GOT_ERR_BAD_PATH,
+			    "switching between branches requires that "
+			    "the entire work tree gets updated");
 			goto done;
 		}
 		error = got_ref_resolve(&head_commit_id, repo, head_ref);
@@ -1210,7 +1212,7 @@ cmd_update(int argc, char *argv[])
 			goto done;
 	}
 
-	error = got_worktree_checkout_files(worktree, path, repo,
+	error = got_worktree_checkout_files(worktree, &paths, repo,
 	    update_progress, &did_something, check_cancelled, NULL);
 	if (error != NULL)
 		goto done;
@@ -1221,7 +1223,9 @@ cmd_update(int argc, char *argv[])
 		printf("Already up-to-date\n");
 done:
 	free(worktree_path);
-	free(path);
+	TAILQ_FOREACH(pe, &paths, entry)
+		free((char *)pe->path);
+	got_pathlist_free(&paths);
 	free(commit_id);
 	free(commit_id_str);
 	return error;
blob - c5070b49c1c0ade4b575d6fa753b6b239b460635
blob + 56a58656792331a503dc2c9ac8dde91ea15d3a5a
--- include/got_path.h
+++ include/got_path.h
@@ -101,6 +101,9 @@ int got_path_dir_is_empty(const char *);
 /* dirname(3) with error handling and dynamically allocated result. */
 const struct got_error *got_path_dirname(char **, const char *);
 
+/* basename(3) with dynamically allocated result. */
+const struct got_error *got_path_basename(char **, const char *);
+
 /* Strip trailing slashes from a path; path will be modified in-place. */
 void got_path_strip_trailing_slashes(char *);
 
blob - e98f4eefa2ca47670e414b5ece91a68d435e66d0
blob + 0a7f10b7adad524cb6e14342756f48ff28261a6c
--- include/got_worktree.h
+++ include/got_worktree.h
@@ -114,21 +114,21 @@ typedef const struct got_error *(*got_worktree_cancel_
  * The checkout progress callback will be invoked with the provided
  * void * argument, and the path of each checked out file.
  *
- * It is possible to restrict the checkout operation to a specific path in
- * the work tree, in which case all files outside this path will remain at
+ * It is possible to restrict the checkout operation to specific paths in
+ * the work tree, in which case all files outside those paths will remain at
  * their currently recorded base commit. Inconsistent base commits can be
  * repaired later by running another update operation across the entire work
  * tree. Inconsistent base-commits may also occur if this function runs into
  * an error or if the checkout operation is cancelled by the cancel callback.
- * The specified path is relative to the work tree's root. Pass "" to check
- * out files across the entire work tree.
+ * Allspecified paths are relative to the work tree's root. Pass a pathlist
+ * with a single empty path "" to check out files across the entire work tree.
  *
  * Some operations may refuse to run while the work tree contains files from
  * multiple base commits.
  */
 const struct got_error *got_worktree_checkout_files(struct got_worktree *,
-    const char *, struct got_repository *, got_worktree_checkout_cb, void *,
-    got_worktree_cancel_cb, void *);
+    struct got_pathlist_head *, struct got_repository *,
+    got_worktree_checkout_cb, void *, got_worktree_cancel_cb, void *);
 
 /* Merge the differences between two commits into a work tree. */
 const struct got_error *
blob - f3e258fea9747229b54c77f3ec2be3c804b0f38c
blob + 66561a03e3bf9733f886c889f517c0d9f2dab98f
--- lib/path.c
+++ lib/path.c
@@ -366,6 +366,22 @@ got_path_dirname(char **parent, const char *path)
 
 	*parent = strdup(p);
 	if (*parent == NULL)
+		return got_error_from_errno("strdup");
+
+	return NULL;
+}
+
+const struct got_error *
+got_path_basename(char **s, const char *path)
+{
+	char *base;
+
+	base = basename(path);
+	if (base == NULL)
+		return got_error_from_errno2("basename", path);
+
+	*s = strdup(base);
+	if (*s == NULL)
 		return got_error_from_errno("strdup");
 
 	return NULL;
blob - 697a915ed38df21ef42020ebdfebe2dac3ab36a2
blob + 0c1f987a71914a9ed85c7e5b799dbe4125269303
--- lib/worktree.c
+++ lib/worktree.c
@@ -1774,34 +1774,59 @@ done:
 }
 
 const struct got_error *
-got_worktree_checkout_files(struct got_worktree *worktree, const char *path,
-    struct got_repository *repo, got_worktree_checkout_cb progress_cb,
-    void *progress_arg, got_worktree_cancel_cb cancel_cb, void *cancel_arg)
+got_worktree_checkout_files(struct got_worktree *worktree,
+    struct got_pathlist_head *paths, struct got_repository *repo,
+    got_worktree_checkout_cb progress_cb, void *progress_arg,
+    got_worktree_cancel_cb cancel_cb, void *cancel_arg)
 {
 	const struct got_error *err = NULL, *sync_err, *unlockerr;
 	struct got_commit_object *commit = NULL;
-	struct got_object_id *tree_id = NULL;
 	struct got_tree_object *tree = NULL;
 	struct got_fileindex *fileindex = NULL;
 	char *fileindex_path = NULL;
-	char *relpath = NULL, *entry_name = NULL;
-	int entry_type;
+	struct got_pathlist_entry *pe;
+	struct tree_path_data {
+		SIMPLEQ_ENTRY(tree_path_data) entry;
+		struct got_object_id *tree_id;
+		int entry_type;
+		char *relpath;
+		char *entry_name;
+	} *tpd = NULL;
+	SIMPLEQ_HEAD(tree_paths, tree_path_data) tree_paths;
 
+	SIMPLEQ_INIT(&tree_paths);
+
 	err = lock_worktree(worktree, LOCK_EX);
 	if (err)
 		return err;
 
-	err = find_tree_entry_for_checkout(&entry_type, &relpath, &tree_id,
-	    path, worktree, repo);
-	if (err)
-		goto done;
+	/* Map all specified paths to in-repository trees. */
+	TAILQ_FOREACH(pe, paths, entry) {
+		tpd = malloc(sizeof(*tpd));
+		if (tpd == NULL) {
+			err = got_error_from_errno("malloc");
+			goto done;
+		}
 
-	if (entry_type == GOT_OBJ_TYPE_BLOB) {
-		entry_name = basename(path);
-		if (entry_name == NULL) {
-			err = got_error_from_errno2("basename", path);
+		err = find_tree_entry_for_checkout(&tpd->entry_type,
+		    &tpd->relpath, &tpd->tree_id, pe->path, worktree, repo);
+		if (err) {
+			free(tpd);
 			goto done;
 		}
+
+		if (tpd->entry_type == GOT_OBJ_TYPE_BLOB) {
+			err = got_path_basename(&tpd->entry_name, pe->path);
+			if (err) {
+				free(tpd->relpath);
+				free(tpd->tree_id);
+				free(tpd);
+				goto done;
+			}
+		} else
+			tpd->entry_name = NULL;
+
+		SIMPLEQ_INSERT_TAIL(&tree_paths, tpd, entry);
 	}
 
 	/*
@@ -1813,31 +1838,47 @@ got_worktree_checkout_files(struct got_worktree *workt
 	if (err)
 		goto done;
 
-	err = checkout_files(worktree, fileindex, relpath, tree_id, entry_name,
-	    repo, progress_cb, progress_arg, cancel_cb, cancel_arg);
-	if (err == NULL) {
+	tpd = SIMPLEQ_FIRST(&tree_paths);
+	TAILQ_FOREACH(pe, paths, entry) {
 		struct bump_base_commit_id_arg bbc_arg;
+
+		err = checkout_files(worktree, fileindex, tpd->relpath,
+		    tpd->tree_id, tpd->entry_name, repo,
+		    progress_cb, progress_arg, cancel_cb, cancel_arg);
+		if (err)
+			break;
+
 		bbc_arg.base_commit_id = worktree->base_commit_id;
-		bbc_arg.entry_name = entry_name;
-		bbc_arg.path = path;
-		bbc_arg.path_len = strlen(path);
+		bbc_arg.entry_name = tpd->entry_name;
+		bbc_arg.path = pe->path;
+		bbc_arg.path_len = strlen(pe->path);
 		bbc_arg.progress_cb = progress_cb;
 		bbc_arg.progress_arg = progress_arg;
 		err = got_fileindex_for_each_entry_safe(fileindex,
 		    bump_base_commit_id, &bbc_arg);
+		if (err)
+			break;
+
+		tpd = SIMPLEQ_NEXT(tpd, entry);
 	}
 	sync_err = sync_fileindex(fileindex, fileindex_path);
 	if (sync_err && err == NULL)
 		err = sync_err;
 done:
 	free(fileindex_path);
-	free(relpath);
 	if (tree)
 		got_object_tree_close(tree);
 	if (commit)
 		got_object_commit_close(commit);
 	if (fileindex)
 		got_fileindex_free(fileindex);
+	while (!SIMPLEQ_EMPTY(&tree_paths)) {
+		tpd = SIMPLEQ_FIRST(&tree_paths);
+		SIMPLEQ_REMOVE_HEAD(&tree_paths, entry);
+		free(tpd->relpath);
+		free(tpd->tree_id);
+		free(tpd);
+	}
 	unlockerr = lock_worktree(worktree, LOCK_SH);
 	if (unlockerr && err == NULL)
 		err = unlockerr;
blob - e3be1a78b49b4799aa92d21da6fb2fbf6a85eb6b
blob + 6764523b2fe96ef5bcc178f254d6572792056e84
--- regress/cmdline/update.sh
+++ regress/cmdline/update.sh
@@ -1095,33 +1095,33 @@ function test_update_partial {
 	echo "modified epsilon/zeta" > $testroot/repo/epsilon/zeta
 	git_commit $testroot/repo -m "modified two files"
 
-	for f in alpha beta; do
-		echo "U  $f" > $testroot/stdout.expected
-		echo -n "Updated to commit " >> $testroot/stdout.expected
-		git_show_head $testroot/repo >> $testroot/stdout.expected
-		echo >> $testroot/stdout.expected
-
-		(cd $testroot/wt && got update $f > $testroot/stdout)
+	echo "U  alpha" > $testroot/stdout.expected
+	echo "U  beta" >> $testroot/stdout.expected
+	echo -n "Updated to commit " >> $testroot/stdout.expected
+	git_show_head $testroot/repo >> $testroot/stdout.expected
+	echo >> $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
+	(cd $testroot/wt && got update alpha beta > $testroot/stdout)
 
-		echo "modified $f" > $testroot/content.expected
-		cat $testroot/wt/$f > $testroot/content
+	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
 
-		cmp -s $testroot/content.expected $testroot/content
-		ret="$?"
-		if [ "$ret" != "0" ]; then
-			diff -u $testroot/content.expected $testroot/content
-			test_done "$testroot" "$ret"
-			return 1
-		fi
-	done
+	echo "modified alpha" > $testroot/content.expected
+	echo "modified beta" >> $testroot/content.expected
+
+	cat $testroot/wt/alpha $testroot/wt/beta > $testroot/content
+	cmp -s $testroot/content.expected $testroot/content
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/content.expected $testroot/content
+		test_done "$testroot" "$ret"
+		return 1
+	fi
 
 	echo "U  epsilon/zeta" > $testroot/stdout.expected
 	echo -n "Updated to commit " >> $testroot/stdout.expected
@@ -1167,33 +1167,32 @@ function test_update_partial_add {
 	(cd $testroot/repo && git add .)
 	git_commit $testroot/repo -m "added two files"
 
-	for f in new epsilon/new2; do
-		echo "A  $f" > $testroot/stdout.expected
-		echo -n "Updated to commit " >> $testroot/stdout.expected
-		git_show_head $testroot/repo >> $testroot/stdout.expected
-		echo >> $testroot/stdout.expected
+	echo "A  new" > $testroot/stdout.expected
+	echo "A  epsilon/new2" >> $testroot/stdout.expected
+	echo -n "Updated to commit " >> $testroot/stdout.expected
+	git_show_head $testroot/repo >> $testroot/stdout.expected
+	echo >> $testroot/stdout.expected
 
-		(cd $testroot/wt && got update $f > $testroot/stdout)
+	(cd $testroot/wt && got update new epsilon/new2 > $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
+	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 "$f" > $testroot/content.expected
-		cat $testroot/wt/$f > $testroot/content
+	echo "new" > $testroot/content.expected
+	echo "epsilon/new2" >> $testroot/content.expected
 
-		cmp -s $testroot/content.expected $testroot/content
-		ret="$?"
-		if [ "$ret" != "0" ]; then
-			diff -u $testroot/content.expected $testroot/content
-			test_done "$testroot" "$ret"
-			return 1
-		fi
-	done
+	cat $testroot/wt/new $testroot/wt/epsilon/new2 > $testroot/content
+
+	cmp -s $testroot/content.expected $testroot/content
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/content.expected $testroot/content
+	fi
 	test_done "$testroot" "$ret"
 }
 
@@ -1207,30 +1206,27 @@ function test_update_partial_rm {
 		return 1
 	fi
 
-	(cd $testroot/repo && git rm -q alpha)
-	(cd $testroot/repo && git rm -q epsilon/zeta)
+	(cd $testroot/repo && git rm -q alpha epsilon/zeta)
 	git_commit $testroot/repo -m "removed two files"
 
-	for f in alpha epsilon/zeta; do
-		echo "got: no such entry found in tree" \
-			> $testroot/stderr.expected
-
-		(cd $testroot/wt && got update $f 2> $testroot/stderr)
-		ret="$?"
-		if [ "$ret" == "0" ]; then
-			echo "update succeeded unexpectedly" >&2
-			test_done "$testroot" "1"
-			return 1
-		fi
+	echo "got: no such entry found in tree" \
+		> $testroot/stderr.expected
+
+	(cd $testroot/wt && got update alpha epsilon/zeta 2> $testroot/stderr)
+	ret="$?"
+	if [ "$ret" == "0" ]; then
+		echo "update succeeded unexpectedly" >&2
+		test_done "$testroot" "1"
+		return 1
+	fi
 
-		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
-	done
+	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
 	test_done "$testroot" "$ret"
 }
 
blob - 26f615388d6c11f50afdbdc448eda9bc500993f3
blob + dc88ba86ed50b00aa2bcbd9463195e873ac1a0af
--- regress/worktree/worktree_test.c
+++ regress/worktree/worktree_test.c
@@ -354,7 +354,10 @@ worktree_checkout(const char *repo_path)
 	char worktree_path[PATH_MAX];
 	int ok = 0;
 	struct stat sb;
+	struct got_pathlist_head paths;
 
+	TAILQ_INIT(&paths);
+
 	err = got_repo_open(&repo, repo_path);
 	if (err != NULL || repo == NULL)
 		goto done;
@@ -375,8 +378,11 @@ worktree_checkout(const char *repo_path)
 	if (err != NULL)
 		goto done;
 
-	err = got_worktree_checkout_files(worktree, "", repo, progress_cb, NULL,
-	    NULL, NULL);
+	err = got_pathlist_append(NULL, &paths, "", NULL);
+	if (err)
+		goto done;
+	err = got_worktree_checkout_files(worktree, &paths, repo, progress_cb,
+	    NULL, NULL, NULL);
 	if (err != NULL)
 		goto done;
 
@@ -410,6 +416,7 @@ done:
 		got_ref_close(head_ref);
 	if (repo)
 		got_repo_close(repo);
+	got_pathlist_free(&paths);
 	free(makefile_path);
 	free(cfile_path);
 	return ok;