Commit Diff


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