Merge branch 'bg/fetch-multi'
* bg/fetch-multi: Re-implement 'git remote update' using 'git fetch' builtin-fetch: add --dry-run option builtin-fetch: add --prune option teach warn_dangling_symref to take a FILE argument remote: refactor some logic into get_stale_heads() Add missing test for 'git remote update --prune' Add the configuration option skipFetchAll Teach the --multiple option to 'git fetch' Teach the --all option to 'git fetch'
This commit is contained in:
commit
65c042d44d
@ -1437,7 +1437,13 @@ remote.<name>.mirror::
|
|||||||
|
|
||||||
remote.<name>.skipDefaultUpdate::
|
remote.<name>.skipDefaultUpdate::
|
||||||
If true, this remote will be skipped by default when updating
|
If true, this remote will be skipped by default when updating
|
||||||
using the update subcommand of linkgit:git-remote[1].
|
using linkgit:git-fetch[1] or the `update` subcommand of
|
||||||
|
linkgit:git-remote[1].
|
||||||
|
|
||||||
|
remote.<name>.skipFetchAll::
|
||||||
|
If true, this remote will be skipped by default when updating
|
||||||
|
using linkgit:git-fetch[1] or the `update` subcommand of
|
||||||
|
linkgit:git-remote[1].
|
||||||
|
|
||||||
remote.<name>.receivepack::
|
remote.<name>.receivepack::
|
||||||
The default program to execute on the remote side when pushing. See
|
The default program to execute on the remote side when pushing. See
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
--all::
|
||||||
|
Fetch all remotes.
|
||||||
|
|
||||||
-a::
|
-a::
|
||||||
--append::
|
--append::
|
||||||
Append ref names and object names of fetched refs to the
|
Append ref names and object names of fetched refs to the
|
||||||
@ -9,6 +12,11 @@
|
|||||||
`git clone` with `--depth=<depth>` option (see linkgit:git-clone[1])
|
`git clone` with `--depth=<depth>` option (see linkgit:git-clone[1])
|
||||||
by the specified number of commits.
|
by the specified number of commits.
|
||||||
|
|
||||||
|
ifndef::git-pull[]
|
||||||
|
--dry-run::
|
||||||
|
Show what would be done, without making any changes.
|
||||||
|
endif::git-pull[]
|
||||||
|
|
||||||
-f::
|
-f::
|
||||||
--force::
|
--force::
|
||||||
When 'git-fetch' is used with `<rbranch>:<lbranch>`
|
When 'git-fetch' is used with `<rbranch>:<lbranch>`
|
||||||
@ -21,6 +29,16 @@
|
|||||||
--keep::
|
--keep::
|
||||||
Keep downloaded pack.
|
Keep downloaded pack.
|
||||||
|
|
||||||
|
ifndef::git-pull[]
|
||||||
|
--multiple::
|
||||||
|
Allow several <repository> and <group> arguments to be
|
||||||
|
specified. No <refspec>s may be specified.
|
||||||
|
|
||||||
|
--prune::
|
||||||
|
After fetching, remove any remote tracking branches which
|
||||||
|
no longer exist on the remote.
|
||||||
|
endif::git-pull[]
|
||||||
|
|
||||||
ifdef::git-pull[]
|
ifdef::git-pull[]
|
||||||
--no-tags::
|
--no-tags::
|
||||||
endif::git-pull[]
|
endif::git-pull[]
|
||||||
|
@ -10,11 +10,17 @@ SYNOPSIS
|
|||||||
--------
|
--------
|
||||||
'git fetch' <options> <repository> <refspec>...
|
'git fetch' <options> <repository> <refspec>...
|
||||||
|
|
||||||
|
'git fetch' <options> <group>
|
||||||
|
|
||||||
|
'git fetch' --multiple <options> [<repository> | <group>]...
|
||||||
|
|
||||||
|
'git fetch' --all <options>
|
||||||
|
|
||||||
|
|
||||||
DESCRIPTION
|
DESCRIPTION
|
||||||
-----------
|
-----------
|
||||||
Fetches named heads or tags from another repository, along with
|
Fetches named heads or tags from one or more other repositories,
|
||||||
the objects necessary to complete them.
|
along with the objects necessary to complete them.
|
||||||
|
|
||||||
The ref names and their object names of fetched refs are stored
|
The ref names and their object names of fetched refs are stored
|
||||||
in `.git/FETCH_HEAD`. This information is left for a later merge
|
in `.git/FETCH_HEAD`. This information is left for a later merge
|
||||||
@ -28,6 +34,10 @@ pointed by remote tags that it does not yet have, then fetch
|
|||||||
those missing tags. If the other end has tags that point at
|
those missing tags. If the other end has tags that point at
|
||||||
branches you are not interested in, you will not get them.
|
branches you are not interested in, you will not get them.
|
||||||
|
|
||||||
|
'git fetch' can fetch from either a single named repository, or
|
||||||
|
or from several repositories at once if <group> is given and
|
||||||
|
there is a remotes.<group> entry in the configuration file.
|
||||||
|
(See linkgit:git-config[1]).
|
||||||
|
|
||||||
OPTIONS
|
OPTIONS
|
||||||
-------
|
-------
|
||||||
|
@ -4,6 +4,13 @@
|
|||||||
(see the section <<URLS,GIT URLS>> below) or the name
|
(see the section <<URLS,GIT URLS>> below) or the name
|
||||||
of a remote (see the section <<REMOTES,REMOTES>> below).
|
of a remote (see the section <<REMOTES,REMOTES>> below).
|
||||||
|
|
||||||
|
ifndef::git-pull[]
|
||||||
|
<group>::
|
||||||
|
A name referring to a list of repositories as the value
|
||||||
|
of remotes.<group> in the configuration file.
|
||||||
|
(See linkgit:git-config[1]).
|
||||||
|
endif::git-pull[]
|
||||||
|
|
||||||
<refspec>::
|
<refspec>::
|
||||||
The format of a <refspec> parameter is an optional plus
|
The format of a <refspec> parameter is an optional plus
|
||||||
`{plus}`, followed by the source ref <src>, followed
|
`{plus}`, followed by the source ref <src>, followed
|
||||||
|
198
builtin-fetch.c
198
builtin-fetch.c
@ -14,6 +14,9 @@
|
|||||||
|
|
||||||
static const char * const builtin_fetch_usage[] = {
|
static const char * const builtin_fetch_usage[] = {
|
||||||
"git fetch [options] [<repository> <refspec>...]",
|
"git fetch [options] [<repository> <refspec>...]",
|
||||||
|
"git fetch [options] <group>",
|
||||||
|
"git fetch --multiple [options] [<repository> | <group>]...",
|
||||||
|
"git fetch --all [options]",
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -23,7 +26,7 @@ enum {
|
|||||||
TAGS_SET = 2
|
TAGS_SET = 2
|
||||||
};
|
};
|
||||||
|
|
||||||
static int append, force, keep, update_head_ok, verbosity;
|
static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity;
|
||||||
static int tags = TAGS_DEFAULT;
|
static int tags = TAGS_DEFAULT;
|
||||||
static const char *depth;
|
static const char *depth;
|
||||||
static const char *upload_pack;
|
static const char *upload_pack;
|
||||||
@ -32,16 +35,24 @@ static struct transport *transport;
|
|||||||
|
|
||||||
static struct option builtin_fetch_options[] = {
|
static struct option builtin_fetch_options[] = {
|
||||||
OPT__VERBOSITY(&verbosity),
|
OPT__VERBOSITY(&verbosity),
|
||||||
|
OPT_BOOLEAN(0, "all", &all,
|
||||||
|
"fetch from all remotes"),
|
||||||
OPT_BOOLEAN('a', "append", &append,
|
OPT_BOOLEAN('a', "append", &append,
|
||||||
"append to .git/FETCH_HEAD instead of overwriting"),
|
"append to .git/FETCH_HEAD instead of overwriting"),
|
||||||
OPT_STRING(0, "upload-pack", &upload_pack, "PATH",
|
OPT_STRING(0, "upload-pack", &upload_pack, "PATH",
|
||||||
"path to upload pack on remote end"),
|
"path to upload pack on remote end"),
|
||||||
OPT_BOOLEAN('f', "force", &force,
|
OPT_BOOLEAN('f', "force", &force,
|
||||||
"force overwrite of local branch"),
|
"force overwrite of local branch"),
|
||||||
|
OPT_BOOLEAN('m', "multiple", &multiple,
|
||||||
|
"fetch from multiple remotes"),
|
||||||
OPT_SET_INT('t', "tags", &tags,
|
OPT_SET_INT('t', "tags", &tags,
|
||||||
"fetch all tags and associated objects", TAGS_SET),
|
"fetch all tags and associated objects", TAGS_SET),
|
||||||
OPT_SET_INT('n', NULL, &tags,
|
OPT_SET_INT('n', NULL, &tags,
|
||||||
"do not fetch all tags (--no-tags)", TAGS_UNSET),
|
"do not fetch all tags (--no-tags)", TAGS_UNSET),
|
||||||
|
OPT_BOOLEAN('p', "prune", &prune,
|
||||||
|
"prune tracking branches no longer on remote"),
|
||||||
|
OPT_BOOLEAN(0, "dry-run", &dry_run,
|
||||||
|
"dry run"),
|
||||||
OPT_BOOLEAN('k', "keep", &keep, "keep downloaded pack"),
|
OPT_BOOLEAN('k', "keep", &keep, "keep downloaded pack"),
|
||||||
OPT_BOOLEAN('u', "update-head-ok", &update_head_ok,
|
OPT_BOOLEAN('u', "update-head-ok", &update_head_ok,
|
||||||
"allow updating of HEAD ref"),
|
"allow updating of HEAD ref"),
|
||||||
@ -178,6 +189,8 @@ static int s_update_ref(const char *action,
|
|||||||
char *rla = getenv("GIT_REFLOG_ACTION");
|
char *rla = getenv("GIT_REFLOG_ACTION");
|
||||||
static struct ref_lock *lock;
|
static struct ref_lock *lock;
|
||||||
|
|
||||||
|
if (dry_run)
|
||||||
|
return 0;
|
||||||
if (!rla)
|
if (!rla)
|
||||||
rla = default_rla.buf;
|
rla = default_rla.buf;
|
||||||
snprintf(msg, sizeof(msg), "%s: %s", rla, action);
|
snprintf(msg, sizeof(msg), "%s: %s", rla, action);
|
||||||
@ -303,7 +316,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
|
|||||||
char note[1024];
|
char note[1024];
|
||||||
const char *what, *kind;
|
const char *what, *kind;
|
||||||
struct ref *rm;
|
struct ref *rm;
|
||||||
char *url, *filename = git_path("FETCH_HEAD");
|
char *url, *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD");
|
||||||
|
|
||||||
fp = fopen(filename, "a");
|
fp = fopen(filename, "a");
|
||||||
if (!fp)
|
if (!fp)
|
||||||
@ -485,6 +498,28 @@ static int fetch_refs(struct transport *transport, struct ref *ref_map)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int prune_refs(struct transport *transport, struct ref *ref_map)
|
||||||
|
{
|
||||||
|
int result = 0;
|
||||||
|
struct ref *ref, *stale_refs = get_stale_heads(transport->remote, ref_map);
|
||||||
|
const char *dangling_msg = dry_run
|
||||||
|
? " (%s will become dangling)\n"
|
||||||
|
: " (%s has become dangling)\n";
|
||||||
|
|
||||||
|
for (ref = stale_refs; ref; ref = ref->next) {
|
||||||
|
if (!dry_run)
|
||||||
|
result |= delete_ref(ref->name, NULL, 0);
|
||||||
|
if (verbosity >= 0) {
|
||||||
|
fprintf(stderr, " x %-*s %-*s -> %s\n",
|
||||||
|
SUMMARY_WIDTH, "[deleted]",
|
||||||
|
REFCOL_WIDTH, "(none)", prettify_refname(ref->name));
|
||||||
|
warn_dangling_symref(stderr, dangling_msg, ref->name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free_refs(stale_refs);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
static int add_existing(const char *refname, const unsigned char *sha1,
|
static int add_existing(const char *refname, const unsigned char *sha1,
|
||||||
int flag, void *cbdata)
|
int flag, void *cbdata)
|
||||||
{
|
{
|
||||||
@ -633,7 +668,7 @@ static int do_fetch(struct transport *transport,
|
|||||||
die("Don't know how to fetch from %s", transport->url);
|
die("Don't know how to fetch from %s", transport->url);
|
||||||
|
|
||||||
/* if not appending, truncate FETCH_HEAD */
|
/* if not appending, truncate FETCH_HEAD */
|
||||||
if (!append) {
|
if (!append && !dry_run) {
|
||||||
char *filename = git_path("FETCH_HEAD");
|
char *filename = git_path("FETCH_HEAD");
|
||||||
FILE *fp = fopen(filename, "w");
|
FILE *fp = fopen(filename, "w");
|
||||||
if (!fp)
|
if (!fp)
|
||||||
@ -661,6 +696,8 @@ static int do_fetch(struct transport *transport,
|
|||||||
free_refs(ref_map);
|
free_refs(ref_map);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
if (prune)
|
||||||
|
prune_refs(transport, ref_map);
|
||||||
free_refs(ref_map);
|
free_refs(ref_map);
|
||||||
|
|
||||||
/* if neither --no-tags nor --tags was specified, do automated tag
|
/* if neither --no-tags nor --tags was specified, do automated tag
|
||||||
@ -691,27 +728,94 @@ static void set_option(const char *name, const char *value)
|
|||||||
name, transport->url);
|
name, transport->url);
|
||||||
}
|
}
|
||||||
|
|
||||||
int cmd_fetch(int argc, const char **argv, const char *prefix)
|
static int get_one_remote_for_fetch(struct remote *remote, void *priv)
|
||||||
|
{
|
||||||
|
struct string_list *list = priv;
|
||||||
|
if (!remote->skip_default_update)
|
||||||
|
string_list_append(remote->name, list);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct remote_group_data {
|
||||||
|
const char *name;
|
||||||
|
struct string_list *list;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int get_remote_group(const char *key, const char *value, void *priv)
|
||||||
|
{
|
||||||
|
struct remote_group_data *g = priv;
|
||||||
|
|
||||||
|
if (!prefixcmp(key, "remotes.") &&
|
||||||
|
!strcmp(key + 8, g->name)) {
|
||||||
|
/* split list by white space */
|
||||||
|
int space = strcspn(value, " \t\n");
|
||||||
|
while (*value) {
|
||||||
|
if (space > 1) {
|
||||||
|
string_list_append(xstrndup(value, space),
|
||||||
|
g->list);
|
||||||
|
}
|
||||||
|
value += space + (value[space] != '\0');
|
||||||
|
space = strcspn(value, " \t\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int add_remote_or_group(const char *name, struct string_list *list)
|
||||||
|
{
|
||||||
|
int prev_nr = list->nr;
|
||||||
|
struct remote_group_data g = { name, list };
|
||||||
|
|
||||||
|
git_config(get_remote_group, &g);
|
||||||
|
if (list->nr == prev_nr) {
|
||||||
|
struct remote *remote;
|
||||||
|
if (!remote_is_configured(name))
|
||||||
|
return 0;
|
||||||
|
remote = remote_get(name);
|
||||||
|
string_list_append(remote->name, list);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fetch_multiple(struct string_list *list)
|
||||||
|
{
|
||||||
|
int i, result = 0;
|
||||||
|
const char *argv[] = { "fetch", NULL, NULL, NULL, NULL, NULL, NULL };
|
||||||
|
int argc = 1;
|
||||||
|
|
||||||
|
if (dry_run)
|
||||||
|
argv[argc++] = "--dry-run";
|
||||||
|
if (prune)
|
||||||
|
argv[argc++] = "--prune";
|
||||||
|
if (verbosity >= 2)
|
||||||
|
argv[argc++] = "-v";
|
||||||
|
if (verbosity >= 1)
|
||||||
|
argv[argc++] = "-v";
|
||||||
|
else if (verbosity < 0)
|
||||||
|
argv[argc++] = "-q";
|
||||||
|
|
||||||
|
for (i = 0; i < list->nr; i++) {
|
||||||
|
const char *name = list->items[i].string;
|
||||||
|
argv[argc] = name;
|
||||||
|
if (verbosity >= 0)
|
||||||
|
printf("Fetching %s\n", name);
|
||||||
|
if (run_command_v_opt(argv, RUN_GIT_CMD)) {
|
||||||
|
error("Could not fetch %s", name);
|
||||||
|
result = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fetch_one(struct remote *remote, int argc, const char **argv)
|
||||||
{
|
{
|
||||||
struct remote *remote;
|
|
||||||
int i;
|
int i;
|
||||||
static const char **refs = NULL;
|
static const char **refs = NULL;
|
||||||
int ref_nr = 0;
|
int ref_nr = 0;
|
||||||
int exit_code;
|
int exit_code;
|
||||||
|
|
||||||
/* Record the command line for the reflog */
|
|
||||||
strbuf_addstr(&default_rla, "fetch");
|
|
||||||
for (i = 1; i < argc; i++)
|
|
||||||
strbuf_addf(&default_rla, " %s", argv[i]);
|
|
||||||
|
|
||||||
argc = parse_options(argc, argv, prefix,
|
|
||||||
builtin_fetch_options, builtin_fetch_usage, 0);
|
|
||||||
|
|
||||||
if (argc == 0)
|
|
||||||
remote = remote_get(NULL);
|
|
||||||
else
|
|
||||||
remote = remote_get(argv[0]);
|
|
||||||
|
|
||||||
if (!remote)
|
if (!remote)
|
||||||
die("Where do you want to fetch from today?");
|
die("Where do you want to fetch from today?");
|
||||||
|
|
||||||
@ -727,10 +831,10 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
|
|||||||
if (depth)
|
if (depth)
|
||||||
set_option(TRANS_OPT_DEPTH, depth);
|
set_option(TRANS_OPT_DEPTH, depth);
|
||||||
|
|
||||||
if (argc > 1) {
|
if (argc > 0) {
|
||||||
int j = 0;
|
int j = 0;
|
||||||
refs = xcalloc(argc + 1, sizeof(const char *));
|
refs = xcalloc(argc + 1, sizeof(const char *));
|
||||||
for (i = 1; i < argc; i++) {
|
for (i = 0; i < argc; i++) {
|
||||||
if (!strcmp(argv[i], "tag")) {
|
if (!strcmp(argv[i], "tag")) {
|
||||||
char *ref;
|
char *ref;
|
||||||
i++;
|
i++;
|
||||||
@ -757,3 +861,57 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
|
|||||||
transport = NULL;
|
transport = NULL;
|
||||||
return exit_code;
|
return exit_code;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int cmd_fetch(int argc, const char **argv, const char *prefix)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
struct string_list list = { NULL, 0, 0, 0 };
|
||||||
|
struct remote *remote;
|
||||||
|
int result = 0;
|
||||||
|
|
||||||
|
/* Record the command line for the reflog */
|
||||||
|
strbuf_addstr(&default_rla, "fetch");
|
||||||
|
for (i = 1; i < argc; i++)
|
||||||
|
strbuf_addf(&default_rla, " %s", argv[i]);
|
||||||
|
|
||||||
|
argc = parse_options(argc, argv, prefix,
|
||||||
|
builtin_fetch_options, builtin_fetch_usage, 0);
|
||||||
|
|
||||||
|
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);
|
||||||
|
} 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);
|
||||||
|
if (list.nr > 1) {
|
||||||
|
/* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* All names were strdup()ed or strndup()ed */
|
||||||
|
list.strdup_strings = 1;
|
||||||
|
string_list_clear(&list, 0);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
122
builtin-remote.c
122
builtin-remote.c
@ -261,32 +261,10 @@ struct ref_states {
|
|||||||
int queried;
|
int queried;
|
||||||
};
|
};
|
||||||
|
|
||||||
static int handle_one_branch(const char *refname,
|
|
||||||
const unsigned char *sha1, int flags, void *cb_data)
|
|
||||||
{
|
|
||||||
struct ref_states *states = cb_data;
|
|
||||||
struct refspec refspec;
|
|
||||||
|
|
||||||
memset(&refspec, 0, sizeof(refspec));
|
|
||||||
refspec.dst = (char *)refname;
|
|
||||||
if (!remote_find_tracking(states->remote, &refspec)) {
|
|
||||||
struct string_list_item *item;
|
|
||||||
const char *name = abbrev_branch(refspec.src);
|
|
||||||
/* symbolic refs pointing nowhere were handled already */
|
|
||||||
if ((flags & REF_ISSYMREF) ||
|
|
||||||
string_list_has_string(&states->tracked, name) ||
|
|
||||||
string_list_has_string(&states->new, name))
|
|
||||||
return 0;
|
|
||||||
item = string_list_append(name, &states->stale);
|
|
||||||
item->util = xstrdup(refname);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int get_ref_states(const struct ref *remote_refs, struct ref_states *states)
|
static int get_ref_states(const struct ref *remote_refs, struct ref_states *states)
|
||||||
{
|
{
|
||||||
struct ref *fetch_map = NULL, **tail = &fetch_map;
|
struct ref *fetch_map = NULL, **tail = &fetch_map;
|
||||||
struct ref *ref;
|
struct ref *ref, *stale_refs;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
for (i = 0; i < states->remote->fetch_refspec_nr; i++)
|
for (i = 0; i < states->remote->fetch_refspec_nr; i++)
|
||||||
@ -302,11 +280,17 @@ static int get_ref_states(const struct ref *remote_refs, struct ref_states *stat
|
|||||||
else
|
else
|
||||||
string_list_append(abbrev_branch(ref->name), &states->tracked);
|
string_list_append(abbrev_branch(ref->name), &states->tracked);
|
||||||
}
|
}
|
||||||
|
stale_refs = get_stale_heads(states->remote, fetch_map);
|
||||||
|
for (ref = stale_refs; ref; ref = ref->next) {
|
||||||
|
struct string_list_item *item =
|
||||||
|
string_list_append(abbrev_branch(ref->name), &states->stale);
|
||||||
|
item->util = xstrdup(ref->name);
|
||||||
|
}
|
||||||
|
free_refs(stale_refs);
|
||||||
free_refs(fetch_map);
|
free_refs(fetch_map);
|
||||||
|
|
||||||
sort_string_list(&states->new);
|
sort_string_list(&states->new);
|
||||||
sort_string_list(&states->tracked);
|
sort_string_list(&states->tracked);
|
||||||
for_each_ref(handle_one_branch, states);
|
|
||||||
sort_string_list(&states->stale);
|
sort_string_list(&states->stale);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@ -1213,94 +1197,62 @@ static int prune_remote(const char *remote, int dry_run)
|
|||||||
|
|
||||||
printf(" * [%s] %s\n", dry_run ? "would prune" : "pruned",
|
printf(" * [%s] %s\n", dry_run ? "would prune" : "pruned",
|
||||||
abbrev_ref(refname, "refs/remotes/"));
|
abbrev_ref(refname, "refs/remotes/"));
|
||||||
warn_dangling_symref(dangling_msg, refname);
|
warn_dangling_symref(stdout, dangling_msg, refname);
|
||||||
}
|
}
|
||||||
|
|
||||||
free_remote_ref_states(&states);
|
free_remote_ref_states(&states);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int get_one_remote_for_update(struct remote *remote, void *priv)
|
static int get_remote_default(const char *key, const char *value, void *priv)
|
||||||
{
|
{
|
||||||
struct string_list *list = priv;
|
if (strcmp(key, "remotes.default") == 0) {
|
||||||
if (!remote->skip_default_update)
|
int *found = priv;
|
||||||
string_list_append(remote->name, list);
|
*found = 1;
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct remote_group {
|
|
||||||
const char *name;
|
|
||||||
struct string_list *list;
|
|
||||||
} remote_group;
|
|
||||||
|
|
||||||
static int get_remote_group(const char *key, const char *value, void *num_hits)
|
|
||||||
{
|
|
||||||
if (!prefixcmp(key, "remotes.") &&
|
|
||||||
!strcmp(key + 8, remote_group.name)) {
|
|
||||||
/* split list by white space */
|
|
||||||
int space = strcspn(value, " \t\n");
|
|
||||||
while (*value) {
|
|
||||||
if (space > 1) {
|
|
||||||
string_list_append(xstrndup(value, space),
|
|
||||||
remote_group.list);
|
|
||||||
++*((int *)num_hits);
|
|
||||||
}
|
|
||||||
value += space + (value[space] != '\0');
|
|
||||||
space = strcspn(value, " \t\n");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int update(int argc, const char **argv)
|
static int update(int argc, const char **argv)
|
||||||
{
|
{
|
||||||
int i, result = 0, prune = 0;
|
int i, prune = 0;
|
||||||
struct string_list list = { NULL, 0, 0, 0 };
|
|
||||||
static const char *default_argv[] = { NULL, "default", NULL };
|
|
||||||
struct option options[] = {
|
struct option options[] = {
|
||||||
OPT_BOOLEAN('p', "prune", &prune,
|
OPT_BOOLEAN('p', "prune", &prune,
|
||||||
"prune remotes after fetching"),
|
"prune remotes after fetching"),
|
||||||
OPT_END()
|
OPT_END()
|
||||||
};
|
};
|
||||||
|
const char **fetch_argv;
|
||||||
|
int fetch_argc = 0;
|
||||||
|
int default_defined = 0;
|
||||||
|
|
||||||
|
fetch_argv = xmalloc(sizeof(char *) * (argc+5));
|
||||||
|
|
||||||
argc = parse_options(argc, argv, NULL, options, builtin_remote_update_usage,
|
argc = parse_options(argc, argv, NULL, options, builtin_remote_update_usage,
|
||||||
PARSE_OPT_KEEP_ARGV0);
|
PARSE_OPT_KEEP_ARGV0);
|
||||||
|
|
||||||
|
fetch_argv[fetch_argc++] = "fetch";
|
||||||
|
|
||||||
|
if (prune)
|
||||||
|
fetch_argv[fetch_argc++] = "--prune";
|
||||||
|
if (verbose)
|
||||||
|
fetch_argv[fetch_argc++] = "-v";
|
||||||
if (argc < 2) {
|
if (argc < 2) {
|
||||||
argc = 2;
|
fetch_argv[fetch_argc++] = "default";
|
||||||
argv = default_argv;
|
} else {
|
||||||
|
fetch_argv[fetch_argc++] = "--multiple";
|
||||||
|
for (i = 1; i < argc; i++)
|
||||||
|
fetch_argv[fetch_argc++] = argv[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
remote_group.list = &list;
|
if (strcmp(fetch_argv[fetch_argc-1], "default") == 0) {
|
||||||
for (i = 1; i < argc; i++) {
|
git_config(get_remote_default, &default_defined);
|
||||||
int groups_found = 0;
|
if (!default_defined)
|
||||||
remote_group.name = argv[i];
|
fetch_argv[fetch_argc-1] = "--all";
|
||||||
result = git_config(get_remote_group, &groups_found);
|
|
||||||
if (!groups_found && (i != 1 || strcmp(argv[1], "default"))) {
|
|
||||||
struct remote *remote;
|
|
||||||
if (!remote_is_configured(argv[i]))
|
|
||||||
die("No such remote or remote group: %s",
|
|
||||||
argv[i]);
|
|
||||||
remote = remote_get(argv[i]);
|
|
||||||
string_list_append(remote->name, remote_group.list);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!result && !list.nr && argc == 2 && !strcmp(argv[1], "default"))
|
fetch_argv[fetch_argc] = NULL;
|
||||||
result = for_each_remote(get_one_remote_for_update, &list);
|
|
||||||
|
|
||||||
for (i = 0; i < list.nr; i++) {
|
return run_command_v_opt(fetch_argv, RUN_GIT_CMD);
|
||||||
int err = fetch_remote(list.items[i].string);
|
|
||||||
result |= err;
|
|
||||||
if (!err && prune)
|
|
||||||
result |= prune_remote(list.items[i].string, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* all names were strdup()ed or strndup()ed */
|
|
||||||
list.strdup_strings = 1;
|
|
||||||
string_list_clear(&list, 0);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int get_one_entry(struct remote *remote, void *priv)
|
static int get_one_entry(struct remote *remote, void *priv)
|
||||||
|
7
refs.c
7
refs.c
@ -286,6 +286,7 @@ static struct ref_list *get_ref_dir(const char *base, struct ref_list *list)
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct warn_if_dangling_data {
|
struct warn_if_dangling_data {
|
||||||
|
FILE *fp;
|
||||||
const char *refname;
|
const char *refname;
|
||||||
const char *msg_fmt;
|
const char *msg_fmt;
|
||||||
};
|
};
|
||||||
@ -304,13 +305,13 @@ static int warn_if_dangling_symref(const char *refname, const unsigned char *sha
|
|||||||
if (!resolves_to || strcmp(resolves_to, d->refname))
|
if (!resolves_to || strcmp(resolves_to, d->refname))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
printf(d->msg_fmt, refname);
|
fprintf(d->fp, d->msg_fmt, refname);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void warn_dangling_symref(const char *msg_fmt, const char *refname)
|
void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname)
|
||||||
{
|
{
|
||||||
struct warn_if_dangling_data data = { refname, msg_fmt };
|
struct warn_if_dangling_data data = { fp, refname, msg_fmt };
|
||||||
for_each_rawref(warn_if_dangling_symref, &data);
|
for_each_rawref(warn_if_dangling_symref, &data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
2
refs.h
2
refs.h
@ -29,7 +29,7 @@ extern int for_each_replace_ref(each_ref_fn, void *);
|
|||||||
/* can be used to learn about broken ref and symref */
|
/* can be used to learn about broken ref and symref */
|
||||||
extern int for_each_rawref(each_ref_fn, void *);
|
extern int for_each_rawref(each_ref_fn, void *);
|
||||||
|
|
||||||
extern void warn_dangling_symref(const char *msg_fmt, const char *refname);
|
extern void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Extra refs will be listed by for_each_ref() before any actual refs
|
* Extra refs will be listed by for_each_ref() before any actual refs
|
||||||
|
42
remote.c
42
remote.c
@ -397,7 +397,8 @@ static int handle_config(const char *key, const char *value, void *cb)
|
|||||||
remote->mirror = git_config_bool(key, value);
|
remote->mirror = git_config_bool(key, value);
|
||||||
else if (!strcmp(subkey, ".skipdefaultupdate"))
|
else if (!strcmp(subkey, ".skipdefaultupdate"))
|
||||||
remote->skip_default_update = git_config_bool(key, value);
|
remote->skip_default_update = git_config_bool(key, value);
|
||||||
|
else if (!strcmp(subkey, ".skipfetchall"))
|
||||||
|
remote->skip_default_update = git_config_bool(key, value);
|
||||||
else if (!strcmp(subkey, ".url")) {
|
else if (!strcmp(subkey, ".url")) {
|
||||||
const char *v;
|
const char *v;
|
||||||
if (git_config_string(&v, key, value))
|
if (git_config_string(&v, key, value))
|
||||||
@ -1591,3 +1592,42 @@ struct ref *guess_remote_head(const struct ref *head,
|
|||||||
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct stale_heads_info {
|
||||||
|
struct remote *remote;
|
||||||
|
struct string_list *ref_names;
|
||||||
|
struct ref **stale_refs_tail;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int get_stale_heads_cb(const char *refname,
|
||||||
|
const unsigned char *sha1, int flags, void *cb_data)
|
||||||
|
{
|
||||||
|
struct stale_heads_info *info = cb_data;
|
||||||
|
struct refspec refspec;
|
||||||
|
memset(&refspec, 0, sizeof(refspec));
|
||||||
|
refspec.dst = (char *)refname;
|
||||||
|
if (!remote_find_tracking(info->remote, &refspec)) {
|
||||||
|
if (!((flags & REF_ISSYMREF) ||
|
||||||
|
string_list_has_string(info->ref_names, refspec.src))) {
|
||||||
|
struct ref *ref = make_linked_ref(refname, &info->stale_refs_tail);
|
||||||
|
hashcpy(ref->new_sha1, sha1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ref *get_stale_heads(struct remote *remote, struct ref *fetch_map)
|
||||||
|
{
|
||||||
|
struct ref *ref, *stale_refs = NULL;
|
||||||
|
struct string_list ref_names = { NULL, 0, 0, 0 };
|
||||||
|
struct stale_heads_info info;
|
||||||
|
info.remote = remote;
|
||||||
|
info.ref_names = &ref_names;
|
||||||
|
info.stale_refs_tail = &stale_refs;
|
||||||
|
for (ref = fetch_map; ref; ref = ref->next)
|
||||||
|
string_list_append(ref->name, &ref_names);
|
||||||
|
sort_string_list(&ref_names);
|
||||||
|
for_each_ref(get_stale_heads_cb, &info);
|
||||||
|
string_list_clear(&ref_names, 0);
|
||||||
|
return stale_refs;
|
||||||
|
}
|
||||||
|
3
remote.h
3
remote.h
@ -154,4 +154,7 @@ struct ref *guess_remote_head(const struct ref *head,
|
|||||||
const struct ref *refs,
|
const struct ref *refs,
|
||||||
int all);
|
int all);
|
||||||
|
|
||||||
|
/* Return refs which no longer exist on remote */
|
||||||
|
struct ref *get_stale_heads(struct remote *remote, struct ref *fetch_map);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -365,6 +365,17 @@ test_expect_success 'update with arguments' '
|
|||||||
|
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success 'update --prune' '
|
||||||
|
|
||||||
|
(cd one &&
|
||||||
|
git branch -m side2 side3) &&
|
||||||
|
(cd test &&
|
||||||
|
git remote update --prune &&
|
||||||
|
(cd ../one && git branch -m side3 side2)
|
||||||
|
git rev-parse refs/remotes/origin/side3 &&
|
||||||
|
test_must_fail git rev-parse refs/remotes/origin/side2)
|
||||||
|
'
|
||||||
|
|
||||||
cat > one/expect << EOF
|
cat > one/expect << EOF
|
||||||
apis/master
|
apis/master
|
||||||
apis/side
|
apis/side
|
||||||
|
@ -51,7 +51,7 @@ test_expect_success 'nonexistant group produces error' '
|
|||||||
! repo_fetched two
|
! repo_fetched two
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_success 'updating group updates all members' '
|
test_expect_success 'updating group updates all members (remote update)' '
|
||||||
mark group-all &&
|
mark group-all &&
|
||||||
update_repos &&
|
update_repos &&
|
||||||
git config --add remotes.all one &&
|
git config --add remotes.all one &&
|
||||||
@ -61,7 +61,15 @@ test_expect_success 'updating group updates all members' '
|
|||||||
repo_fetched two
|
repo_fetched two
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_success 'updating group does not update non-members' '
|
test_expect_success 'updating group updates all members (fetch)' '
|
||||||
|
mark fetch-group-all &&
|
||||||
|
update_repos &&
|
||||||
|
git fetch all &&
|
||||||
|
repo_fetched one &&
|
||||||
|
repo_fetched two
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'updating group does not update non-members (remote update)' '
|
||||||
mark group-some &&
|
mark group-some &&
|
||||||
update_repos &&
|
update_repos &&
|
||||||
git config --add remotes.some one &&
|
git config --add remotes.some one &&
|
||||||
@ -70,6 +78,15 @@ test_expect_success 'updating group does not update non-members' '
|
|||||||
! repo_fetched two
|
! repo_fetched two
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success 'updating group does not update non-members (fetch)' '
|
||||||
|
mark fetch-group-some &&
|
||||||
|
update_repos &&
|
||||||
|
git config --add remotes.some one &&
|
||||||
|
git remote update some &&
|
||||||
|
repo_fetched one &&
|
||||||
|
! repo_fetched two
|
||||||
|
'
|
||||||
|
|
||||||
test_expect_success 'updating remote name updates that remote' '
|
test_expect_success 'updating remote name updates that remote' '
|
||||||
mark remote-name &&
|
mark remote-name &&
|
||||||
update_repos &&
|
update_repos &&
|
||||||
|
154
t/t5514-fetch-multiple.sh
Executable file
154
t/t5514-fetch-multiple.sh
Executable file
@ -0,0 +1,154 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
test_description='fetch --all works correctly'
|
||||||
|
|
||||||
|
. ./test-lib.sh
|
||||||
|
|
||||||
|
setup_repository () {
|
||||||
|
mkdir "$1" && (
|
||||||
|
cd "$1" &&
|
||||||
|
git init &&
|
||||||
|
>file &&
|
||||||
|
git add file &&
|
||||||
|
test_tick &&
|
||||||
|
git commit -m "Initial" &&
|
||||||
|
git checkout -b side &&
|
||||||
|
>elif &&
|
||||||
|
git add elif &&
|
||||||
|
test_tick &&
|
||||||
|
git commit -m "Second" &&
|
||||||
|
git checkout master
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
test_expect_success setup '
|
||||||
|
setup_repository one &&
|
||||||
|
setup_repository two &&
|
||||||
|
(
|
||||||
|
cd two && git branch another
|
||||||
|
) &&
|
||||||
|
git clone --mirror two three
|
||||||
|
git clone one test
|
||||||
|
'
|
||||||
|
|
||||||
|
cat > test/expect << EOF
|
||||||
|
one/master
|
||||||
|
one/side
|
||||||
|
origin/HEAD -> origin/master
|
||||||
|
origin/master
|
||||||
|
origin/side
|
||||||
|
three/another
|
||||||
|
three/master
|
||||||
|
three/side
|
||||||
|
two/another
|
||||||
|
two/master
|
||||||
|
two/side
|
||||||
|
EOF
|
||||||
|
|
||||||
|
test_expect_success 'git fetch --all' '
|
||||||
|
(cd test &&
|
||||||
|
git remote add one ../one &&
|
||||||
|
git remote add two ../two &&
|
||||||
|
git remote add three ../three &&
|
||||||
|
git fetch --all &&
|
||||||
|
git branch -r > output &&
|
||||||
|
test_cmp expect output)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'git fetch --all should continue if a remote has errors' '
|
||||||
|
(git clone one test2 &&
|
||||||
|
cd test2 &&
|
||||||
|
git remote add bad ../non-existing &&
|
||||||
|
git remote add one ../one &&
|
||||||
|
git remote add two ../two &&
|
||||||
|
git remote add three ../three &&
|
||||||
|
test_must_fail git fetch --all &&
|
||||||
|
git branch -r > output &&
|
||||||
|
test_cmp ../test/expect output)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'git fetch --all does not allow non-option arguments' '
|
||||||
|
(cd test &&
|
||||||
|
test_must_fail git fetch --all origin &&
|
||||||
|
test_must_fail git fetch --all origin master)
|
||||||
|
'
|
||||||
|
|
||||||
|
cat > expect << EOF
|
||||||
|
origin/HEAD -> origin/master
|
||||||
|
origin/master
|
||||||
|
origin/side
|
||||||
|
three/another
|
||||||
|
three/master
|
||||||
|
three/side
|
||||||
|
EOF
|
||||||
|
|
||||||
|
test_expect_success 'git fetch --multiple (but only one remote)' '
|
||||||
|
(git clone one test3 &&
|
||||||
|
cd test3 &&
|
||||||
|
git remote add three ../three &&
|
||||||
|
git fetch --multiple three &&
|
||||||
|
git branch -r > output &&
|
||||||
|
test_cmp ../expect output)
|
||||||
|
'
|
||||||
|
|
||||||
|
cat > expect << EOF
|
||||||
|
one/master
|
||||||
|
one/side
|
||||||
|
two/another
|
||||||
|
two/master
|
||||||
|
two/side
|
||||||
|
EOF
|
||||||
|
|
||||||
|
test_expect_success 'git fetch --multiple (two remotes)' '
|
||||||
|
(git clone one test4 &&
|
||||||
|
cd test4 &&
|
||||||
|
git remote rm origin &&
|
||||||
|
git remote add one ../one &&
|
||||||
|
git remote add two ../two &&
|
||||||
|
git fetch --multiple one two &&
|
||||||
|
git branch -r > output &&
|
||||||
|
test_cmp ../expect output)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'git fetch --multiple (bad remote names)' '
|
||||||
|
(cd test4 &&
|
||||||
|
test_must_fail git fetch --multiple four)
|
||||||
|
'
|
||||||
|
|
||||||
|
|
||||||
|
test_expect_success 'git fetch --all (skipFetchAll)' '
|
||||||
|
(cd test4 &&
|
||||||
|
for b in $(git branch -r)
|
||||||
|
do
|
||||||
|
git branch -r -d $b || break
|
||||||
|
done &&
|
||||||
|
git remote add three ../three &&
|
||||||
|
git config remote.three.skipFetchAll true &&
|
||||||
|
git fetch --all &&
|
||||||
|
git branch -r > output &&
|
||||||
|
test_cmp ../expect output)
|
||||||
|
'
|
||||||
|
|
||||||
|
cat > expect << EOF
|
||||||
|
one/master
|
||||||
|
one/side
|
||||||
|
three/another
|
||||||
|
three/master
|
||||||
|
three/side
|
||||||
|
two/another
|
||||||
|
two/master
|
||||||
|
two/side
|
||||||
|
EOF
|
||||||
|
|
||||||
|
test_expect_success 'git fetch --multiple (ignoring skipFetchAll)' '
|
||||||
|
(cd test4 &&
|
||||||
|
for b in $(git branch -r)
|
||||||
|
do
|
||||||
|
git branch -r -d $b || break
|
||||||
|
done &&
|
||||||
|
git fetch --multiple one two three &&
|
||||||
|
git branch -r > output &&
|
||||||
|
test_cmp ../expect output)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_done
|
Loading…
Reference in New Issue
Block a user