"GOT", but the "O" is a cute, smiling pufferfish. Index | Thread | Search

From:
Stefan Sperling <stsp@stsp.name>
Subject:
add got fetch -X option
To:
gameoftrees@openbsd.org
Date:
Thu, 22 Jul 2021 13:02:00 +0200

Download raw body.

Thread
  • Stefan Sperling:

    add got fetch -X option

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