commit f2ea84fab96c77d352fe460a37f2722beb6225d7 from: Stefan Sperling date: Sat Jul 27 12:26:10 2019 UTC make 'got update' accept mulitple path arguments 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;