core.fileMode::
If false, the executable bit differences between the index and
the working copy are ignored; useful on broken filesystems like FAT.
- See linkgit:git-update-index[1]. True by default.
+ See linkgit:git-update-index[1].
++
+The default is true, except linkgit:git-clone[1] or linkgit:git-init[1]
+will probe and set core.fileMode false if appropriate when the
+repository is created.
core.ignoreCygwinFSTricks::
This option is only used by Cygwin implementation of Git. If false,
is true, in which case ignoreCygwinFSTricks is ignored as Cygwin's
POSIX emulation is required to support core.filemode.
+core.ignorecase::
+ If true, this option enables various workarounds to enable
+ git to work better on filesystems that are not case sensitive,
+ like FAT. For example, if a directory listing finds
+ "makefile" when git expects "Makefile", git will assume
+ it is really the same file, and continue to remember it as
+ "Makefile".
++
+The default is false, except linkgit:git-clone[1] or linkgit:git-init[1]
+will probe and set core.ignorecase true if appropriate when the repository
+is created.
+
core.trustctime::
If false, the ctime differences between the index and the
working copy are ignored; useful when the inode change time
writing to the filesystem. The variable can be set to
'input', in which case the conversion happens only while
reading from the filesystem but files are written out with
- `LF` at the end of lines. Currently, which paths to consider
- "text" (i.e. be subjected to the autocrlf mechanism) is
- decided purely based on the contents.
+ `LF` at the end of lines. A file is considered
+ "text" (i.e. be subjected to the autocrlf mechanism) based on
+ the file's `crlf` attribute, or if `crlf` is unspecified,
+ based on the file's contents. See linkgit:gitattributes[5].
core.safecrlf::
If true, makes git check if converting `CRLF` as controlled by
contain the link text. linkgit:git-update-index[1] and
linkgit:git-add[1] will not change the recorded type to regular
file. Useful on filesystems like FAT that do not support
- symbolic links. True by default.
+ symbolic links.
++
+The default is true, except linkgit:git-clone[1] or linkgit:git-init[1]
+will probe and set core.symlinks false if appropriate when the repository
+is created.
core.gitProxy::
A "proxy command" to execute (as 'command host port') instead
core.excludesfile::
In addition to '.gitignore' (per-directory) and
'.git/info/exclude', git looks into this file for patterns
- of files which are not meant to be tracked. See
- linkgit:gitignore[5].
+ of files which are not meant to be tracked. "{tilde}/" is expanded
+ to the value of `$HOME` and "{tilde}user/" to the specified user's
+ home directory. See linkgit:gitignore[5].
core.editor::
Commands such as `commit` and `tag` that lets you edit
messages by launching an editor uses the value of this
variable when it is set, and the environment variable
- `GIT_EDITOR` is not set. The order of preference is
- `GIT_EDITOR` environment, `core.editor`, `VISUAL` and
- `EDITOR` environment variables and then finally `vi`.
+ `GIT_EDITOR` is not set. See linkgit:git-var[1].
core.pager::
The command that git will use to paginate output. Can
consider them as errors. You can prefix `-` to disable
any of them (e.g. `-trailing-space`):
+
-* `trailing-space` treats trailing whitespaces at the end of the line
+* `blank-at-eol` treats trailing whitespaces at the end of the line
as an error (enabled by default).
* `space-before-tab` treats a space character that appears immediately
before a tab character in the initial indent part of the line as an
error (enabled by default).
* `indent-with-non-tab` treats a line that is indented with 8 or more
space characters as an error (not enabled by default).
+* `blank-at-eof` treats blank lines added at the end of file as an error
+ (enabled by default).
+* `trailing-space` is a short-hand to cover both `blank-at-eol` and
+ `blank-at-eof`.
* `cr-at-eol` treats a carriage-return at the end of line as
part of the line terminator, i.e. with it, `trailing-space`
does not trigger if the character before such a carriage-return
Set this config setting to 'rename' there; However, This will remove the
check that makes sure that existing object files will not get overwritten.
+core.notesRef::
+ When showing commit messages, also show notes which are stored in
+ the given ref. This ref is expected to contain files named
+ after the full SHA-1 of the commit they annotate.
++
+If such a file exists in the given ref, the referenced blob is read, and
+appended to the commit message, separated by a "Notes:" line. If the
+given ref itself does not exist, it is not an error, but means that no
+notes should be printed.
++
+This setting defaults to "refs/notes/commits", and can be overridden by
+the `GIT_NOTES_REF` environment variable.
+
add.ignore-errors::
Tells 'git-add' to continue adding files when some files cannot be
added due to indexing errors. Equivalent to the '--ignore-errors'
commit.template::
Specify a file to use as the template for new commit messages.
+ "{tilde}/" is expanded to the value of `$HOME` and "{tilde}user/" to the
+ specified user's home directory.
diff.autorefreshindex::
When using 'git-diff' to compare with work tree
How many HTTP requests to launch in parallel. Can be overridden
by the 'GIT_HTTP_MAX_REQUESTS' environment variable. Default is 5.
+http.postBuffer::
+ Maximum size in bytes of the buffer used by smart HTTP
+ transports when POSTing data to the remote system.
+ For requests larger than this buffer size, HTTP/1.1 and
+ Transfer-Encoding: chunked is used to avoid creating a
+ massive pack file locally. Default is 1 MiB, which is
+ sufficient for most requests.
+
http.lowSpeedLimit, http.lowSpeedTime::
If the HTTP transfer speed is less than 'http.lowSpeedLimit'
for longer than 'http.lowSpeedTime' seconds, the transfer is aborted.
Whether to show a diffstat of what changed upstream since the last
rebase. False by default.
+receive.autogc::
+ By default, git-receive-pack will run "git-gc --auto" after
+ receiving data from git-push and updating refs. You can stop
+ it by setting this variable to false.
+
receive.fsckObjects::
If it is set to true, git-receive-pack will check all received
objects. It will abort in the case of a malformed object or a
receive.denyNonFastForwards::
If set to true, git-receive-pack will deny a ref update which is
- not a fast forward. Use this to prevent such an update via a push,
+ not a fast-forward. Use this to prevent such an update via a push,
even if that push is forced. This configuration variable is
set when initializing a shared repository.
+receive.updateserverinfo::
+ If set to true, git-receive-pack will run git-update-server-info
+ after receiving data from git-push and updating refs.
+
remote.<name>.url::
The URL of a remote repository. See linkgit:git-fetch[1] or
linkgit:git-push[1].
remote.<name>.skipDefaultUpdate::
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::
The default program to execute on the remote side when pushing. See
--------
'git fetch' <options> <repository> <refspec>...
+ 'git fetch' <options> <group>
+
+ 'git fetch' --multiple <options> [<repository> | <group>]...
+
+ 'git fetch' --all <options>
+
DESCRIPTION
-----------
- Fetches named heads or tags from another repository, along with
- the objects necessary to complete them.
+ Fetches named heads or tags from one or more other repositories,
+ along with the objects necessary to complete them.
The ref names and their object names of fetched refs are stored
in `.git/FETCH_HEAD`. This information is left for a later merge
those missing tags. If the other end has tags that point at
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
-------
include::urls-remotes.txt[]
+
+EXAMPLES
+--------
+
+* Update the remote-tracking branches:
++
+------------------------------------------------
+$ git fetch origin
+------------------------------------------------
++
+The above command copies all branches from the remote refs/heads/
+namespace and stores them to the local refs/remotes/origin/ namespace,
+unless the branch.<name>.fetch option is used to specify a non-default
+refspec.
+
+* Using refspecs explicitly:
++
+------------------------------------------------
+$ git fetch origin +pu:pu maint:tmp
+------------------------------------------------
++
+This updates (or creates, as necessary) branches `pu` and `tmp` in
+the local repository by fetching from the branches (respectively)
+`pu` and `maint` from the remote repository.
++
+The `pu` branch will be updated even if it is does not fast-forward,
+because it is prefixed with a plus sign; `tmp` will not be.
+
+
SEE ALSO
--------
linkgit:git-pull[1]
(see the section <<URLS,GIT URLS>> below) or the name
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>::
The format of a <refspec> parameter is an optional plus
`{plus}`, followed by the source ref <src>, followed
+
The remote ref that matches <src>
is fetched, and if <dst> is not empty string, the local
-ref that matches it is fast forwarded using <src>.
+ref that matches it is fast-forwarded using <src>.
If the optional plus `+` is used, the local ref
-is updated even if it does not result in a fast forward
+is updated even if it does not result in a fast-forward
update.
+
[NOTE]
static const char * const builtin_fetch_usage[] = {
"git fetch [options] [<repository> <refspec>...]",
+ "git fetch [options] <group>",
+ "git fetch --multiple [options] [<repository> | <group>]...",
+ "git fetch --all [options]",
NULL
};
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 const char *depth;
static const char *upload_pack;
static struct option builtin_fetch_options[] = {
OPT__VERBOSITY(&verbosity),
+ OPT_BOOLEAN(0, "all", &all,
+ "fetch from all remotes"),
OPT_BOOLEAN('a', "append", &append,
"append to .git/FETCH_HEAD instead of overwriting"),
OPT_STRING(0, "upload-pack", &upload_pack, "PATH",
"path to upload pack on remote end"),
OPT_BOOLEAN('f', "force", &force,
"force overwrite of local branch"),
+ OPT_BOOLEAN('m', "multiple", &multiple,
+ "fetch from multiple remotes"),
OPT_SET_INT('t', "tags", &tags,
"fetch all tags and associated objects", TAGS_SET),
OPT_SET_INT('n', NULL, &tags,
"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('u', "update-head-ok", &update_head_ok,
"allow updating of HEAD ref"),
char *rla = getenv("GIT_REFLOG_ACTION");
static struct ref_lock *lock;
+ if (dry_run)
+ return 0;
if (!rla)
rla = default_rla.buf;
snprintf(msg, sizeof(msg), "%s: %s", rla, action);
strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV));
strcat(quickref, "..");
strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV));
- r = s_update_ref("fast forward", ref, 1);
+ r = s_update_ref("fast-forward", ref, 1);
sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : ' ',
SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote,
pretty_ref, r ? " (unable to update local ref)" : "");
r ? "unable to update local ref" : "forced update");
return r;
} else {
- sprintf(display, "! %-*s %-*s -> %s (non fast forward)",
+ sprintf(display, "! %-*s %-*s -> %s (non-fast-forward)",
SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote,
pretty_ref);
return 1;
char note[1024];
const char *what, *kind;
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");
if (!fp)
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,
int flag, void *cbdata)
{
struct string_list *list = (struct string_list *)cbdata;
- string_list_insert(refname, list);
+ struct string_list_item *item = string_list_insert(refname, list);
+ item->util = (void *)sha1;
return 0;
}
return 0;
}
+struct tag_data {
+ struct ref **head;
+ struct ref ***tail;
+};
+
+static int add_to_tail(struct string_list_item *item, void *cb_data)
+{
+ struct tag_data *data = (struct tag_data *)cb_data;
+ struct ref *rm = NULL;
+
+ /* We have already decided to ignore this item */
+ if (!item->util)
+ return 0;
+
+ rm = alloc_ref(item->string);
+ rm->peer_ref = alloc_ref(item->string);
+ hashcpy(rm->old_sha1, item->util);
+
+ **data->tail = rm;
+ *data->tail = &rm->next;
+
+ return 0;
+}
+
static void find_non_local_tags(struct transport *transport,
struct ref **head,
struct ref ***tail)
{
struct string_list existing_refs = { NULL, 0, 0, 0 };
- struct string_list new_refs = { NULL, 0, 0, 1 };
- char *ref_name;
- int ref_name_len;
- const unsigned char *ref_sha1;
- const struct ref *tag_ref;
- struct ref *rm = NULL;
+ struct string_list remote_refs = { NULL, 0, 0, 0 };
+ struct tag_data data = {head, tail};
const struct ref *ref;
+ struct string_list_item *item = NULL;
for_each_ref(add_existing, &existing_refs);
for (ref = transport_get_remote_refs(transport); ref; ref = ref->next) {
if (prefixcmp(ref->name, "refs/tags"))
continue;
- ref_name = xstrdup(ref->name);
- ref_name_len = strlen(ref_name);
- ref_sha1 = ref->old_sha1;
-
- if (!strcmp(ref_name + ref_name_len - 3, "^{}")) {
- ref_name[ref_name_len - 3] = 0;
- tag_ref = transport_get_remote_refs(transport);
- while (tag_ref) {
- if (!strcmp(tag_ref->name, ref_name)) {
- ref_sha1 = tag_ref->old_sha1;
- break;
- }
- tag_ref = tag_ref->next;
- }
+ /*
+ * The peeled ref always follows the matching base
+ * ref, so if we see a peeled ref that we don't want
+ * to fetch then we can mark the ref entry in the list
+ * as one to ignore by setting util to NULL.
+ */
+ if (!strcmp(ref->name + strlen(ref->name) - 3, "^{}")) {
+ if (item && !has_sha1_file(ref->old_sha1) &&
+ !will_fetch(head, ref->old_sha1) &&
+ !has_sha1_file(item->util) &&
+ !will_fetch(head, item->util))
+ item->util = NULL;
+ item = NULL;
+ continue;
}
- if (!string_list_has_string(&existing_refs, ref_name) &&
- !string_list_has_string(&new_refs, ref_name) &&
- (has_sha1_file(ref->old_sha1) ||
- will_fetch(head, ref->old_sha1))) {
- string_list_insert(ref_name, &new_refs);
+ /*
+ * If item is non-NULL here, then we previously saw a
+ * ref not followed by a peeled reference, so we need
+ * to check if it is a lightweight tag that we want to
+ * fetch.
+ */
+ if (item && !has_sha1_file(item->util) &&
+ !will_fetch(head, item->util))
+ item->util = NULL;
- rm = alloc_ref(ref_name);
- rm->peer_ref = alloc_ref(ref_name);
- hashcpy(rm->old_sha1, ref_sha1);
+ item = NULL;
- **tail = rm;
- *tail = &rm->next;
- }
- free(ref_name);
+ /* skip duplicates and refs that we already have */
+ if (string_list_has_string(&remote_refs, ref->name) ||
+ string_list_has_string(&existing_refs, ref->name))
+ continue;
+
+ item = string_list_insert(ref->name, &remote_refs);
+ item->util = (void *)ref->old_sha1;
}
string_list_clear(&existing_refs, 0);
- string_list_clear(&new_refs, 0);
+
+ /*
+ * We may have a final lightweight tag that needs to be
+ * checked to see if it needs fetching.
+ */
+ if (item && !has_sha1_file(item->util) &&
+ !will_fetch(head, item->util))
+ item->util = NULL;
+
+ /*
+ * For all the tags in the remote_refs string list, call
+ * add_to_tail to add them to the list of refs to be fetched
+ */
+ for_each_string_list(add_to_tail, &remote_refs, &data);
+
+ string_list_clear(&remote_refs, 0);
}
static void check_not_current_branch(struct ref *ref_map)
static int do_fetch(struct transport *transport,
struct refspec *refs, int ref_count)
{
+ struct string_list existing_refs = { NULL, 0, 0, 0 };
+ struct string_list_item *peer_item = NULL;
struct ref *ref_map;
struct ref *rm;
int autotags = (transport->remote->fetch_tags == 1);
+
+ for_each_ref(add_existing, &existing_refs);
+
if (transport->remote->fetch_tags == 2 && tags != TAGS_UNSET)
tags = TAGS_SET;
if (transport->remote->fetch_tags == -1)
die("Don't know how to fetch from %s", transport->url);
/* if not appending, truncate FETCH_HEAD */
- if (!append) {
+ if (!append && !dry_run) {
char *filename = git_path("FETCH_HEAD");
FILE *fp = fopen(filename, "w");
if (!fp)
check_not_current_branch(ref_map);
for (rm = ref_map; rm; rm = rm->next) {
- if (rm->peer_ref)
- read_ref(rm->peer_ref->name, rm->peer_ref->old_sha1);
+ if (rm->peer_ref) {
+ peer_item = string_list_lookup(rm->peer_ref->name,
+ &existing_refs);
+ if (peer_item)
+ hashcpy(rm->peer_ref->old_sha1,
+ peer_item->util);
+ }
}
if (tags == TAGS_DEFAULT && autotags)
free_refs(ref_map);
return 1;
}
+ if (prune)
+ prune_refs(transport, ref_map);
free_refs(ref_map);
/* if neither --no-tags nor --tags was specified, do automated tag
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;
static const char **refs = NULL;
int ref_nr = 0;
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)
die("Where do you want to fetch from today?");
transport = transport_get(remote, remote->url[0]);
if (verbosity >= 2)
- transport->verbose = 1;
+ transport->verbose = verbosity <= 3 ? verbosity : 3;
if (verbosity < 0)
transport->verbose = -1;
if (upload_pack)
if (depth)
set_option(TRANS_OPT_DEPTH, depth);
- if (argc > 1) {
+ if (argc > 0) {
int j = 0;
refs = xcalloc(argc + 1, sizeof(const char *));
- for (i = 1; i < argc; i++) {
+ for (i = 0; i < argc; i++) {
if (!strcmp(argv[i], "tag")) {
char *ref;
i++;
transport = NULL;
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;
+ }
"git remote add [-t <branch>] [-m <master>] [-f] [--mirror] <name> <url>",
"git remote rename <old> <new>",
"git remote rm <name>",
- "git remote set-head <name> [-a | -d | <branch>]",
- "git remote show [-n] <name>",
+ "git remote set-head <name> (-a | -d | <branch>)",
+ "git remote [-v | --verbose] show [-n] <name>",
"git remote prune [-n | --dry-run] <name>",
- "git remote [-v | --verbose] update [-p | --prune] [group]",
+ "git remote [-v | --verbose] update [-p | --prune] [group | remote]",
+ NULL
+};
+
+static const char * const builtin_remote_add_usage[] = {
+ "git remote add [<options>] <name> <url>",
+ NULL
+};
+
+static const char * const builtin_remote_rename_usage[] = {
+ "git remote rename <old> <new>",
+ NULL
+};
+
+static const char * const builtin_remote_rm_usage[] = {
+ "git remote rm <name>",
+ NULL
+};
+
+static const char * const builtin_remote_sethead_usage[] = {
+ "git remote set-head <name> (-a | -d | <branch>])",
+ NULL
+};
+
+static const char * const builtin_remote_show_usage[] = {
+ "git remote show [<options>] <name>",
+ NULL
+};
+
+static const char * const builtin_remote_prune_usage[] = {
+ "git remote prune [<options>] <name>",
+ NULL
+};
+
+static const char * const builtin_remote_update_usage[] = {
+ "git remote update [<options>] [<group> | <remote>]...",
NULL
};
int i;
struct option options[] = {
- OPT_GROUP("add specific options"),
OPT_BOOLEAN('f', "fetch", &fetch, "fetch the remote branches"),
OPT_CALLBACK('t', "track", &track, "branch",
"branch(es) to track", opt_parse_track),
OPT_END()
};
- argc = parse_options(argc, argv, NULL, options, builtin_remote_usage,
+ argc = parse_options(argc, argv, NULL, options, builtin_remote_add_usage,
0);
if (argc < 2)
- usage_with_options(builtin_remote_usage, options);
+ usage_with_options(builtin_remote_add_usage, options);
name = argv[0];
url = argv[1];
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)
{
struct ref *fetch_map = NULL, **tail = &fetch_map;
- struct ref *ref;
+ struct ref *ref, *stale_refs;
int i;
for (i = 0; i < states->remote->fetch_refspec_nr; i++)
else
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);
sort_string_list(&states->new);
sort_string_list(&states->tracked);
- for_each_ref(handle_one_branch, states);
sort_string_list(&states->stale);
return 0;
int i;
if (argc != 3)
- usage_with_options(builtin_remote_usage, options);
+ usage_with_options(builtin_remote_rename_usage, options);
rename.old = argv[1];
rename.new = argv[2];
int i, result;
if (argc != 2)
- usage_with_options(builtin_remote_usage, options);
+ usage_with_options(builtin_remote_rm_usage, options);
remote = remote_get(argv[1]);
if (!remote)
status = "up to date";
break;
case PUSH_STATUS_FASTFORWARD:
- status = "fast forwardable";
+ status = "fast-forwardable";
break;
case PUSH_STATUS_OUTOFDATE:
status = "local out of date";
{
int no_query = 0, result = 0, query_flag = 0;
struct option options[] = {
- OPT_GROUP("show specific options"),
OPT_BOOLEAN('n', NULL, &no_query, "do not query remotes"),
OPT_END()
};
struct string_list info_list = { NULL, 0, 0, 0 };
struct show_info info;
- argc = parse_options(argc, argv, NULL, options, builtin_remote_usage,
+ argc = parse_options(argc, argv, NULL, options, builtin_remote_show_usage,
0);
if (argc < 1)
char *head_name = NULL;
struct option options[] = {
- OPT_GROUP("set-head specific options"),
OPT_BOOLEAN('a', "auto", &opt_a,
"set refs/remotes/<name>/HEAD according to remote"),
OPT_BOOLEAN('d', "delete", &opt_d,
"delete refs/remotes/<name>/HEAD"),
OPT_END()
};
- argc = parse_options(argc, argv, NULL, options, builtin_remote_usage,
+ argc = parse_options(argc, argv, NULL, options, builtin_remote_sethead_usage,
0);
if (argc)
strbuf_addf(&buf, "refs/remotes/%s/HEAD", argv[0]);
if (delete_ref(buf.buf, NULL, REF_NODEREF))
result |= error("Could not delete %s", buf.buf);
} else
- usage_with_options(builtin_remote_usage, options);
+ usage_with_options(builtin_remote_sethead_usage, options);
if (head_name) {
unsigned char sha1[20];
{
int dry_run = 0, result = 0;
struct option options[] = {
- OPT_GROUP("prune specific options"),
OPT__DRY_RUN(&dry_run),
OPT_END()
};
- argc = parse_options(argc, argv, NULL, options, builtin_remote_usage,
+ argc = parse_options(argc, argv, NULL, options, builtin_remote_prune_usage,
0);
if (argc < 1)
- usage_with_options(builtin_remote_usage, options);
+ usage_with_options(builtin_remote_prune_usage, options);
for (; argc; argc--, argv++)
result |= prune_remote(*argv, dry_run);
printf(" * [%s] %s\n", dry_run ? "would prune" : "pruned",
abbrev_ref(refname, "refs/remotes/"));
- warn_dangling_symref(dangling_msg, refname);
+ warn_dangling_symref(stdout, dangling_msg, refname);
}
free_remote_ref_states(&states);
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 (!remote->skip_default_update)
- string_list_append(remote->name, list);
- 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");
- }
+ if (strcmp(key, "remotes.default") == 0) {
+ int *found = priv;
+ *found = 1;
}
-
return 0;
}
static int update(int argc, const char **argv)
{
- int i, result = 0, prune = 0;
- struct string_list list = { NULL, 0, 0, 0 };
- static const char *default_argv[] = { NULL, "default", NULL };
+ int i, prune = 0;
struct option options[] = {
- OPT_GROUP("update specific options"),
OPT_BOOLEAN('p', "prune", &prune,
"prune remotes after fetching"),
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_usage,
+ argc = parse_options(argc, argv, NULL, options, builtin_remote_update_usage,
PARSE_OPT_KEEP_ARGV0);
- if (argc < 2) {
- argc = 2;
- argv = default_argv;
- }
- remote_group.list = &list;
- for (i = 1; i < argc; i++) {
- int groups_found = 0;
- remote_group.name = argv[i];
- 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);
- }
- }
+ fetch_argv[fetch_argc++] = "fetch";
- if (!result && !list.nr && argc == 2 && !strcmp(argv[1], "default"))
- result = for_each_remote(get_one_remote_for_update, &list);
+ if (prune)
+ fetch_argv[fetch_argc++] = "--prune";
+ if (verbose)
+ fetch_argv[fetch_argc++] = "-v";
+ if (argc < 2) {
+ fetch_argv[fetch_argc++] = "default";
+ } else {
+ fetch_argv[fetch_argc++] = "--multiple";
+ for (i = 1; i < argc; i++)
+ fetch_argv[fetch_argc++] = argv[i];
+ }
- for (i = 0; i < list.nr; i++) {
- int err = fetch_remote(list.items[i].string);
- result |= err;
- if (!err && prune)
- result |= prune_remote(list.items[i].string, 0);
+ if (strcmp(fetch_argv[fetch_argc-1], "default") == 0) {
+ git_config(get_remote_default, &default_defined);
+ if (!default_defined)
+ fetch_argv[fetch_argc-1] = "--all";
}
- /* all names were strdup()ed or strndup()ed */
- list.strdup_strings = 1;
- string_list_clear(&list, 0);
+ fetch_argv[fetch_argc] = NULL;
- return result;
+ return run_command_v_opt(fetch_argv, RUN_GIT_CMD);
}
static int get_one_entry(struct remote *remote, void *priv)
int cmd_remote(int argc, const char **argv, const char *prefix)
{
struct option options[] = {
- OPT__VERBOSE(&verbose),
+ OPT_BOOLEAN('v', "verbose", &verbose, "be verbose; must be placed before a subcommand"),
OPT_END()
};
int result;
remote->mirror = git_config_bool(key, value);
else if (!strcmp(subkey, ".skipdefaultupdate"))
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")) {
const char *v;
if (git_config_string(&v, key, value))
void ref_remove_duplicates(struct ref *ref_map)
{
- struct ref **posn;
- struct ref *next;
- for (; ref_map; ref_map = ref_map->next) {
+ struct string_list refs = { NULL, 0, 0, 0 };
+ struct string_list_item *item = NULL;
+ struct ref *prev = NULL, *next = NULL;
+ for (; ref_map; prev = ref_map, ref_map = next) {
+ next = ref_map->next;
if (!ref_map->peer_ref)
continue;
- posn = &ref_map->next;
- while (*posn) {
- if ((*posn)->peer_ref &&
- !strcmp((*posn)->peer_ref->name,
- ref_map->peer_ref->name)) {
- if (strcmp((*posn)->name, ref_map->name))
- die("%s tracks both %s and %s",
- ref_map->peer_ref->name,
- (*posn)->name, ref_map->name);
- next = (*posn)->next;
- free((*posn)->peer_ref);
- free(*posn);
- *posn = next;
- } else {
- posn = &(*posn)->next;
- }
+
+ item = string_list_lookup(ref_map->peer_ref->name, &refs);
+ if (item) {
+ if (strcmp(((struct ref *)item->util)->name,
+ ref_map->name))
+ die("%s tracks both %s and %s",
+ ref_map->peer_ref->name,
+ ((struct ref *)item->util)->name,
+ ref_map->name);
+ prev->next = ref_map->next;
+ free(ref_map->peer_ref);
+ free(ref_map);
+ ref_map = prev; /* skip this; we freed it */
+ continue;
}
+
+ item = string_list_insert(ref_map->peer_ref->name, &refs);
+ item->util = ref_map;
}
+ string_list_clear(&refs, 0);
}
int remote_has_url(struct remote *remote, const char *url)
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;
+ }
another
master
Local refs configured for 'git push':
- ahead forces to master (fast forwardable)
+ ahead forces to master (fast-forwardable)
master pushes to another (up to date)
EOF
'
+ 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
apis/master
apis/side