diff --git a/contrib/scalar/scalar.c b/contrib/scalar/scalar.c index e6f3f1687ad626..055937510be00e 100644 --- a/contrib/scalar/scalar.c +++ b/contrib/scalar/scalar.c @@ -19,55 +19,91 @@ static int is_unattended(void) { return git_env_bool("Scalar_UNATTENDED", 0); } +/* + * Remove the deepest subdirectory in the provided path string. Path must not + * include a trailing path separator. Returns 1 if parent directory found, + * otherwise 0. + */ +static int strbuf_parentdir(struct strbuf *buf) +{ + size_t len = buf->len; + size_t offset = offset_1st_component(buf->buf); + char *path_sep = find_last_dir_sep(buf->buf + offset); + strbuf_setlen(buf, path_sep ? path_sep - buf->buf : offset); + + return buf->len < len; +} + static void setup_enlistment_directory(int argc, const char **argv, const char * const *usagestr, - const struct option *options) + const struct option *options, + struct strbuf *enlistment_root) { + struct strbuf path = STRBUF_INIT; + char *root; + int enlistment_found = 0; + if (startup_info->have_repository) BUG("gitdir already set up?!?"); if (argc > 1) usage_with_options(usagestr, options); + /* find the worktree, determine its corresponding root */ if (argc == 1) { - char *src = xstrfmt("%s/src", argv[0]); - const char *dir = is_directory(src) ? src : argv[0]; + strbuf_add_absolute_path(&path, argv[0]); + } else if (strbuf_getcwd(&path) < 0) { + die(_("need a working directory")); + } - if (chdir(dir) < 0) - die_errno(_("could not switch to '%s'"), dir); + strbuf_trim_trailing_dir_sep(&path); + do { + const size_t len = path.len; - free(src); - } else { - /* find the worktree, and ensure that it is named `src` */ - struct strbuf path = STRBUF_INIT; + /* check if currently in enlistment root with src/ workdir */ + strbuf_addstr(&path, "/src/.git"); + if (is_git_directory(path.buf)) { + strbuf_strip_suffix(&path, "/.git"); - if (strbuf_getcwd(&path) < 0) - die(_("need a working directory")); - - for (;;) { - size_t len = path.len; - - strbuf_addstr(&path, "/src/.git"); - if (is_git_directory(path.buf)) { - strbuf_setlen(&path, len); - strbuf_addstr(&path, "/src"); - if (chdir(path.buf) < 0) - die_errno(_("could not switch to '%s'"), - path.buf); - strbuf_release(&path); - break; - } + if (enlistment_root) + strbuf_add(enlistment_root, path.buf, len); - while (len > 0 && !is_dir_sep(path.buf[--len])) - ; /* keep looking for parent directory */ + enlistment_found = 1; + break; + } - if (!len) - die(_("could not find enlistment root")); + /* reset to original path */ + strbuf_setlen(&path, len); + /* check if currently in workdir */ + strbuf_addstr(&path, "/.git"); + if (is_git_directory(path.buf)) { strbuf_setlen(&path, len); + + if (enlistment_root) { + /* + * If the worktree's directory's name is `src`, the enlistment is the + * parent directory, otherwise it is identical to the worktree. + */ + root = strip_path_suffix(path.buf, "src"); + strbuf_addstr(enlistment_root, root ? root : path.buf); + free(root); + } + + enlistment_found = 1; + break; } - } + strbuf_setlen(&path, len); + } while (strbuf_parentdir(&path)); + + if (!enlistment_found) + die(_("could not find enlistment root")); + + if (chdir(path.buf) < 0) + die_errno(_("could not switch to '%s'"), path.buf); + + strbuf_release(&path); setup_git_directory(); } @@ -789,17 +825,8 @@ static char *remote_default_branch(const char *url) return NULL; } -static void strbuf_parentdir(struct strbuf *buf) +static int delete_enlistment(struct strbuf *enlistment) { - int len = buf->len; - while (len > 0 && !is_dir_sep(buf->buf[--len])) - ; /* keep looking for parent directory */ - strbuf_setlen(buf, len); -} - -static int delete_enlistment(void) -{ - struct strbuf enlistment = STRBUF_INIT; #ifdef WIN32 struct strbuf parent = STRBUF_INIT; #endif @@ -807,24 +834,19 @@ static int delete_enlistment(void) if (unregister_dir()) die(_("failed to unregister repository")); - /* Compute the enlistment path (parent of the worktree) */ - strbuf_addstr(&enlistment, the_repository->worktree); - strbuf_parentdir(&enlistment); - #ifdef WIN32 /* Change current directory to one outside of the enlistment so that we may delete everything underneath it. */ - strbuf_addbuf(&parent, &enlistment); + strbuf_addbuf(&parent, enlistment); strbuf_parentdir(&parent); if (chdir(parent.buf) < 0) die_errno(_("could not switch to '%s'"), parent.buf); strbuf_release(&parent); #endif - if (remove_dir_recursively(&enlistment, 0)) + if (remove_dir_recursively(enlistment, 0)) die(_("failed to delete enlistment directory")); - strbuf_release(&enlistment); return 0; } @@ -1185,9 +1207,9 @@ static int cmd_diagnose(int argc, const char **argv) argc = parse_options(argc, argv, NULL, options, usage, 0); - setup_enlistment_directory(argc, argv, usage, options); + setup_enlistment_directory(argc, argv, usage, options, &buf); - strbuf_addstr(&buf, "../.scalarDiagnostics/scalar_"); + strbuf_addstr(&buf, "/.scalarDiagnostics/scalar_"); strbuf_addftime(&buf, "%Y%m%d_%H%M%S", localtime_r(&now, &tm), 0, 0); if (run_git("init", "-q", "-b", "dummy", "--bare", buf.buf, NULL)) { res = error(_("could not initialize temporary repository: %s"), @@ -1299,7 +1321,7 @@ static int cmd_register(int argc, const char **argv) argc = parse_options(argc, argv, NULL, options, usage, 0); - setup_enlistment_directory(argc, argv, usage, options); + setup_enlistment_directory(argc, argv, usage, options, NULL); return register_dir(); } @@ -1335,7 +1357,7 @@ static int cmd_reconfigure(int argc, const char **argv) usage, 0); if (!all) { - setup_enlistment_directory(argc, argv, usage, options); + setup_enlistment_directory(argc, argv, usage, options, NULL); return set_recommended_config(1); } @@ -1421,7 +1443,7 @@ static int cmd_run(int argc, const char **argv) argc--; argv++; - setup_enlistment_directory(argc, argv, usagestr, options); + setup_enlistment_directory(argc, argv, usagestr, options, NULL); strbuf_release(&buf); if (i == 0) @@ -1440,6 +1462,24 @@ static int cmd_run(int argc, const char **argv) return 0; } +static int remove_deleted_enlistment(struct strbuf *path) +{ + int res = 0; + strbuf_realpath_forgiving(path, path->buf, 1); + + if (run_git("config", "--global", + "--unset", "--fixed-value", + "scalar.repo", path->buf, NULL) < 0) + res = -1; + + if (run_git("config", "--global", + "--unset", "--fixed-value", + "maintenance.repo", path->buf, NULL) < 0) + res = -1; + + return res; +} + static int cmd_unregister(int argc, const char **argv) { struct option options[] = { @@ -1459,32 +1499,29 @@ static int cmd_unregister(int argc, const char **argv) * mistake and _still_ wants to unregister the thing. */ if (argc == 1) { - struct strbuf path = STRBUF_INIT; + struct strbuf src_path = STRBUF_INIT, workdir_path = STRBUF_INIT; - strbuf_addf(&path, "%s/src/.git", argv[0]); - if (!is_directory(path.buf)) { - int res = 0; + strbuf_addf(&src_path, "%s/src/.git", argv[0]); + strbuf_addf(&workdir_path, "%s/.git", argv[0]); + if (!is_directory(src_path.buf) && !is_directory(workdir_path.buf)) { + /* remove possible matching registrations */ + int res = -1; - strbuf_strip_suffix(&path, "/.git"); - strbuf_realpath_forgiving(&path, path.buf, 1); - - if (run_git("config", "--global", - "--unset", "--fixed-value", - "scalar.repo", path.buf, NULL) < 0) - res = -1; + strbuf_strip_suffix(&src_path, "/.git"); + res = remove_deleted_enlistment(&src_path) && res; - if (run_git("config", "--global", - "--unset", "--fixed-value", - "maintenance.repo", path.buf, NULL) < 0) - res = -1; + strbuf_strip_suffix(&workdir_path, "/.git"); + res = remove_deleted_enlistment(&workdir_path) && res; - strbuf_release(&path); + strbuf_release(&src_path); + strbuf_release(&workdir_path); return res; } - strbuf_release(&path); + strbuf_release(&src_path); + strbuf_release(&workdir_path); } - setup_enlistment_directory(argc, argv, usage, options); + setup_enlistment_directory(argc, argv, usage, options, NULL); return unregister_dir(); } @@ -1498,6 +1535,8 @@ static int cmd_delete(int argc, const char **argv) N_("scalar delete "), NULL }; + struct strbuf enlistment = STRBUF_INIT; + int res = 0; argc = parse_options(argc, argv, NULL, options, usage, 0); @@ -1505,9 +1544,12 @@ static int cmd_delete(int argc, const char **argv) if (argc != 1) usage_with_options(usage, options); - setup_enlistment_directory(argc, argv, usage, options); + setup_enlistment_directory(argc, argv, usage, options, &enlistment); - return delete_enlistment(); + res = delete_enlistment(&enlistment); + strbuf_release(&enlistment); + + return res; } static int cmd_help(int argc, const char **argv) @@ -1586,7 +1628,7 @@ static int cmd_cache_server(int argc, const char **argv) usage_msg_opt(_("--get/--set/--list are mutually exclusive"), usage, options); - setup_enlistment_directory(argc, argv, usage, options); + setup_enlistment_directory(argc, argv, usage, options, NULL); if (list) { const char *name = list, *url = list; diff --git a/contrib/scalar/t/t9099-scalar.sh b/contrib/scalar/t/t9099-scalar.sh index fd8d26324111b9..9644f91a6cd6b7 100755 --- a/contrib/scalar/t/t9099-scalar.sh +++ b/contrib/scalar/t/t9099-scalar.sh @@ -204,4 +204,57 @@ test_expect_success '`scalar clone` with GVFS-enabled server' ' ) ' +test_expect_success '`scalar register` parallel to worktree' ' + git init test-repo/src && + mkdir -p test-repo/out && + scalar register test-repo/out && + git config --get --global --fixed-value \ + maintenance.repo "$(pwd)/test-repo/src" && + scalar list >scalar.repos && + grep -F "$(pwd)/test-repo/src" scalar.repos && + scalar delete test-repo +' + +test_expect_success '`scalar register` & `unregister` with existing repo' ' + git init existing && + scalar register existing && + git config --get --global --fixed-value \ + maintenance.repo "$(pwd)/existing" && + scalar list >scalar.repos && + grep -F "$(pwd)/existing" scalar.repos && + scalar unregister existing && + test_must_fail git config --get --global --fixed-value \ + maintenance.repo "$(pwd)/existing" && + scalar list >scalar.repos && + ! grep -F "$(pwd)/existing" scalar.repos +' + +test_expect_success '`scalar unregister` with existing repo, deleted .git' ' + scalar register existing && + rm -rf existing/.git && + scalar unregister existing && + test_must_fail git config --get --global --fixed-value \ + maintenance.repo "$(pwd)/existing" && + scalar list >scalar.repos && + ! grep -F "$(pwd)/existing" scalar.repos +' + +test_expect_success '`scalar register` existing repo with `src` folder' ' + git init existing && + mkdir -p existing/src && + scalar register existing/src && + scalar list >scalar.repos && + grep -F "$(pwd)/existing" scalar.repos && + scalar unregister existing && + scalar list >scalar.repos && + ! grep -F "$(pwd)/existing" scalar.repos +' + +test_expect_success '`scalar delete` with existing repo' ' + git init existing && + scalar register existing && + scalar delete existing && + test_path_is_missing existing +' + test_done