From: Stefan Sperling Subject: add got fetch -X option To: gameoftrees@openbsd.org Date: Thu, 22 Jul 2021 13:02:00 +0200 got fetch -X makes it easier to delete all references which belong to a particular remote repository. ok? Note that we are currently lacking built-in means to delete the corresponding objects. Fetched objects will generally be stored in pack files and our 'gotadmin cleanup' command can only remove objects stored in loose form. This can be addressed later. Perhaps gotadmin could offer a way to detect and remove pack files which only contribute redundant or unreferenced objects. diff b07ecf77ff51de303ba8127321b8a850836e2810 1046f7676f73828f0c7b1c002351a8f7b4426d76 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); @@ -2116,6 +2116,62 @@ done: } 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 (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 (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