Skip to content

Commit c1a7902

Browse files
committed
Merge branch 'ab/fetch-prune'
Clarify how configured fetch refspecs interact with the "--prune" option of "git fetch", and also add a handy short-hand for getting rid of stale tags that are locally held. * ab/fetch-prune: fetch: make the --prune-tags work with <url> fetch: add a --prune-tags option and fetch.pruneTags config fetch tests: add scaffolding for the new fetch.pruneTags git-fetch & config doc: link to the new PRUNING section git remote doc: correct dangerous lies about what prune does git fetch doc: add a new section to explain the ins & outs of pruning fetch tests: fetch <url> <spec> as well as fetch [<remote>] fetch tests: expand case/esac for later change fetch tests: double quote a variable for interpolation fetch tests: test --prune and refspec interaction fetch tests: add a tag to be deleted to the pruning tests fetch tests: re-arrange arguments for future readability fetch tests: refactor in preparation for testing tag pruning remote: add a macro for "refs/tags/*:refs/tags/*" fetch: stop accessing "remote" variable indirectly fetch: trivially refactor assignment to ref_nr fetch: don't redundantly NULL something calloc() gave us
2 parents a4ae2e5 + 6317972 commit c1a7902

File tree

9 files changed

+392
-60
lines changed

9 files changed

+392
-60
lines changed

Documentation/config.txt

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1398,7 +1398,16 @@ fetch.unpackLimit::
13981398

13991399
fetch.prune::
14001400
If true, fetch will automatically behave as if the `--prune`
1401-
option was given on the command line. See also `remote.<name>.prune`.
1401+
option was given on the command line. See also `remote.<name>.prune`
1402+
and the PRUNING section of linkgit:git-fetch[1].
1403+
1404+
fetch.pruneTags::
1405+
If true, fetch will automatically behave as if the
1406+
`refs/tags/*:refs/tags/*` refspec was provided when pruning,
1407+
if not set already. This allows for setting both this option
1408+
and `fetch.prune` to maintain a 1=1 mapping to upstream
1409+
refs. See also `remote.<name>.pruneTags` and the PRUNING
1410+
section of linkgit:git-fetch[1].
14021411

14031412
fetch.output::
14041413
Control how ref update status is printed. Valid values are
@@ -2945,6 +2954,15 @@ remote.<name>.prune::
29452954
remote (as if the `--prune` option was given on the command line).
29462955
Overrides `fetch.prune` settings, if any.
29472956

2957+
remote.<name>.pruneTags::
2958+
When set to true, fetching from this remote by default will also
2959+
remove any local tags that no longer exist on the remote if pruning
2960+
is activated in general via `remote.<name>.prune`, `fetch.prune` or
2961+
`--prune`. Overrides `fetch.pruneTags` settings, if any.
2962+
+
2963+
See also `remote.<name>.prune` and the PRUNING section of
2964+
linkgit:git-fetch[1].
2965+
29482966
remotes.<group>::
29492967
The list of remotes which are fetched by "git remote update
29502968
<group>". See linkgit:git-remote[1].

Documentation/fetch-options.txt

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,22 @@ ifndef::git-pull[]
7373
are fetched due to an explicit refspec (either on the command
7474
line or in the remote configuration, for example if the remote
7575
was cloned with the --mirror option), then they are also
76-
subject to pruning.
76+
subject to pruning. Supplying `--prune-tags` is a shorthand for
77+
providing the tag refspec.
78+
+
79+
See the PRUNING section below for more details.
80+
81+
-P::
82+
--prune-tags::
83+
Before fetching, remove any local tags that no longer exist on
84+
the remote if `--prune` is enabled. This option should be used
85+
more carefully, unlike `--prune` it will remove any local
86+
references (local tags) that have been created. This option is
87+
a shorthand for providing the explicit tag refspec along with
88+
`--prune`, see the discussion about that in its documentation.
89+
+
90+
See the PRUNING section below for more details.
91+
7792
endif::git-pull[]
7893

7994
ifndef::git-pull[]

Documentation/git-fetch.txt

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,93 @@ The latter use of the `remote.<repository>.fetch` values can be
9999
overridden by giving the `--refmap=<refspec>` parameter(s) on the
100100
command line.
101101

102+
PRUNING
103+
-------
104+
105+
Git has a default disposition of keeping data unless it's explicitly
106+
thrown away; this extends to holding onto local references to branches
107+
on remotes that have themselves deleted those branches.
108+
109+
If left to accumulate, these stale references might make performance
110+
worse on big and busy repos that have a lot of branch churn, and
111+
e.g. make the output of commands like `git branch -a --contains
112+
<commit>` needlessly verbose, as well as impacting anything else
113+
that'll work with the complete set of known references.
114+
115+
These remote-tracking references can be deleted as a one-off with
116+
either of:
117+
118+
------------------------------------------------
119+
# While fetching
120+
$ git fetch --prune <name>
121+
122+
# Only prune, don't fetch
123+
$ git remote prune <name>
124+
------------------------------------------------
125+
126+
To prune references as part of your normal workflow without needing to
127+
remember to run that, set `fetch.prune` globally, or
128+
`remote.<name>.prune` per-remote in the config. See
129+
linkgit:git-config[1].
130+
131+
Here's where things get tricky and more specific. The pruning feature
132+
doesn't actually care about branches, instead it'll prune local <->
133+
remote-references as a function of the refspec of the remote (see
134+
`<refspec>` and <<CRTB,CONFIGURED REMOTE-TRACKING BRANCHES>> above).
135+
136+
Therefore if the refspec for the remote includes
137+
e.g. `refs/tags/*:refs/tags/*`, or you manually run e.g. `git fetch
138+
--prune <name> "refs/tags/*:refs/tags/*"` it won't be stale remote
139+
tracking branches that are deleted, but any local tag that doesn't
140+
exist on the remote.
141+
142+
This might not be what you expect, i.e. you want to prune remote
143+
`<name>`, but also explicitly fetch tags from it, so when you fetch
144+
from it you delete all your local tags, most of which may not have
145+
come from the `<name>` remote in the first place.
146+
147+
So be careful when using this with a refspec like
148+
`refs/tags/*:refs/tags/*`, or any other refspec which might map
149+
references from multiple remotes to the same local namespace.
150+
151+
Since keeping up-to-date with both branches and tags on the remote is
152+
a common use-case the `--prune-tags` option can be supplied along with
153+
`--prune` to prune local tags that don't exist on the remote, and
154+
force-update those tags that differ. Tag pruning can also be enabled
155+
with `fetch.pruneTags` or `remote.<name>.pruneTags` in the config. See
156+
linkgit:git-config[1].
157+
158+
The `--prune-tags` option is equivalent to having
159+
`refs/tags/*:refs/tags/*` declared in the refspecs of the remote. This
160+
can lead to some seemingly strange interactions:
161+
162+
------------------------------------------------
163+
# These both fetch tags
164+
$ git fetch --no-tags origin 'refs/tags/*:refs/tags/*'
165+
$ git fetch --no-tags --prune-tags origin
166+
------------------------------------------------
167+
168+
The reason it doesn't error out when provided without `--prune` or its
169+
config versions is for flexibility of the configured versions, and to
170+
maintain a 1=1 mapping between what the command line flags do, and
171+
what the configuration versions do.
172+
173+
It's reasonable to e.g. configure `fetch.pruneTags=true` in
174+
`~/.gitconfig` to have tags pruned whenever `git fetch --prune` is
175+
run, without making every invocation of `git fetch` without `--prune`
176+
an error.
177+
178+
Pruning tags with `--prune-tags` also works when fetching a URL
179+
instead of a named remote. These will all prune tags not found on
180+
origin:
181+
182+
------------------------------------------------
183+
$ git fetch origin --prune --prune-tags
184+
$ git fetch origin --prune 'refs/tags/*:refs/tags/*'
185+
$ git fetch <url of origin> --prune --prune-tags
186+
$ git fetch <url of origin> --prune 'refs/tags/*:refs/tags/*'
187+
------------------------------------------------
188+
102189
OUTPUT
103190
------
104191

Documentation/git-remote.txt

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -172,10 +172,14 @@ With `-n` option, the remote heads are not queried first with
172172

173173
'prune'::
174174

175-
Deletes all stale remote-tracking branches under <name>.
176-
These stale branches have already been removed from the remote repository
177-
referenced by <name>, but are still locally available in
178-
"remotes/<name>".
175+
Deletes stale references associated with <name>. By default, stale
176+
remote-tracking branches under <name> are deleted, but depending on
177+
global configuration and the configuration of the remote we might even
178+
prune local tags that haven't been pushed there. Equivalent to `git
179+
fetch --prune <name>`, except that no new references will be fetched.
180+
+
181+
See the PRUNING section of linkgit:git-fetch[1] for what it'll prune
182+
depending on various configuration.
179183
+
180184
With `--dry-run` option, report what branches will be pruned, but do not
181185
actually prune them.
@@ -189,7 +193,7 @@ remotes.default is not defined, all remotes which do not have the
189193
configuration parameter remote.<name>.skipDefaultUpdate set to true will
190194
be updated. (See linkgit:git-config[1]).
191195
+
192-
With `--prune` option, prune all the remotes that are updated.
196+
With `--prune` option, run pruning against all the remotes that are updated.
193197

194198

195199
DISCUSSION

builtin/fetch.c

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ static int fetch_prune_config = -1; /* unspecified */
3939
static int prune = -1; /* unspecified */
4040
#define PRUNE_BY_DEFAULT 0 /* do we prune by default? */
4141

42+
static int fetch_prune_tags_config = -1; /* unspecified */
43+
static int prune_tags = -1; /* unspecified */
44+
#define PRUNE_TAGS_BY_DEFAULT 0 /* do we prune tags by default? */
45+
4246
static int all, append, dry_run, force, keep, multiple, update_head_ok, verbosity, deepen_relative;
4347
static int progress = -1;
4448
static int tags = TAGS_DEFAULT, unshallow, update_shallow, deepen;
@@ -66,6 +70,11 @@ static int git_fetch_config(const char *k, const char *v, void *cb)
6670
return 0;
6771
}
6872

73+
if (!strcmp(k, "fetch.prunetags")) {
74+
fetch_prune_tags_config = git_config_bool(k, v);
75+
return 0;
76+
}
77+
6978
if (!strcmp(k, "submodule.recurse")) {
7079
int r = git_config_bool(k, v) ?
7180
RECURSE_SUBMODULES_ON : RECURSE_SUBMODULES_OFF;
@@ -128,6 +137,8 @@ static struct option builtin_fetch_options[] = {
128137
N_("number of submodules fetched in parallel")),
129138
OPT_BOOL('p', "prune", &prune,
130139
N_("prune remote-tracking branches no longer on remote")),
140+
OPT_BOOL('P', "prune-tags", &prune_tags,
141+
N_("prune local tags no longer on remote and clobber changed tags")),
131142
{ OPTION_CALLBACK, 0, "recurse-submodules", &recurse_submodules, N_("on-demand"),
132143
N_("control recursive fetching of submodules"),
133144
PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules },
@@ -1220,6 +1231,8 @@ static void add_options_to_argv(struct argv_array *argv)
12201231
argv_array_push(argv, "--dry-run");
12211232
if (prune != -1)
12221233
argv_array_push(argv, prune ? "--prune" : "--no-prune");
1234+
if (prune_tags != -1)
1235+
argv_array_push(argv, prune_tags ? "--prune-tags" : "--no-prune-tags");
12231236
if (update_head_ok)
12241237
argv_array_push(argv, "--update-head-ok");
12251238
if (force)
@@ -1323,12 +1336,15 @@ static inline void fetch_one_setup_partial(struct remote *remote)
13231336
return;
13241337
}
13251338

1326-
static int fetch_one(struct remote *remote, int argc, const char **argv)
1339+
static int fetch_one(struct remote *remote, int argc, const char **argv, int prune_tags_ok)
13271340
{
13281341
static const char **refs = NULL;
13291342
struct refspec *refspec;
13301343
int ref_nr = 0;
1344+
int j = 0;
13311345
int exit_code;
1346+
int maybe_prune_tags;
1347+
int remote_via_config = remote_is_configured(remote, 0);
13321348

13331349
if (!remote)
13341350
die(_("No remote repository specified. Please, specify either a URL or a\n"
@@ -1338,18 +1354,39 @@ static int fetch_one(struct remote *remote, int argc, const char **argv)
13381354

13391355
if (prune < 0) {
13401356
/* no command line request */
1341-
if (0 <= gtransport->remote->prune)
1342-
prune = gtransport->remote->prune;
1357+
if (0 <= remote->prune)
1358+
prune = remote->prune;
13431359
else if (0 <= fetch_prune_config)
13441360
prune = fetch_prune_config;
13451361
else
13461362
prune = PRUNE_BY_DEFAULT;
13471363
}
13481364

1365+
if (prune_tags < 0) {
1366+
/* no command line request */
1367+
if (0 <= remote->prune_tags)
1368+
prune_tags = remote->prune_tags;
1369+
else if (0 <= fetch_prune_tags_config)
1370+
prune_tags = fetch_prune_tags_config;
1371+
else
1372+
prune_tags = PRUNE_TAGS_BY_DEFAULT;
1373+
}
1374+
1375+
maybe_prune_tags = prune_tags_ok && prune_tags;
1376+
if (maybe_prune_tags && remote_via_config)
1377+
add_prune_tags_to_fetch_refspec(remote);
1378+
1379+
if (argc > 0 || (maybe_prune_tags && !remote_via_config)) {
1380+
size_t nr_alloc = st_add3(argc, maybe_prune_tags, 1);
1381+
refs = xcalloc(nr_alloc, sizeof(const char *));
1382+
if (maybe_prune_tags) {
1383+
refs[j++] = xstrdup("refs/tags/*:refs/tags/*");
1384+
ref_nr++;
1385+
}
1386+
}
1387+
13491388
if (argc > 0) {
1350-
int j = 0;
13511389
int i;
1352-
refs = xcalloc(st_add(argc, 1), sizeof(const char *));
13531390
for (i = 0; i < argc; i++) {
13541391
if (!strcmp(argv[i], "tag")) {
13551392
i++;
@@ -1359,9 +1396,8 @@ static int fetch_one(struct remote *remote, int argc, const char **argv)
13591396
argv[i], argv[i]);
13601397
} else
13611398
refs[j++] = argv[i];
1399+
ref_nr++;
13621400
}
1363-
refs[j] = NULL;
1364-
ref_nr = j;
13651401
}
13661402

13671403
sigchain_push_common(unlock_pack_on_signal);
@@ -1380,6 +1416,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
13801416
struct string_list list = STRING_LIST_INIT_DUP;
13811417
struct remote *remote = NULL;
13821418
int result = 0;
1419+
int prune_tags_ok = 1;
13831420
struct argv_array argv_gc_auto = ARGV_ARRAY_INIT;
13841421

13851422
packet_trace_identity("fetch");
@@ -1446,6 +1483,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
14461483
} else {
14471484
/* Zero or one remotes */
14481485
remote = remote_get(argv[0]);
1486+
prune_tags_ok = (argc == 1);
14491487
argc--;
14501488
argv++;
14511489
}
@@ -1454,7 +1492,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
14541492
if (remote) {
14551493
if (filter_options.choice || repository_format_partial_clone)
14561494
fetch_one_setup_partial(remote);
1457-
result = fetch_one(remote, argc, argv);
1495+
result = fetch_one(remote, argc, argv, prune_tags_ok);
14581496
} else {
14591497
if (filter_options.choice)
14601498
die(_("--filter can only be used with the remote configured in core.partialClone"));

contrib/completion/git-completion.bash

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1468,7 +1468,7 @@ __git_fetch_recurse_submodules="yes on-demand no"
14681468
__git_fetch_options="
14691469
--quiet --verbose --append --upload-pack --force --keep --depth=
14701470
--tags --no-tags --all --prune --dry-run --recurse-submodules=
1471-
--unshallow --update-shallow
1471+
--unshallow --update-shallow --prune-tags
14721472
"
14731473

14741474
_git_fetch ()

remote.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ static struct refspec s_tag_refspec = {
2222
"refs/tags/*"
2323
};
2424

25+
/* See TAG_REFSPEC for the string version */
2526
const struct refspec *tag_refspec = &s_tag_refspec;
2627

2728
struct counted_string {
@@ -103,6 +104,17 @@ static void add_fetch_refspec(struct remote *remote, const char *ref)
103104
remote->fetch_refspec[remote->fetch_refspec_nr++] = ref;
104105
}
105106

107+
void add_prune_tags_to_fetch_refspec(struct remote *remote)
108+
{
109+
int nr = remote->fetch_refspec_nr;
110+
int bufsize = nr + 1;
111+
int size = sizeof(struct refspec);
112+
113+
remote->fetch = xrealloc(remote->fetch, size * bufsize);
114+
memcpy(&remote->fetch[nr], tag_refspec, size);
115+
add_fetch_refspec(remote, xstrdup(TAG_REFSPEC));
116+
}
117+
106118
static void add_url(struct remote *remote, const char *url)
107119
{
108120
ALLOC_GROW(remote->url, remote->url_nr + 1, remote->url_alloc);
@@ -173,6 +185,7 @@ static struct remote *make_remote(const char *name, int len)
173185

174186
ret = xcalloc(1, sizeof(struct remote));
175187
ret->prune = -1; /* unspecified */
188+
ret->prune_tags = -1; /* unspecified */
176189
ALLOC_GROW(remotes, remotes_nr + 1, remotes_alloc);
177190
remotes[remotes_nr++] = ret;
178191
ret->name = xstrndup(name, len);
@@ -391,6 +404,8 @@ static int handle_config(const char *key, const char *value, void *cb)
391404
remote->skip_default_update = git_config_bool(key, value);
392405
else if (!strcmp(subkey, "prune"))
393406
remote->prune = git_config_bool(key, value);
407+
else if (!strcmp(subkey, "prunetags"))
408+
remote->prune_tags = git_config_bool(key, value);
394409
else if (!strcmp(subkey, "url")) {
395410
const char *v;
396411
if (git_config_string(&v, key, value))

remote.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ struct remote {
4747
int skip_default_update;
4848
int mirror;
4949
int prune;
50+
int prune_tags;
5051

5152
const char *receivepack;
5253
const char *uploadpack;
@@ -297,4 +298,8 @@ extern int parseopt_push_cas_option(const struct option *, const char *arg, int
297298
extern int is_empty_cas(const struct push_cas_option *);
298299
void apply_push_cas(struct push_cas_option *, struct remote *, struct ref *);
299300

301+
#define TAG_REFSPEC "refs/tags/*:refs/tags/*"
302+
303+
void add_prune_tags_to_fetch_refspec(struct remote *remote);
304+
300305
#endif

0 commit comments

Comments
 (0)