fetch.prune::
If true, fetch will automatically behave as if the `--prune`
- option was given on the command line. See also `remote.<name>.prune`.
+ option was given on the command line. See also `remote.<name>.prune`
+ and the PRUNING section of linkgit:git-fetch[1].
+
+ fetch.pruneTags::
+ If true, fetch will automatically behave as if the
+ `refs/tags/*:refs/tags/*` refspec was provided when pruning,
+ if not set already. This allows for setting both this option
+ and `fetch.prune` to maintain a 1=1 mapping to upstream
+ refs. See also `remote.<name>.pruneTags` and the PRUNING
+ section of linkgit:git-fetch[1].
fetch.output::
Control how ref update status is printed. Valid values are
remote (as if the `--prune` option was given on the command line).
Overrides `fetch.prune` settings, if any.
+ remote.<name>.pruneTags::
+ When set to true, fetching from this remote by default will also
+ remove any local tags that no longer exist on the remote if pruning
+ is activated in general via `remote.<name>.prune`, `fetch.prune` or
+ `--prune`. Overrides `fetch.pruneTags` settings, if any.
+ +
+ See also `remote.<name>.prune` and the PRUNING section of
+ linkgit:git-fetch[1].
+
remotes.<group>::
The list of remotes which are fetched by "git remote update
<group>". See linkgit:git-remote[1].
was run. I.e., `upload-pack` will feed input intended for
`pack-objects` to the hook, and expects a completed packfile on
stdout.
+
+uploadpack.allowFilter::
+ If this option is set, `upload-pack` will advertise partial
+ clone and partial fetch object filtering.
+
Note that this configuration variable is ignored if it is seen in the
repository-level config (this is a safety measure against fetching from
#include "argv-array.h"
#include "utf8.h"
#include "packfile.h"
+#include "list-objects-filter-options.h"
static const char * const builtin_fetch_usage[] = {
N_("git fetch [<options>] [<repository> [<refspec>...]]"),
static int prune = -1; /* unspecified */
#define PRUNE_BY_DEFAULT 0 /* do we prune by default? */
+ static int fetch_prune_tags_config = -1; /* unspecified */
+ static int prune_tags = -1; /* unspecified */
+ #define PRUNE_TAGS_BY_DEFAULT 0 /* do we prune tags by default? */
+
static int all, append, dry_run, force, keep, multiple, update_head_ok, verbosity, deepen_relative;
static int progress = -1;
static int tags = TAGS_DEFAULT, unshallow, update_shallow, deepen;
static int shown_url = 0;
static int refmap_alloc, refmap_nr;
static const char **refmap_array;
+static struct list_objects_filter_options filter_options;
static int git_fetch_config(const char *k, const char *v, void *cb)
{
return 0;
}
+ if (!strcmp(k, "fetch.prunetags")) {
+ fetch_prune_tags_config = git_config_bool(k, v);
+ return 0;
+ }
+
if (!strcmp(k, "submodule.recurse")) {
int r = git_config_bool(k, v) ?
RECURSE_SUBMODULES_ON : RECURSE_SUBMODULES_OFF;
N_("number of submodules fetched in parallel")),
OPT_BOOL('p', "prune", &prune,
N_("prune remote-tracking branches no longer on remote")),
+ OPT_BOOL('P', "prune-tags", &prune_tags,
+ N_("prune local tags no longer on remote and clobber changed tags")),
{ OPTION_CALLBACK, 0, "recurse-submodules", &recurse_submodules, N_("on-demand"),
N_("control recursive fetching of submodules"),
PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules },
TRANSPORT_FAMILY_IPV4),
OPT_SET_INT('6', "ipv6", &family, N_("use IPv6 addresses only"),
TRANSPORT_FAMILY_IPV6),
+ OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options),
OPT_END()
};
set_option(transport, TRANS_OPT_DEEPEN_RELATIVE, "yes");
if (update_shallow)
set_option(transport, TRANS_OPT_UPDATE_SHALLOW, "yes");
+ if (filter_options.choice) {
+ set_option(transport, TRANS_OPT_LIST_OBJECTS_FILTER,
+ filter_options.filter_spec);
+ set_option(transport, TRANS_OPT_FROM_PROMISOR, "1");
+ }
return transport;
}
argv_array_push(argv, "--dry-run");
if (prune != -1)
argv_array_push(argv, prune ? "--prune" : "--no-prune");
+ if (prune_tags != -1)
+ argv_array_push(argv, prune_tags ? "--prune-tags" : "--no-prune-tags");
if (update_head_ok)
argv_array_push(argv, "--update-head-ok");
if (force)
return result;
}
- static int fetch_one(struct remote *remote, int argc, const char **argv)
+/*
+ * Fetching from the promisor remote should use the given filter-spec
+ * or inherit the default filter-spec from the config.
+ */
+static inline void fetch_one_setup_partial(struct remote *remote)
+{
+ /*
+ * Explicit --no-filter argument overrides everything, regardless
+ * of any prior partial clones and fetches.
+ */
+ if (filter_options.no_filter)
+ return;
+
+ /*
+ * If no prior partial clone/fetch and the current fetch DID NOT
+ * request a partial-fetch, do a normal fetch.
+ */
+ if (!repository_format_partial_clone && !filter_options.choice)
+ return;
+
+ /*
+ * If this is the FIRST partial-fetch request, we enable partial
+ * on this repo and remember the given filter-spec as the default
+ * for subsequent fetches to this remote.
+ */
+ if (!repository_format_partial_clone && filter_options.choice) {
+ partial_clone_register(remote->name, &filter_options);
+ return;
+ }
+
+ /*
+ * We are currently limited to only ONE promisor remote and only
+ * allow partial-fetches from the promisor remote.
+ */
+ if (strcmp(remote->name, repository_format_partial_clone)) {
+ if (filter_options.choice)
+ die(_("--filter can only be used with the remote configured in core.partialClone"));
+ return;
+ }
+
+ /*
+ * Do a partial-fetch from the promisor remote using either the
+ * explicitly given filter-spec or inherit the filter-spec from
+ * the config.
+ */
+ if (!filter_options.choice)
+ partial_clone_get_default_filter_spec(&filter_options);
+ return;
+}
+
+ static int fetch_one(struct remote *remote, int argc, const char **argv, int prune_tags_ok)
{
static const char **refs = NULL;
struct refspec *refspec;
int ref_nr = 0;
+ int j = 0;
int exit_code;
+ int maybe_prune_tags;
+ int remote_via_config = remote_is_configured(remote, 0);
if (!remote)
die(_("No remote repository specified. Please, specify either a URL or a\n"
if (prune < 0) {
/* no command line request */
- if (0 <= gtransport->remote->prune)
- prune = gtransport->remote->prune;
+ if (0 <= remote->prune)
+ prune = remote->prune;
else if (0 <= fetch_prune_config)
prune = fetch_prune_config;
else
prune = PRUNE_BY_DEFAULT;
}
+ if (prune_tags < 0) {
+ /* no command line request */
+ if (0 <= remote->prune_tags)
+ prune_tags = remote->prune_tags;
+ else if (0 <= fetch_prune_tags_config)
+ prune_tags = fetch_prune_tags_config;
+ else
+ prune_tags = PRUNE_TAGS_BY_DEFAULT;
+ }
+
+ maybe_prune_tags = prune_tags_ok && prune_tags;
+ if (maybe_prune_tags && remote_via_config)
+ add_prune_tags_to_fetch_refspec(remote);
+
+ if (argc > 0 || (maybe_prune_tags && !remote_via_config)) {
+ size_t nr_alloc = st_add3(argc, maybe_prune_tags, 1);
+ refs = xcalloc(nr_alloc, sizeof(const char *));
+ if (maybe_prune_tags) {
+ refs[j++] = xstrdup("refs/tags/*:refs/tags/*");
+ ref_nr++;
+ }
+ }
+
if (argc > 0) {
- int j = 0;
int i;
- refs = xcalloc(st_add(argc, 1), sizeof(const char *));
for (i = 0; i < argc; i++) {
if (!strcmp(argv[i], "tag")) {
i++;
argv[i], argv[i]);
} else
refs[j++] = argv[i];
+ ref_nr++;
}
- refs[j] = NULL;
- ref_nr = j;
}
sigchain_push_common(unlock_pack_on_signal);
{
int i;
struct string_list list = STRING_LIST_INIT_DUP;
- struct remote *remote;
+ struct remote *remote = NULL;
int result = 0;
++ int prune_tags_ok = 1;
struct argv_array argv_gc_auto = ARGV_ARRAY_INIT;
packet_trace_identity("fetch");
+ fetch_if_missing = 0;
+
/* Record the command line for the reflog */
strbuf_addstr(&default_rla, "fetch");
for (i = 1; i < argc; i++)
if (depth || deepen_since || deepen_not.nr)
deepen = 1;
+ if (filter_options.choice && !repository_format_partial_clone)
+ die("--filter can only be used when extensions.partialClone is set");
+
if (all) {
if (argc == 1)
die(_("fetch --all does not take a repository argument"));
else if (argc > 1)
die(_("fetch --all does not make sense with refspecs"));
(void) for_each_remote(get_one_remote_for_fetch, &list);
- result = fetch_multiple(&list);
} else if (argc == 0) {
/* No arguments -- use default remote */
remote = remote_get(NULL);
- result = fetch_one(remote, argc, argv, 1);
} else if (multiple) {
/* All arguments are assumed to be remotes or groups */
for (i = 0; i < argc; i++)
if (!add_remote_or_group(argv[i], &list))
die(_("No such remote or remote group: %s"), argv[i]);
- result = fetch_multiple(&list);
} else {
/* Single remote or group */
(void) add_remote_or_group(argv[0], &list);
/* More than one remote */
if (argc > 1)
die(_("Fetching a group and specifying refspecs does not make sense"));
- result = fetch_multiple(&list);
} else {
/* Zero or one remotes */
remote = remote_get(argv[0]);
- result = fetch_one(remote, argc-1, argv+1, argc == 1);
++ prune_tags_ok = (argc == 1);
+ argc--;
+ argv++;
}
}
- result = fetch_one(remote, argc, argv);
+ if (remote) {
+ if (filter_options.choice || repository_format_partial_clone)
+ fetch_one_setup_partial(remote);
++ result = fetch_one(remote, argc, argv, prune_tags_ok);
+ } else {
+ if (filter_options.choice)
+ die(_("--filter can only be used with the remote configured in core.partialClone"));
+ /* TODO should this also die if we have a previous partial-clone? */
+ result = fetch_multiple(&list);
+ }
+
if (!result && (recurse_submodules != RECURSE_SUBMODULES_OFF)) {
struct argv_array options = ARGV_ARRAY_INIT;
__git_list_merge_strategies ()
{
- git merge -s help 2>&1 |
+ LANG=C LC_ALL=C git merge -s help 2>&1 |
sed -n -e '/[Aa]vailable strategies are: /,/^$/{
s/\.$//
s/.*://
{
__git_find_repo_path
if [ -d "$__git_repo_path"/rebase-apply ]; then
- __gitcomp "--skip --continue --resolved --abort"
+ __gitcomp "--skip --continue --resolved --abort --quit"
return
fi
case "$cur" in
__git_fetch_options="
--quiet --verbose --append --upload-pack --force --keep --depth=
--tags --no-tags --all --prune --dry-run --recurse-submodules=
- --unshallow --update-shallow
+ --unshallow --update-shallow --prune-tags
"
_git_fetch ()
(
cd descriptive &&
git fetch o 2>actual &&
- grep " -> refs/crazyheads/descriptive-branch$" actual |
- test_i18ngrep "new branch" &&
- grep " -> descriptive-tag$" actual |
- test_i18ngrep "new tag" &&
- grep " -> crazy$" actual |
- test_i18ngrep "new ref"
+ test_i18ngrep "new branch.* -> refs/crazyheads/descriptive-branch$" actual &&
+ test_i18ngrep "new tag.* -> descriptive-tag$" actual &&
+ test_i18ngrep "new ref.* -> crazy$" actual
) &&
git checkout master
'
set_config_tristate () {
# var=$1 val=$2
case "$2" in
- unset) test_unconfig "$1" ;;
- *) git config "$1" "$2" ;;
+ unset)
+ test_unconfig "$1"
+ ;;
+ *)
+ git config "$1" "$2"
+ key=$(echo $1 | sed -e 's/^remote\.origin/fetch/')
+ git_fetch_c="$git_fetch_c -c $key=$2"
+ ;;
esac
}
test_configured_prune () {
- fetch_prune=$1 remote_origin_prune=$2 cmdline=$3 expected=$4
+ test_configured_prune_type "$@" "name"
+ test_configured_prune_type "$@" "link"
+ }
- test_expect_success "prune fetch.prune=$1 remote.origin.prune=$2${3:+ $3}; $4" '
+ test_configured_prune_type () {
+ fetch_prune=$1
+ remote_origin_prune=$2
+ fetch_prune_tags=$3
+ remote_origin_prune_tags=$4
+ expected_branch=$5
+ expected_tag=$6
+ cmdline=$7
+ mode=$8
+
+ if test -z "$cmdline_setup"
+ then
+ test_expect_success 'setup cmdline_setup variable for subsequent test' '
+ remote_url="file://$(git -C one config remote.origin.url)" &&
+ remote_fetch="$(git -C one config remote.origin.fetch)" &&
+ cmdline_setup="\"$remote_url\" \"$remote_fetch\""
+ '
+ fi
+
+ if test "$mode" = 'link'
+ then
+ new_cmdline=""
+
+ if test "$cmdline" = ""
+ then
+ new_cmdline=$cmdline_setup
+ else
+ new_cmdline=$(printf "%s" "$cmdline" | perl -pe 's[origin(?!/)]["'"$remote_url"'"]g')
+ fi
+
+ if test "$fetch_prune_tags" = 'true' ||
+ test "$remote_origin_prune_tags" = 'true'
+ then
+ if ! printf '%s' "$cmdline\n" | grep -q refs/remotes/origin/
+ then
+ new_cmdline="$new_cmdline refs/tags/*:refs/tags/*"
+ fi
+ fi
+
+ cmdline="$new_cmdline"
+ fi
+
+ test_expect_success "$mode prune fetch.prune=$1 remote.origin.prune=$2 fetch.pruneTags=$3 remote.origin.pruneTags=$4${7:+ $7}; branch:$5 tag:$6" '
# make sure a newbranch is there in . and also in one
git branch -f newbranch &&
+ git tag -f newtag &&
(
cd one &&
test_unconfig fetch.prune &&
+ test_unconfig fetch.pruneTags &&
test_unconfig remote.origin.prune &&
- git fetch &&
- git rev-parse --verify refs/remotes/origin/newbranch
+ test_unconfig remote.origin.pruneTags &&
+ git fetch '"$cmdline_setup"' &&
+ git rev-parse --verify refs/remotes/origin/newbranch &&
+ git rev-parse --verify refs/tags/newtag
) &&
# now remove it
git branch -d newbranch &&
+ git tag -d newtag &&
# then test
(
cd one &&
+ git_fetch_c="" &&
set_config_tristate fetch.prune $fetch_prune &&
+ set_config_tristate fetch.pruneTags $fetch_prune_tags &&
set_config_tristate remote.origin.prune $remote_origin_prune &&
-
- git fetch $cmdline &&
- case "$expected" in
+ set_config_tristate remote.origin.pruneTags $remote_origin_prune_tags &&
+
+ if test "$mode" != "link"
+ then
+ git_fetch_c=""
+ fi &&
+ git$git_fetch_c fetch '"$cmdline"' &&
+ case "$expected_branch" in
pruned)
test_must_fail git rev-parse --verify refs/remotes/origin/newbranch
;;
kept)
git rev-parse --verify refs/remotes/origin/newbranch
;;
+ esac &&
+ case "$expected_tag" in
+ pruned)
+ test_must_fail git rev-parse --verify refs/tags/newtag
+ ;;
+ kept)
+ git rev-parse --verify refs/tags/newtag
+ ;;
esac
)
'
}
- test_configured_prune unset unset "" kept
- test_configured_prune unset unset "--no-prune" kept
- test_configured_prune unset unset "--prune" pruned
-
- test_configured_prune false unset "" kept
- test_configured_prune false unset "--no-prune" kept
- test_configured_prune false unset "--prune" pruned
-
- test_configured_prune true unset "" pruned
- test_configured_prune true unset "--prune" pruned
- test_configured_prune true unset "--no-prune" kept
-
- test_configured_prune unset false "" kept
- test_configured_prune unset false "--no-prune" kept
- test_configured_prune unset false "--prune" pruned
-
- test_configured_prune false false "" kept
- test_configured_prune false false "--no-prune" kept
- test_configured_prune false false "--prune" pruned
-
- test_configured_prune true false "" kept
- test_configured_prune true false "--prune" pruned
- test_configured_prune true false "--no-prune" kept
-
- test_configured_prune unset true "" pruned
- test_configured_prune unset true "--no-prune" kept
- test_configured_prune unset true "--prune" pruned
-
- test_configured_prune false true "" pruned
- test_configured_prune false true "--no-prune" kept
- test_configured_prune false true "--prune" pruned
-
- test_configured_prune true true "" pruned
- test_configured_prune true true "--prune" pruned
- test_configured_prune true true "--no-prune" kept
+ # $1 config: fetch.prune
+ # $2 config: remote.<name>.prune
+ # $3 config: fetch.pruneTags
+ # $4 config: remote.<name>.pruneTags
+ # $5 expect: branch to be pruned?
+ # $6 expect: tag to be pruned?
+ # $7 git-fetch $cmdline:
+ #
+ # $1 $2 $3 $4 $5 $6 $7
+ test_configured_prune unset unset unset unset kept kept ""
+ test_configured_prune unset unset unset unset kept kept "--no-prune"
+ test_configured_prune unset unset unset unset pruned kept "--prune"
+ test_configured_prune unset unset unset unset kept pruned \
+ "--prune origin refs/tags/*:refs/tags/*"
+ test_configured_prune unset unset unset unset pruned pruned \
+ "--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*"
+
+ test_configured_prune false unset unset unset kept kept ""
+ test_configured_prune false unset unset unset kept kept "--no-prune"
+ test_configured_prune false unset unset unset pruned kept "--prune"
+
+ test_configured_prune true unset unset unset pruned kept ""
+ test_configured_prune true unset unset unset pruned kept "--prune"
+ test_configured_prune true unset unset unset kept kept "--no-prune"
+
+ test_configured_prune unset false unset unset kept kept ""
+ test_configured_prune unset false unset unset kept kept "--no-prune"
+ test_configured_prune unset false unset unset pruned kept "--prune"
+
+ test_configured_prune false false unset unset kept kept ""
+ test_configured_prune false false unset unset kept kept "--no-prune"
+ test_configured_prune false false unset unset pruned kept "--prune"
+ test_configured_prune false false unset unset kept pruned \
+ "--prune origin refs/tags/*:refs/tags/*"
+ test_configured_prune false false unset unset pruned pruned \
+ "--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*"
+
+ test_configured_prune true false unset unset kept kept ""
+ test_configured_prune true false unset unset pruned kept "--prune"
+ test_configured_prune true false unset unset kept kept "--no-prune"
+
+ test_configured_prune unset true unset unset pruned kept ""
+ test_configured_prune unset true unset unset kept kept "--no-prune"
+ test_configured_prune unset true unset unset pruned kept "--prune"
+
+ test_configured_prune false true unset unset pruned kept ""
+ test_configured_prune false true unset unset kept kept "--no-prune"
+ test_configured_prune false true unset unset pruned kept "--prune"
+
+ test_configured_prune true true unset unset pruned kept ""
+ test_configured_prune true true unset unset pruned kept "--prune"
+ test_configured_prune true true unset unset kept kept "--no-prune"
+ test_configured_prune true true unset unset kept pruned \
+ "--prune origin refs/tags/*:refs/tags/*"
+ test_configured_prune true true unset unset pruned pruned \
+ "--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*"
+
+ # --prune-tags on its own does nothing, needs --prune as well, same
+ # for for fetch.pruneTags without fetch.prune
+ test_configured_prune unset unset unset unset kept kept "--prune-tags"
+ test_configured_prune unset unset true unset kept kept ""
+ test_configured_prune unset unset unset true kept kept ""
+
+ # These will prune the tags
+ test_configured_prune unset unset unset unset pruned pruned "--prune --prune-tags"
+ test_configured_prune true unset true unset pruned pruned ""
+ test_configured_prune unset true unset true pruned pruned ""
+
+ # remote.<name>.pruneTags overrides fetch.pruneTags, just like
+ # remote.<name>.prune overrides fetch.prune if set.
+ test_configured_prune true unset true unset pruned pruned ""
+ test_configured_prune false true false true pruned pruned ""
+ test_configured_prune true false true false kept kept ""
+
+ # When --prune-tags is supplied it's ignored if an explicit refspec is
+ # given, same for the configuration options.
+ test_configured_prune unset unset unset unset pruned kept \
+ "--prune --prune-tags origin +refs/heads/*:refs/remotes/origin/*"
+ test_configured_prune unset unset true unset pruned kept \
+ "--prune origin +refs/heads/*:refs/remotes/origin/*"
+ test_configured_prune unset unset unset true pruned kept \
+ "--prune origin +refs/heads/*:refs/remotes/origin/*"
+
+ # Pruning that also takes place if a file:// url replaces a named
+ # remote. However, because there's no implicit
+ # +refs/heads/*:refs/remotes/origin/* refspec and supplying it on the
+ # command-line negates --prune-tags, the branches will not be pruned.
+ test_configured_prune_type unset unset unset unset kept kept "origin --prune-tags" "name"
+ test_configured_prune_type unset unset unset unset kept kept "origin --prune-tags" "link"
+ test_configured_prune_type unset unset unset unset pruned pruned "origin --prune --prune-tags" "name"
+ test_configured_prune_type unset unset unset unset kept pruned "origin --prune --prune-tags" "link"
+ test_configured_prune_type unset unset unset unset pruned pruned "--prune --prune-tags origin" "name"
+ test_configured_prune_type unset unset unset unset kept pruned "--prune --prune-tags origin" "link"
+ test_configured_prune_type unset unset true unset pruned pruned "--prune origin" "name"
+ test_configured_prune_type unset unset true unset kept pruned "--prune origin" "link"
+ test_configured_prune_type unset unset unset true pruned pruned "--prune origin" "name"
+ test_configured_prune_type unset unset unset true kept pruned "--prune origin" "link"
+ test_configured_prune_type true unset true unset pruned pruned "origin" "name"
+ test_configured_prune_type true unset true unset kept pruned "origin" "link"
+ test_configured_prune_type unset true true unset pruned pruned "origin" "name"
+ test_configured_prune_type unset true true unset kept pruned "origin" "link"
+ test_configured_prune_type unset true unset true pruned pruned "origin" "name"
+ test_configured_prune_type unset true unset true kept pruned "origin" "link"
+
+ # When all remote.origin.fetch settings are deleted a --prune
+ # --prune-tags still implicitly supplies refs/tags/*:refs/tags/* so
+ # tags, but not tracking branches, will be deleted.
+ test_expect_success 'remove remote.origin.fetch "one"' '
+ (
+ cd one &&
+ git config --unset-all remote.origin.fetch
+ )
+ '
+ test_configured_prune_type unset unset unset unset kept pruned "origin --prune --prune-tags" "name"
+ test_configured_prune_type unset unset unset unset kept pruned "origin --prune --prune-tags" "link"
test_expect_success 'all boundary commits are excluded' '
test_commit base &&