commit 161728eb26bf63ad53f11367358ea6190bad8968 from: Stefan Sperling date: Sat Jul 24 09:08:52 2021 UTC add 'got fetch -X' option for deleting references created by 'got fetch' commit - da630daa54557a42066707f18f070393197a0243 commit + 161728eb26bf63ad53f11367358ea6190bad8968 blob - 64eb89a0f0d09c013a42191b548de64e1ecc96c7 blob + 2fc788135c449d339f9e22c8ac5d28eaf3bb8047 --- got/got.1 +++ got/got.1 @@ -319,7 +319,7 @@ namespace. .It Cm cl Short alias for .Cm clone . -.It Cm fetch Oo Fl a Oc Oo Fl b Ar branch Oc Oo Fl d Oc Oo Fl l Oc Oo Fl r Ar repository-path Oc Oo Fl t Oc Oo Fl q Oc Oo Fl v Oc Oo Fl R Ar reference Oc Op Ar remote-repository +.It Cm fetch Oo Fl a Oc Oo Fl b Ar branch Oc Oo Fl d Oc Oo Fl l Oc Oo Fl r Ar repository-path Oc Oo Fl t Oc Oo Fl q Oc Oo Fl v Oc Oo Fl R Ar reference Oc Oo Fl X Oc Op Ar remote-repository Fetch new changes from a remote repository. If no .Ar remote-repository @@ -466,6 +466,29 @@ will refuse to fetch references from the remote reposi or .Dq refs/got/ namespace. +.It Fl X +Delete all references which correspond to a particular +.Ar remote-repository +instead of fetching new changes. +This can be useful when a remote repository is being removed from +.Xr got.conf 5 . +.Pp +With +.Fl X , +the +.Ar remote-repository +argument is mandatory and no other options except +.Fl r , +.Fl v , +and +.Fl q +are allowed. +.Pp +Only references are deleted. +Any commit, tree, tag, and blob objects fetched from a remote repository +will generally be stored in pack files and may be removed separately with +.Xr git-repack 1 +and Git's garbage collector. .El .It Cm fe Short alias for blob - f4cf4ebbb4c63cb23b1bfd3b1d55c216a3e8951b blob + eead62c4d2989b648729acc4857033da98231ab3 --- got/got.c +++ got/got.c @@ -1949,7 +1949,7 @@ __dead static void usage_fetch(void) { fprintf(stderr, "usage: %s fetch [-a] [-b branch] [-d] [-l] " - "[-r repository-path] [-t] [-q] [-v] [-R reference] " + "[-r repository-path] [-t] [-q] [-v] [-R reference] [-X] " "[remote-repository-name]\n", getprogname()); exit(1); @@ -2112,10 +2112,66 @@ update_wanted_ref(const char *refname, struct got_obje } done: free(remote_refname); + return err; +} + +static const struct got_error * +delete_ref(struct got_repository *repo, struct got_reference *ref) +{ + const struct got_error *err = NULL; + struct got_object_id *id = NULL; + char *id_str = NULL; + const char *target; + + if (got_ref_is_symbolic(ref)) { + target = got_ref_get_symref_target(ref); + } else { + err = got_ref_resolve(&id, repo, ref); + if (err) + goto done; + err = got_object_id_str(&id_str, id); + if (err) + goto done; + target = id_str; + } + + err = got_ref_delete(ref, repo); + if (err) + goto done; + + printf("Deleted %s: %s\n", got_ref_get_name(ref), target); +done: + free(id); + free(id_str); return err; } static const struct got_error * +delete_refs_for_remote(struct got_repository *repo, const char *remote_name) +{ + const struct got_error *err = NULL; + struct got_reflist_head refs; + struct got_reflist_entry *re; + char *prefix; + + TAILQ_INIT(&refs); + + if (asprintf(&prefix, "refs/remotes/%s", remote_name) == -1) { + err = got_error_from_errno("asprintf"); + goto done; + } + err = got_ref_list(&refs, repo, prefix, got_ref_cmp_by_name, NULL); + if (err) + goto done; + + TAILQ_FOREACH(re, &refs, entry) + delete_ref(repo, re->ref); +done: + got_ref_list_free(&refs); + return err; +} + +static const struct got_error * cmd_fetch(int argc, char *argv[]) { const struct got_error *error = NULL, *unlock_err; @@ -2136,14 +2192,14 @@ cmd_fetch(int argc, char *argv[]) pid_t fetchpid = -1; struct got_fetch_progress_arg fpa; int verbosity = 0, fetch_all_branches = 0, list_refs_only = 0; - int delete_refs = 0, replace_tags = 0; + int delete_refs = 0, replace_tags = 0, delete_remote = 0; TAILQ_INIT(&refs); TAILQ_INIT(&symrefs); TAILQ_INIT(&wanted_branches); TAILQ_INIT(&wanted_refs); - while ((ch = getopt(argc, argv, "ab:dlr:tvqR:")) != -1) { + while ((ch = getopt(argc, argv, "ab:dlr:tvqR:X")) != -1) { switch (ch) { case 'a': fetch_all_branches = 1; @@ -2185,6 +2241,9 @@ cmd_fetch(int argc, char *argv[]) if (error) return error; break; + case 'X': + delete_remote = 1; + break; default: usage_fetch(); break; @@ -2202,11 +2261,27 @@ cmd_fetch(int argc, char *argv[]) option_conflict('l', 'a'); if (delete_refs) option_conflict('l', 'd'); + if (delete_remote) + option_conflict('l', 'X'); } - - if (argc == 0) + if (delete_remote) { + if (fetch_all_branches) + option_conflict('X', 'a'); + if (!TAILQ_EMPTY(&wanted_branches)) + option_conflict('X', 'b'); + if (delete_refs) + option_conflict('X', 'd'); + if (replace_tags) + option_conflict('X', 't'); + if (!TAILQ_EMPTY(&wanted_refs)) + option_conflict('X', 'R'); + } + + if (argc == 0) { + if (delete_remote) + errx(1, "-X option requires a remote name"); remote_name = GOT_FETCH_DEFAULT_REMOTE_NAME; - else if (argc == 1) + } else if (argc == 1) remote_name = argv[0]; else usage_fetch(); @@ -2243,6 +2318,11 @@ cmd_fetch(int argc, char *argv[]) if (error) goto done; + if (delete_remote) { + error = delete_refs_for_remote(repo, remote_name); + goto done; /* nothing else to do */ + } + if (worktree) { worktree_conf = got_worktree_get_gotconfig(worktree); if (worktree_conf) { @@ -5273,39 +5353,17 @@ list_refs(struct got_repository *repo, const char *ref } static const struct got_error * -delete_ref(struct got_repository *repo, const char *refname) +delete_ref_by_name(struct got_repository *repo, const char *refname) { - const struct got_error *err = NULL; + const struct got_error *err; struct got_reference *ref; - struct got_object_id *id = NULL; - char *id_str = NULL; - const char *target; err = got_ref_open(&ref, repo, refname, 0); if (err) return err; - if (got_ref_is_symbolic(ref)) { - target = got_ref_get_symref_target(ref); - } else { - err = got_ref_resolve(&id, repo, ref); - if (err) - goto done; - err = got_object_id_str(&id_str, id); - if (err) - goto done; - target = id_str; - } - - err = got_ref_delete(ref, repo); - if (err) - goto done; - - printf("Deleted %s: %s\n", got_ref_get_name(ref), target); -done: + err = delete_ref(repo, ref); got_ref_close(ref); - free(id); - free(id_str); return err; } @@ -5512,7 +5570,7 @@ cmd_ref(int argc, char *argv[]) if (do_list) error = list_refs(repo, refname); else if (do_delete) - error = delete_ref(repo, refname); + error = delete_ref_by_name(repo, refname); else if (symref_target) error = add_symref(repo, refname, symref_target); else { blob - 5292407a8ef566f5ec84e218ccc736574fa22077 blob + c0be768af618f9a9416b82e0725d11644762ee9b --- regress/cmdline/fetch.sh +++ regress/cmdline/fetch.sh @@ -1090,6 +1090,100 @@ EOF test_done "$testroot" "$ret" } +test_fetch_delete_remote_refs() { + local testroot=`test_init fetch_basic` + local testurl=ssh://127.0.0.1/$testroot + local commit_id=`git_show_head $testroot/repo` + + got clone -q $testurl/repo $testroot/repo-clone + ret="$?" + if [ "$ret" != "0" ]; then + echo "got clone command failed unexpectedly" >&2 + test_done "$testroot" "$ret" + return 1 + fi + + got ref -l -r $testroot/repo-clone > $testroot/stdout + if [ "$ret" != "0" ]; then + echo "got ref command failed unexpectedly" >&2 + test_done "$testroot" "$ret" + return 1 + fi + + echo "HEAD: refs/heads/master" > $testroot/stdout.expected + echo "refs/heads/master: $commit_id" >> $testroot/stdout.expected + echo "refs/remotes/origin/HEAD: refs/remotes/origin/master" \ + >> $testroot/stdout.expected + echo "refs/remotes/origin/master: $commit_id" \ + >> $testroot/stdout.expected + + cmp -s $testroot/stdout $testroot/stdout.expected + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + got fetch -q -r $testroot/repo-clone -X > $testroot/stdout \ + 2> $testroot/stderr + ret="$?" + if [ "$ret" == "0" ]; then + echo "got fetch command succeeded unexpectedly" >&2 + test_done "$testroot" "$ret" + return 1 + fi + + echo "got: -X option requires a remote name" > $testroot/stderr.expected + cmp -s $testroot/stderr $testroot/stderr.expected + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/stderr.expected $testroot/stderr + test_done "$testroot" "$ret" + return 1 + fi + + got fetch -q -r $testroot/repo-clone -X origin > $testroot/stdout \ + 2> $testroot/stderr + ret="$?" + if [ "$ret" != "0" ]; then + echo "got fetch command failed unexpectedly" >&2 + test_done "$testroot" "$ret" + return 1 + fi + + echo -n "Deleted refs/remotes/origin/HEAD: " > $testroot/stdout.expected + echo "refs/remotes/origin/master" >> $testroot/stdout.expected + echo "Deleted refs/remotes/origin/master: $commit_id" \ + >> $testroot/stdout.expected + + cmp -s $testroot/stdout $testroot/stdout.expected + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + got ref -l -r $testroot/repo-clone > $testroot/stdout + if [ "$ret" != "0" ]; then + echo "got ref command failed unexpectedly" >&2 + test_done "$testroot" "$ret" + return 1 + fi + + echo "HEAD: refs/heads/master" > $testroot/stdout.expected + echo "refs/heads/master: $commit_id" >> $testroot/stdout.expected + + cmp -s $testroot/stdout $testroot/stdout.expected + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/stdout.expected $testroot/stdout + fi + test_done "$testroot" "$ret" +} + + test_parseargs "$@" run_test test_fetch_basic run_test test_fetch_list @@ -1103,3 +1197,4 @@ run_test test_fetch_replace_symref run_test test_fetch_update_headref run_test test_fetch_headref_deleted_locally run_test test_fetch_gotconfig_remote_repo +run_test test_fetch_delete_remote_refs