commit - 54817d72bf743bcd8d0313b5af1bd9899bbda5e9
commit + f2ea84fab96c77d352fe460a37f2722beb6225d7
blob - 50fbe74615a591d44b3910961c12101fd22bb7c3
blob + 5d1c026749d5a0bc5909270c81cfd0bb9b78a61f
--- got/got.1
+++ got/got.1
.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
.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
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) {
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;
printf("Now shut up and hack\n");
done:
+ got_pathlist_free(&paths);
free(commit_id_str);
free(repo_path);
free(worktree_path);
__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);
}
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':
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)
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);
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;
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
/* 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
* 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
*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
}
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);
}
/*
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
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
(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"
}
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
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;
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;
got_ref_close(head_ref);
if (repo)
got_repo_close(repo);
+ got_pathlist_free(&paths);
free(makefile_path);
free(cfile_path);
return ok;