git-commit-vandalism/remote.c

2873 lines
74 KiB
C
Raw Normal View History

#include "git-compat-util.h"
#include "abspath.h"
#include "alloc.h"
#include "config.h"
#include "environment.h"
#include "gettext.h"
#include "hex.h"
#include "remote.h"
remote: create fetch.credentialsInUrl config Users sometimes provide a "username:password" combination in their plaintext URLs. Since Git stores these URLs in plaintext in the .git/config file, this is a very insecure way of storing these credentials. Credential managers are a more secure way of storing this information. System administrators might want to prevent this kind of use by users on their machines. Create a new "fetch.credentialsInUrl" config option and teach Git to warn or die when seeing a URL with this kind of information. The warning anonymizes the sensitive information of the URL to be clear about the issue. This change currently defaults the behavior to "allow" which does nothing with these URLs. We can consider changing this behavior to "warn" by default if we wish. At that time, we may want to add some advice about setting fetch.credentialsInUrl=ignore for users who still want to follow this pattern (and not receive the warning). An earlier version of this change injected the logic into url_normalize() in urlmatch.c. While most code paths that parse URLs eventually normalize the URL, that normalization does not happen early enough in the stack to avoid attempting connections to the URL first. By inserting a check into the remote validation, we identify the issue before making a connection. In the old code path, this was revealed by testing the new t5601-clone.sh test under --stress, resulting in an instance where the return code was 13 (SIGPIPE) instead of 128 from the die(). However, we can reuse the parsing information from url_normalize() in order to benefit from its well-worn parsing logic. We can use the struct url_info that is created in that method to replace the password with "<redacted>" in our error messages. This comes with a slight downside that the normalized URL might look slightly different from the input URL (for instance, the normalized version adds a closing slash). This should not hinder users figuring out what the problem is and being able to fix the issue. As an attempt to ensure the parsing logic did not catch any unintentional cases, I modified this change locally to to use the "die" option by default. Running the test suite succeeds except for the explicit username:password URLs used in t5550-http-fetch-dumb.sh and t5541-http-push-smart.sh. This means that all other tested URLs did not trigger this logic. The tests show that the proper error messages appear (or do not appear), but also count the number of error messages. When only warning, each process validates the remote URL and outputs a warning. This happens twice for clone, three times for fetch, and once for push. Helped-by: Junio C Hamano <gitster@pobox.com> Signed-off-by: Derrick Stolee <derrickstolee@github.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-06-06 16:36:16 +02:00
#include "urlmatch.h"
#include "refs.h"
#include "refspec.h"
#include "object-name.h"
#include "object-store.h"
#include "commit.h"
#include "diff.h"
#include "revision.h"
#include "dir.h"
#include "tag.h"
#include "setup.h"
#include "string-list.h"
#include "strvec.h"
#include "commit-reach.h"
#include "advice.h"
#include "connect.h"
#include "parse-options.h"
enum map_direction { FROM_SRC, FROM_DST };
struct counted_string {
size_t len;
const char *s;
};
static int valid_remote(const struct remote *remote)
{
return (!!remote->url) || (!!remote->foreign_vcs);
}
static const char *alias_url(const char *url, struct rewrites *r)
{
int i, j;
struct counted_string *longest;
int longest_i;
longest = NULL;
longest_i = -1;
for (i = 0; i < r->rewrite_nr; i++) {
if (!r->rewrite[i])
continue;
for (j = 0; j < r->rewrite[i]->instead_of_nr; j++) {
if (starts_with(url, r->rewrite[i]->instead_of[j].s) &&
(!longest ||
longest->len < r->rewrite[i]->instead_of[j].len)) {
longest = &(r->rewrite[i]->instead_of[j]);
longest_i = i;
}
}
}
if (!longest)
return url;
return xstrfmt("%s%s", r->rewrite[longest_i]->base, url + longest->len);
}
static void add_url(struct remote *remote, const char *url)
{
ALLOC_GROW(remote->url, remote->url_nr + 1, remote->url_alloc);
remote->url[remote->url_nr++] = url;
}
static void add_pushurl(struct remote *remote, const char *pushurl)
{
ALLOC_GROW(remote->pushurl, remote->pushurl_nr + 1, remote->pushurl_alloc);
remote->pushurl[remote->pushurl_nr++] = pushurl;
}
static void add_pushurl_alias(struct remote_state *remote_state,
struct remote *remote, const char *url)
{
const char *pushurl = alias_url(url, &remote_state->rewrites_push);
if (pushurl != url)
add_pushurl(remote, pushurl);
}
static void add_url_alias(struct remote_state *remote_state,
struct remote *remote, const char *url)
{
add_url(remote, alias_url(url, &remote_state->rewrites));
add_pushurl_alias(remote_state, remote, url);
}
struct remotes_hash_key {
const char *str;
int len;
};
static int remotes_hash_cmp(const void *cmp_data UNUSED,
const struct hashmap_entry *eptr,
const struct hashmap_entry *entry_or_key,
const void *keydata)
{
const struct remote *a, *b;
const struct remotes_hash_key *key = keydata;
a = container_of(eptr, const struct remote, ent);
b = container_of(entry_or_key, const struct remote, ent);
if (key)
return strncmp(a->name, key->str, key->len) || a->name[key->len];
else
return strcmp(a->name, b->name);
}
static struct remote *make_remote(struct remote_state *remote_state,
const char *name, int len)
{
struct remote *ret;
struct remotes_hash_key lookup;
struct hashmap_entry lookup_entry, *e;
if (!len)
len = strlen(name);
lookup.str = name;
lookup.len = len;
hashmap_entry_init(&lookup_entry, memhash(name, len));
e = hashmap_get(&remote_state->remotes_hash, &lookup_entry, &lookup);
if (e)
return container_of(e, struct remote, ent);
CALLOC_ARRAY(ret, 1);
ret->prune = -1; /* unspecified */
fetch: add a --prune-tags option and fetch.pruneTags config Add a --prune-tags option to git-fetch, along with fetch.pruneTags config option and a -P shorthand (-p is --prune). This allows for doing any of: git fetch -p -P git fetch --prune --prune-tags git fetch -p -P origin git fetch --prune --prune-tags origin Or simply: git config fetch.prune true && git config fetch.pruneTags true && git fetch Instead of the much more verbose: git fetch --prune origin 'refs/tags/*:refs/tags/*' '+refs/heads/*:refs/remotes/origin/*' Before this feature it was painful to support the use-case of pulling from a repo which is having both its branches *and* tags deleted regularly, and have our local references to reflect upstream. At work we create deployment tags in the repo for each rollout, and there's *lots* of those, so they're archived within weeks for performance reasons. Without this change it's hard to centrally configure such repos in /etc/gitconfig (on servers that are only used for working with them). You need to set fetch.prune=true globally, and then for each repo: git -C {} config --replace-all remote.origin.fetch "refs/tags/*:refs/tags/*" "^\+*refs/tags/\*:refs/tags/\*$" Now I can simply set fetch.pruneTags=true in /etc/gitconfig as well, and users running "git pull" will automatically get the pruning semantics I want. Even though "git remote" has corresponding "prune" and "update --prune" subcommands I'm intentionally not adding a corresponding prune-tags or "update --prune --prune-tags" mode to that command. It's advertised (as noted in my recent "git remote doc: correct dangerous lies about what prune does") as only modifying remote tracking references, whereas any --prune-tags option is always going to modify what from the user's perspective is a local copy of the tag, since there's no such thing as a remote tracking tag. Ideally add_prune_tags_to_fetch_refspec() would be something that would use ALLOC_GROW() to grow the 'fetch` member of the 'remote' struct. Instead I'm realloc-ing remote->fetch and adding the tag_refspec to the end. The reason is that parse_{fetch,push}_refspec which allocate the refspec (ultimately remote->fetch) struct are called many places that don't have access to a 'remote' struct. It would be hard to change all their callsites to be amenable to carry around the bookkeeping variables required for dynamic allocation. All the other callers of the API first incrementally construct the string version of the refspec in remote->fetch_refspec via add_fetch_refspec(), before finally calling parse_fetch_refspec() via some variation of remote_get(). It's less of a pain to deal with the one special case that needs to modify already constructed refspecs than to chase down and change all the other callsites. The API I'm adding is intentionally not generalized because if we add more of these we'd probably want to re-visit how this is done. See my "Re: [BUG] git remote prune removes local tags, depending on fetch config" (87po6ahx87.fsf@evledraar.gmail.com; https://public-inbox.org/git/87po6ahx87.fsf@evledraar.gmail.com/) for more background info. Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-02-09 21:32:15 +01:00
ret->prune_tags = -1; /* unspecified */
ret->name = xstrndup(name, len);
refspec_init(&ret->push, REFSPEC_PUSH);
refspec_init(&ret->fetch, REFSPEC_FETCH);
ALLOC_GROW(remote_state->remotes, remote_state->remotes_nr + 1,
remote_state->remotes_alloc);
remote_state->remotes[remote_state->remotes_nr++] = ret;
hashmap_entry_init(&ret->ent, lookup_entry.hash);
if (hashmap_put_entry(&remote_state->remotes_hash, ret, ent))
BUG("hashmap_put overwrote entry after hashmap_get returned NULL");
return ret;
}
static void remote_clear(struct remote *remote)
{
int i;
free((char *)remote->name);
free((char *)remote->foreign_vcs);
for (i = 0; i < remote->url_nr; i++)
free((char *)remote->url[i]);
FREE_AND_NULL(remote->url);
for (i = 0; i < remote->pushurl_nr; i++)
free((char *)remote->pushurl[i]);
FREE_AND_NULL(remote->pushurl);
free((char *)remote->receivepack);
free((char *)remote->uploadpack);
FREE_AND_NULL(remote->http_proxy);
FREE_AND_NULL(remote->http_proxy_authmethod);
}
static void add_merge(struct branch *branch, const char *name)
{
ALLOC_GROW(branch->merge_name, branch->merge_nr + 1,
branch->merge_alloc);
branch->merge_name[branch->merge_nr++] = name;
}
struct branches_hash_key {
const char *str;
int len;
};
static int branches_hash_cmp(const void *cmp_data UNUSED,
const struct hashmap_entry *eptr,
const struct hashmap_entry *entry_or_key,
const void *keydata)
{
const struct branch *a, *b;
const struct branches_hash_key *key = keydata;
a = container_of(eptr, const struct branch, ent);
b = container_of(entry_or_key, const struct branch, ent);
if (key)
return strncmp(a->name, key->str, key->len) ||
a->name[key->len];
else
return strcmp(a->name, b->name);
}
static struct branch *find_branch(struct remote_state *remote_state,
const char *name, size_t len)
{
struct branches_hash_key lookup;
struct hashmap_entry lookup_entry, *e;
lookup.str = name;
lookup.len = len;
hashmap_entry_init(&lookup_entry, memhash(name, len));
e = hashmap_get(&remote_state->branches_hash, &lookup_entry, &lookup);
if (e)
return container_of(e, struct branch, ent);
return NULL;
}
static void die_on_missing_branch(struct repository *repo,
struct branch *branch)
{
/* branch == NULL is always valid because it represents detached HEAD. */
if (branch &&
remote.c: don't BUG() on 0-length branch names 4a2dcb1a08 (remote: die if branch is not found in repository, 2021-11-17) introduced a regression where multiple config entries with an empty branch name, e.g. [branch ""] remote = foo merge = bar could cause Git to fail when it tries to look up branch tracking information. We parse the config key to get (branch name, branch name length), but when the branch name subsection is empty, we get a bogus branch name, e.g. "branch..remote" gives (".remote", 0). We continue to use the bogus branch name as if it were valid, and prior to 4a2dcb1a08, this wasn't an issue because length = 0 caused the branch name to effectively be "" everywhere. However, that commit handles length = 0 inconsistently when we create the branch: - When find_branch() is called to check if the branch exists in the branch hash map, it interprets a length of 0 to mean that it should call strlen on the char pointer. - But the code path that inserts into the branch hash map interprets a length of 0 to mean that the string is 0-length. This results in the bug described above: - "branch..remote" looks for ".remote" in the branch hash map. Since we do not find it, we insert the "" entry into the hash map. - "branch..merge" looks for ".merge" in the branch hash map. Since we do not find it, we again try to insert the "" entry into the hash map. However, the entries in the branch hash map are supposed to be appended to, not overwritten. - Since overwriting an entry is a BUG(), Git fails instead of silently ignoring the empty branch name. Fix the bug by removing the convenience strlen functionality, so that 0 means that the string is 0-length. We still insert a bogus branch name into the hash map, but this will be fixed in a later commit. Reported-by: "Ing. Martin Prantl Ph.D." <perry@ntis.zcu.cz> Helped-by: Jeff King <peff@peff.net> Signed-off-by: Glen Choo <chooglen@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-06-01 01:12:33 +02:00
branch != find_branch(repo->remote_state, branch->name,
strlen(branch->name)))
die("branch %s was not found in the repository", branch->name);
}
static struct branch *make_branch(struct remote_state *remote_state,
const char *name, size_t len)
{
struct branch *ret;
ret = find_branch(remote_state, name, len);
if (ret)
return ret;
CALLOC_ARRAY(ret, 1);
ret->name = xstrndup(name, len);
ret->refname = xstrfmt("refs/heads/%s", ret->name);
hashmap_entry_init(&ret->ent, memhash(name, len));
if (hashmap_put_entry(&remote_state->branches_hash, ret, ent))
BUG("hashmap_put overwrote entry after hashmap_get returned NULL");
return ret;
}
static struct rewrite *make_rewrite(struct rewrites *r,
const char *base, size_t len)
{
struct rewrite *ret;
int i;
for (i = 0; i < r->rewrite_nr; i++) {
if (len == r->rewrite[i]->baselen &&
!strncmp(base, r->rewrite[i]->base, len))
return r->rewrite[i];
}
ALLOC_GROW(r->rewrite, r->rewrite_nr + 1, r->rewrite_alloc);
CALLOC_ARRAY(ret, 1);
r->rewrite[r->rewrite_nr++] = ret;
ret->base = xstrndup(base, len);
ret->baselen = len;
return ret;
}
static void add_instead_of(struct rewrite *rewrite, const char *instead_of)
{
ALLOC_GROW(rewrite->instead_of, rewrite->instead_of_nr + 1, rewrite->instead_of_alloc);
rewrite->instead_of[rewrite->instead_of_nr].s = instead_of;
rewrite->instead_of[rewrite->instead_of_nr].len = strlen(instead_of);
rewrite->instead_of_nr++;
}
static const char *skip_spaces(const char *s)
{
while (isspace(*s))
s++;
return s;
}
static void read_remotes_file(struct remote_state *remote_state,
struct remote *remote)
{
struct strbuf buf = STRBUF_INIT;
FILE *f = fopen_or_warn(git_path("remotes/%s", remote->name), "r");
if (!f)
return;
remote rename: more carefully determine whether a remote is configured One of the really nice features of the ~/.gitconfig file is that users can override defaults by their own preferred settings for all of their repositories. One such default that some users like to override is whether the "origin" remote gets auto-pruned or not. The user would simply call git config --global remote.origin.prune true and from now on all "origin" remotes would be pruned automatically when fetching into the local repository. There is just one catch: now Git thinks that the "origin" remote is configured, even if the repository config has no [remote "origin"] section at all, as it does not realize that the "prune" setting was configured globally and that there really is no "origin" remote configured in this repository. That is a problem e.g. when renaming a remote to a new name, when Git may be fooled into thinking that there is already a remote of that new name. Let's fix this by paying more attention to *where* the remote settings came from: if they are configured in the local repository config, we must not overwrite them. If they were configured elsewhere, we cannot overwrite them to begin with, as we only write the repository config. There is only one caller of remote_is_configured() (in `git fetch`) that may want to take remotes into account even if they were configured outside the repository config; all other callers essentially try to prevent the Git command from overwriting settings in the repository config. To accommodate that fact, the remote_is_configured() function now requires a parameter that states whether the caller is interested in all remotes, or only in those that were configured in the repository config. Many thanks to Jeff King whose tireless review helped with settling for nothing less than the current strategy. This fixes https://github.com/git-for-windows/git/issues/888 Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-01-19 22:20:02 +01:00
remote->configured_in_repo = 1;
remote->origin = REMOTE_REMOTES;
while (strbuf_getline(&buf, f) != EOF) {
const char *v;
strbuf_rtrim(&buf);
if (skip_prefix(buf.buf, "URL:", &v))
add_url_alias(remote_state, remote,
xstrdup(skip_spaces(v)));
else if (skip_prefix(buf.buf, "Push:", &v))
refspec_append(&remote->push, skip_spaces(v));
else if (skip_prefix(buf.buf, "Pull:", &v))
refspec_append(&remote->fetch, skip_spaces(v));
}
strbuf_release(&buf);
fclose(f);
}
static void read_branches_file(struct remote_state *remote_state,
struct remote *remote)
{
char *frag;
struct strbuf buf = STRBUF_INIT;
FILE *f = fopen_or_warn(git_path("branches/%s", remote->name), "r");
if (!f)
return;
strbuf_getline_lf(&buf, f);
fclose(f);
strbuf_trim(&buf);
if (!buf.len) {
strbuf_release(&buf);
return;
}
remote rename: more carefully determine whether a remote is configured One of the really nice features of the ~/.gitconfig file is that users can override defaults by their own preferred settings for all of their repositories. One such default that some users like to override is whether the "origin" remote gets auto-pruned or not. The user would simply call git config --global remote.origin.prune true and from now on all "origin" remotes would be pruned automatically when fetching into the local repository. There is just one catch: now Git thinks that the "origin" remote is configured, even if the repository config has no [remote "origin"] section at all, as it does not realize that the "prune" setting was configured globally and that there really is no "origin" remote configured in this repository. That is a problem e.g. when renaming a remote to a new name, when Git may be fooled into thinking that there is already a remote of that new name. Let's fix this by paying more attention to *where* the remote settings came from: if they are configured in the local repository config, we must not overwrite them. If they were configured elsewhere, we cannot overwrite them to begin with, as we only write the repository config. There is only one caller of remote_is_configured() (in `git fetch`) that may want to take remotes into account even if they were configured outside the repository config; all other callers essentially try to prevent the Git command from overwriting settings in the repository config. To accommodate that fact, the remote_is_configured() function now requires a parameter that states whether the caller is interested in all remotes, or only in those that were configured in the repository config. Many thanks to Jeff King whose tireless review helped with settling for nothing less than the current strategy. This fixes https://github.com/git-for-windows/git/issues/888 Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-01-19 22:20:02 +01:00
remote->configured_in_repo = 1;
remote->origin = REMOTE_BRANCHES;
/*
* The branches file would have URL and optionally
* #branch specified. The default (or specified) branch is
* fetched and stored in the local branch matching the
* remote name.
*/
frag = strchr(buf.buf, '#');
if (frag)
*(frag++) = '\0';
else
frag = (char *)git_default_branch_name(0);
add_url_alias(remote_state, remote, strbuf_detach(&buf, NULL));
refspec_appendf(&remote->fetch, "refs/heads/%s:refs/heads/%s",
frag, remote->name);
/*
* Cogito compatible push: push current HEAD to remote #branch
* (master if missing)
*/
refspec_appendf(&remote->push, "HEAD:refs/heads/%s", frag);
remote->fetch_tags = 1; /* always auto-follow */
}
static int handle_config(const char *key, const char *value, void *cb)
{
const char *name;
size_t namelen;
const char *subkey;
struct remote *remote;
struct branch *branch;
struct remote_state *remote_state = cb;
if (parse_config_key(key, "branch", &name, &namelen, &subkey) >= 0) {
/* There is no subsection. */
if (!name)
return 0;
/* There is a subsection, but it is empty. */
if (!namelen)
return -1;
branch = make_branch(remote_state, name, namelen);
if (!strcmp(subkey, "remote")) {
return git_config_string(&branch->remote_name, key, value);
} else if (!strcmp(subkey, "pushremote")) {
return git_config_string(&branch->pushremote_name, key, value);
} else if (!strcmp(subkey, "merge")) {
if (!value)
return config_error_nonbool(key);
add_merge(branch, xstrdup(value));
}
return 0;
}
if (parse_config_key(key, "url", &name, &namelen, &subkey) >= 0) {
struct rewrite *rewrite;
if (!name)
return 0;
if (!strcmp(subkey, "insteadof")) {
if (!value)
return config_error_nonbool(key);
rewrite = make_rewrite(&remote_state->rewrites, name,
namelen);
add_instead_of(rewrite, xstrdup(value));
} else if (!strcmp(subkey, "pushinsteadof")) {
if (!value)
return config_error_nonbool(key);
rewrite = make_rewrite(&remote_state->rewrites_push,
name, namelen);
add_instead_of(rewrite, xstrdup(value));
}
}
if (parse_config_key(key, "remote", &name, &namelen, &subkey) < 0)
return 0;
/* Handle remote.* variables */
if (!name && !strcmp(subkey, "pushdefault"))
return git_config_string(&remote_state->pushremote_name, key,
value);
if (!name)
return 0;
/* Handle remote.<name>.* variables */
if (*name == '/') {
warning(_("config remote shorthand cannot begin with '/': %s"),
name);
return 0;
}
remote = make_remote(remote_state, name, namelen);
remote->origin = REMOTE_CONFIG;
if (current_config_scope() == CONFIG_SCOPE_LOCAL ||
current_config_scope() == CONFIG_SCOPE_WORKTREE)
remote rename: more carefully determine whether a remote is configured One of the really nice features of the ~/.gitconfig file is that users can override defaults by their own preferred settings for all of their repositories. One such default that some users like to override is whether the "origin" remote gets auto-pruned or not. The user would simply call git config --global remote.origin.prune true and from now on all "origin" remotes would be pruned automatically when fetching into the local repository. There is just one catch: now Git thinks that the "origin" remote is configured, even if the repository config has no [remote "origin"] section at all, as it does not realize that the "prune" setting was configured globally and that there really is no "origin" remote configured in this repository. That is a problem e.g. when renaming a remote to a new name, when Git may be fooled into thinking that there is already a remote of that new name. Let's fix this by paying more attention to *where* the remote settings came from: if they are configured in the local repository config, we must not overwrite them. If they were configured elsewhere, we cannot overwrite them to begin with, as we only write the repository config. There is only one caller of remote_is_configured() (in `git fetch`) that may want to take remotes into account even if they were configured outside the repository config; all other callers essentially try to prevent the Git command from overwriting settings in the repository config. To accommodate that fact, the remote_is_configured() function now requires a parameter that states whether the caller is interested in all remotes, or only in those that were configured in the repository config. Many thanks to Jeff King whose tireless review helped with settling for nothing less than the current strategy. This fixes https://github.com/git-for-windows/git/issues/888 Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-01-19 22:20:02 +01:00
remote->configured_in_repo = 1;
if (!strcmp(subkey, "mirror"))
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, "prune"))
remote->prune = git_config_bool(key, value);
fetch: add a --prune-tags option and fetch.pruneTags config Add a --prune-tags option to git-fetch, along with fetch.pruneTags config option and a -P shorthand (-p is --prune). This allows for doing any of: git fetch -p -P git fetch --prune --prune-tags git fetch -p -P origin git fetch --prune --prune-tags origin Or simply: git config fetch.prune true && git config fetch.pruneTags true && git fetch Instead of the much more verbose: git fetch --prune origin 'refs/tags/*:refs/tags/*' '+refs/heads/*:refs/remotes/origin/*' Before this feature it was painful to support the use-case of pulling from a repo which is having both its branches *and* tags deleted regularly, and have our local references to reflect upstream. At work we create deployment tags in the repo for each rollout, and there's *lots* of those, so they're archived within weeks for performance reasons. Without this change it's hard to centrally configure such repos in /etc/gitconfig (on servers that are only used for working with them). You need to set fetch.prune=true globally, and then for each repo: git -C {} config --replace-all remote.origin.fetch "refs/tags/*:refs/tags/*" "^\+*refs/tags/\*:refs/tags/\*$" Now I can simply set fetch.pruneTags=true in /etc/gitconfig as well, and users running "git pull" will automatically get the pruning semantics I want. Even though "git remote" has corresponding "prune" and "update --prune" subcommands I'm intentionally not adding a corresponding prune-tags or "update --prune --prune-tags" mode to that command. It's advertised (as noted in my recent "git remote doc: correct dangerous lies about what prune does") as only modifying remote tracking references, whereas any --prune-tags option is always going to modify what from the user's perspective is a local copy of the tag, since there's no such thing as a remote tracking tag. Ideally add_prune_tags_to_fetch_refspec() would be something that would use ALLOC_GROW() to grow the 'fetch` member of the 'remote' struct. Instead I'm realloc-ing remote->fetch and adding the tag_refspec to the end. The reason is that parse_{fetch,push}_refspec which allocate the refspec (ultimately remote->fetch) struct are called many places that don't have access to a 'remote' struct. It would be hard to change all their callsites to be amenable to carry around the bookkeeping variables required for dynamic allocation. All the other callers of the API first incrementally construct the string version of the refspec in remote->fetch_refspec via add_fetch_refspec(), before finally calling parse_fetch_refspec() via some variation of remote_get(). It's less of a pain to deal with the one special case that needs to modify already constructed refspecs than to chase down and change all the other callsites. The API I'm adding is intentionally not generalized because if we add more of these we'd probably want to re-visit how this is done. See my "Re: [BUG] git remote prune removes local tags, depending on fetch config" (87po6ahx87.fsf@evledraar.gmail.com; https://public-inbox.org/git/87po6ahx87.fsf@evledraar.gmail.com/) for more background info. Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-02-09 21:32:15 +01:00
else if (!strcmp(subkey, "prunetags"))
remote->prune_tags = git_config_bool(key, value);
else if (!strcmp(subkey, "url")) {
const char *v;
if (git_config_string(&v, key, value))
return -1;
add_url(remote, v);
} else if (!strcmp(subkey, "pushurl")) {
const char *v;
if (git_config_string(&v, key, value))
return -1;
add_pushurl(remote, v);
} else if (!strcmp(subkey, "push")) {
const char *v;
if (git_config_string(&v, key, value))
return -1;
refspec_append(&remote->push, v);
free((char *)v);
} else if (!strcmp(subkey, "fetch")) {
const char *v;
if (git_config_string(&v, key, value))
return -1;
refspec_append(&remote->fetch, v);
free((char *)v);
} else if (!strcmp(subkey, "receivepack")) {
const char *v;
if (git_config_string(&v, key, value))
return -1;
if (!remote->receivepack)
remote->receivepack = v;
else
error(_("more than one receivepack given, using the first"));
} else if (!strcmp(subkey, "uploadpack")) {
const char *v;
if (git_config_string(&v, key, value))
return -1;
if (!remote->uploadpack)
remote->uploadpack = v;
else
error(_("more than one uploadpack given, using the first"));
} else if (!strcmp(subkey, "tagopt")) {
if (!strcmp(value, "--no-tags"))
remote->fetch_tags = -1;
else if (!strcmp(value, "--tags"))
remote->fetch_tags = 2;
} else if (!strcmp(subkey, "proxy")) {
return git_config_string((const char **)&remote->http_proxy,
key, value);
} else if (!strcmp(subkey, "proxyauthmethod")) {
return git_config_string((const char **)&remote->http_proxy_authmethod,
key, value);
} else if (!strcmp(subkey, "vcs")) {
return git_config_string(&remote->foreign_vcs, key, value);
}
return 0;
}
static void alias_all_urls(struct remote_state *remote_state)
{
int i, j;
for (i = 0; i < remote_state->remotes_nr; i++) {
int add_pushurl_aliases;
if (!remote_state->remotes[i])
continue;
for (j = 0; j < remote_state->remotes[i]->pushurl_nr; j++) {
remote_state->remotes[i]->pushurl[j] =
alias_url(remote_state->remotes[i]->pushurl[j],
&remote_state->rewrites);
}
add_pushurl_aliases = remote_state->remotes[i]->pushurl_nr == 0;
for (j = 0; j < remote_state->remotes[i]->url_nr; j++) {
if (add_pushurl_aliases)
add_pushurl_alias(
remote_state, remote_state->remotes[i],
remote_state->remotes[i]->url[j]);
remote_state->remotes[i]->url[j] =
alias_url(remote_state->remotes[i]->url[j],
&remote_state->rewrites);
}
}
}
static void read_config(struct repository *repo)
{
int flag;
if (repo->remote_state->initialized)
return;
repo->remote_state->initialized = 1;
repo->remote_state->current_branch = NULL;
if (startup_info->have_repository) {
const char *head_ref = refs_resolve_ref_unsafe(
refs API: remove "failure_errno" from refs_resolve_ref_unsafe() Remove the now-unused "failure_errno" parameter from the refs_resolve_ref_unsafe() signature. In my recent 96f6623ada0 (Merge branch 'ab/refs-errno-cleanup', 2021-11-29) series we made all of its callers explicitly request the errno via an output parameter. As that series shows all but one caller ended up passing in a boilerplate "ignore_errno", since they only cared about whether the return value was NULL or not, i.e. if the ref could be resolved. There was one small issue with that series fixed with a follow-up in 31e39123695 (Merge branch 'ab/refs-errno-cleanup', 2022-01-14) a small bug in that series was fixed. After those two there was one caller left in sequencer.c that used the "failure_errno', but as of the preceding commit it uses a boilerplate "ignore_errno" instead. This leaves the public refs API without any use of "failure_errno" at all. We could still do with a bit of cleanup and generalization between refs.c and refs/files-backend.c before the "reftable" integration lands, but that's all internal to the reference code itself. So let's remove this output parameter. Not only isn't it used now, but it's unlikely that we'll want it again in the future. We'd like to slowly move the refs API to a more file-backend independent way of communicating error codes, having it use a "failure_errno" was only the first step in that direction. If this or any other function needs to communicate what specifically is wrong with the requested "refname" it'll be better to have the function set some output enum of well-defined error states than piggy-backend on "errno". Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-01-26 15:37:01 +01:00
get_main_ref_store(repo), "HEAD", 0, NULL, &flag);
if (head_ref && (flag & REF_ISSYMREF) &&
skip_prefix(head_ref, "refs/heads/", &head_ref)) {
repo->remote_state->current_branch = make_branch(
repo->remote_state, head_ref, strlen(head_ref));
}
}
repo_config(repo, handle_config, repo->remote_state);
alias_all_urls(repo->remote_state);
}
static int valid_remote_nick(const char *name)
{
if (!name[0] || is_dot_or_dotdot(name))
return 0;
/* remote nicknames cannot contain slashes */
while (*name)
if (is_dir_sep(*name++))
return 0;
return 1;
}
static const char *remotes_remote_for_branch(struct remote_state *remote_state,
struct branch *branch,
int *explicit)
{
if (branch && branch->remote_name) {
if (explicit)
*explicit = 1;
return branch->remote_name;
}
if (explicit)
*explicit = 0;
if (remote_state->remotes_nr == 1)
return remote_state->remotes[0]->name;
return "origin";
}
const char *remote_for_branch(struct branch *branch, int *explicit)
{
read_config(the_repository);
die_on_missing_branch(the_repository, branch);
return remotes_remote_for_branch(the_repository->remote_state, branch,
explicit);
}
static const char *
remotes_pushremote_for_branch(struct remote_state *remote_state,
struct branch *branch, int *explicit)
{
if (branch && branch->pushremote_name) {
if (explicit)
*explicit = 1;
return branch->pushremote_name;
}
if (remote_state->pushremote_name) {
if (explicit)
*explicit = 1;
return remote_state->pushremote_name;
}
return remotes_remote_for_branch(remote_state, branch, explicit);
}
const char *pushremote_for_branch(struct branch *branch, int *explicit)
{
read_config(the_repository);
die_on_missing_branch(the_repository, branch);
return remotes_pushremote_for_branch(the_repository->remote_state,
branch, explicit);
}
static struct remote *remotes_remote_get(struct remote_state *remote_state,
const char *name);
const char *remote_ref_for_branch(struct branch *branch, int for_push)
{
read_config(the_repository);
die_on_missing_branch(the_repository, branch);
if (branch) {
if (!for_push) {
if (branch->merge_nr) {
return branch->merge_name[0];
}
} else {
const char *dst,
*remote_name = remotes_pushremote_for_branch(
the_repository->remote_state, branch,
NULL);
struct remote *remote = remotes_remote_get(
the_repository->remote_state, remote_name);
if (remote && remote->push.nr &&
(dst = apply_refspecs(&remote->push,
branch->refname))) {
return dst;
}
}
}
return NULL;
}
remote: create fetch.credentialsInUrl config Users sometimes provide a "username:password" combination in their plaintext URLs. Since Git stores these URLs in plaintext in the .git/config file, this is a very insecure way of storing these credentials. Credential managers are a more secure way of storing this information. System administrators might want to prevent this kind of use by users on their machines. Create a new "fetch.credentialsInUrl" config option and teach Git to warn or die when seeing a URL with this kind of information. The warning anonymizes the sensitive information of the URL to be clear about the issue. This change currently defaults the behavior to "allow" which does nothing with these URLs. We can consider changing this behavior to "warn" by default if we wish. At that time, we may want to add some advice about setting fetch.credentialsInUrl=ignore for users who still want to follow this pattern (and not receive the warning). An earlier version of this change injected the logic into url_normalize() in urlmatch.c. While most code paths that parse URLs eventually normalize the URL, that normalization does not happen early enough in the stack to avoid attempting connections to the URL first. By inserting a check into the remote validation, we identify the issue before making a connection. In the old code path, this was revealed by testing the new t5601-clone.sh test under --stress, resulting in an instance where the return code was 13 (SIGPIPE) instead of 128 from the die(). However, we can reuse the parsing information from url_normalize() in order to benefit from its well-worn parsing logic. We can use the struct url_info that is created in that method to replace the password with "<redacted>" in our error messages. This comes with a slight downside that the normalized URL might look slightly different from the input URL (for instance, the normalized version adds a closing slash). This should not hinder users figuring out what the problem is and being able to fix the issue. As an attempt to ensure the parsing logic did not catch any unintentional cases, I modified this change locally to to use the "die" option by default. Running the test suite succeeds except for the explicit username:password URLs used in t5550-http-fetch-dumb.sh and t5541-http-push-smart.sh. This means that all other tested URLs did not trigger this logic. The tests show that the proper error messages appear (or do not appear), but also count the number of error messages. When only warning, each process validates the remote URL and outputs a warning. This happens twice for clone, three times for fetch, and once for push. Helped-by: Junio C Hamano <gitster@pobox.com> Signed-off-by: Derrick Stolee <derrickstolee@github.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-06-06 16:36:16 +02:00
static void validate_remote_url(struct remote *remote)
{
int i;
const char *value;
struct strbuf redacted = STRBUF_INIT;
int warn_not_die;
if (git_config_get_string_tmp("transfer.credentialsinurl", &value))
remote: create fetch.credentialsInUrl config Users sometimes provide a "username:password" combination in their plaintext URLs. Since Git stores these URLs in plaintext in the .git/config file, this is a very insecure way of storing these credentials. Credential managers are a more secure way of storing this information. System administrators might want to prevent this kind of use by users on their machines. Create a new "fetch.credentialsInUrl" config option and teach Git to warn or die when seeing a URL with this kind of information. The warning anonymizes the sensitive information of the URL to be clear about the issue. This change currently defaults the behavior to "allow" which does nothing with these URLs. We can consider changing this behavior to "warn" by default if we wish. At that time, we may want to add some advice about setting fetch.credentialsInUrl=ignore for users who still want to follow this pattern (and not receive the warning). An earlier version of this change injected the logic into url_normalize() in urlmatch.c. While most code paths that parse URLs eventually normalize the URL, that normalization does not happen early enough in the stack to avoid attempting connections to the URL first. By inserting a check into the remote validation, we identify the issue before making a connection. In the old code path, this was revealed by testing the new t5601-clone.sh test under --stress, resulting in an instance where the return code was 13 (SIGPIPE) instead of 128 from the die(). However, we can reuse the parsing information from url_normalize() in order to benefit from its well-worn parsing logic. We can use the struct url_info that is created in that method to replace the password with "<redacted>" in our error messages. This comes with a slight downside that the normalized URL might look slightly different from the input URL (for instance, the normalized version adds a closing slash). This should not hinder users figuring out what the problem is and being able to fix the issue. As an attempt to ensure the parsing logic did not catch any unintentional cases, I modified this change locally to to use the "die" option by default. Running the test suite succeeds except for the explicit username:password URLs used in t5550-http-fetch-dumb.sh and t5541-http-push-smart.sh. This means that all other tested URLs did not trigger this logic. The tests show that the proper error messages appear (or do not appear), but also count the number of error messages. When only warning, each process validates the remote URL and outputs a warning. This happens twice for clone, three times for fetch, and once for push. Helped-by: Junio C Hamano <gitster@pobox.com> Signed-off-by: Derrick Stolee <derrickstolee@github.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-06-06 16:36:16 +02:00
return;
if (!strcmp("warn", value))
warn_not_die = 1;
else if (!strcmp("die", value))
warn_not_die = 0;
else if (!strcmp("allow", value))
return;
else
die(_("unrecognized value transfer.credentialsInUrl: '%s'"), value);
remote: create fetch.credentialsInUrl config Users sometimes provide a "username:password" combination in their plaintext URLs. Since Git stores these URLs in plaintext in the .git/config file, this is a very insecure way of storing these credentials. Credential managers are a more secure way of storing this information. System administrators might want to prevent this kind of use by users on their machines. Create a new "fetch.credentialsInUrl" config option and teach Git to warn or die when seeing a URL with this kind of information. The warning anonymizes the sensitive information of the URL to be clear about the issue. This change currently defaults the behavior to "allow" which does nothing with these URLs. We can consider changing this behavior to "warn" by default if we wish. At that time, we may want to add some advice about setting fetch.credentialsInUrl=ignore for users who still want to follow this pattern (and not receive the warning). An earlier version of this change injected the logic into url_normalize() in urlmatch.c. While most code paths that parse URLs eventually normalize the URL, that normalization does not happen early enough in the stack to avoid attempting connections to the URL first. By inserting a check into the remote validation, we identify the issue before making a connection. In the old code path, this was revealed by testing the new t5601-clone.sh test under --stress, resulting in an instance where the return code was 13 (SIGPIPE) instead of 128 from the die(). However, we can reuse the parsing information from url_normalize() in order to benefit from its well-worn parsing logic. We can use the struct url_info that is created in that method to replace the password with "<redacted>" in our error messages. This comes with a slight downside that the normalized URL might look slightly different from the input URL (for instance, the normalized version adds a closing slash). This should not hinder users figuring out what the problem is and being able to fix the issue. As an attempt to ensure the parsing logic did not catch any unintentional cases, I modified this change locally to to use the "die" option by default. Running the test suite succeeds except for the explicit username:password URLs used in t5550-http-fetch-dumb.sh and t5541-http-push-smart.sh. This means that all other tested URLs did not trigger this logic. The tests show that the proper error messages appear (or do not appear), but also count the number of error messages. When only warning, each process validates the remote URL and outputs a warning. This happens twice for clone, three times for fetch, and once for push. Helped-by: Junio C Hamano <gitster@pobox.com> Signed-off-by: Derrick Stolee <derrickstolee@github.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-06-06 16:36:16 +02:00
for (i = 0; i < remote->url_nr; i++) {
struct url_info url_info = { 0 };
if (!url_normalize(remote->url[i], &url_info) ||
!url_info.passwd_off)
goto loop_cleanup;
strbuf_reset(&redacted);
strbuf_add(&redacted, url_info.url, url_info.passwd_off);
strbuf_addstr(&redacted, "<redacted>");
strbuf_addstr(&redacted,
url_info.url + url_info.passwd_off + url_info.passwd_len);
if (warn_not_die)
warning(_("URL '%s' uses plaintext credentials"), redacted.buf);
else
die(_("URL '%s' uses plaintext credentials"), redacted.buf);
loop_cleanup:
free(url_info.url);
}
strbuf_release(&redacted);
}
static struct remote *
remotes_remote_get_1(struct remote_state *remote_state, const char *name,
const char *(*get_default)(struct remote_state *,
struct branch *, int *))
{
struct remote *ret;
int name_given = 0;
if (name)
name_given = 1;
else
name = get_default(remote_state, remote_state->current_branch,
&name_given);
ret = make_remote(remote_state, name, 0);
if (valid_remote_nick(name) && have_git_dir()) {
if (!valid_remote(ret))
read_remotes_file(remote_state, ret);
if (!valid_remote(ret))
read_branches_file(remote_state, ret);
}
if (name_given && !valid_remote(ret))
add_url_alias(remote_state, ret, name);
if (!valid_remote(ret))
return NULL;
remote: create fetch.credentialsInUrl config Users sometimes provide a "username:password" combination in their plaintext URLs. Since Git stores these URLs in plaintext in the .git/config file, this is a very insecure way of storing these credentials. Credential managers are a more secure way of storing this information. System administrators might want to prevent this kind of use by users on their machines. Create a new "fetch.credentialsInUrl" config option and teach Git to warn or die when seeing a URL with this kind of information. The warning anonymizes the sensitive information of the URL to be clear about the issue. This change currently defaults the behavior to "allow" which does nothing with these URLs. We can consider changing this behavior to "warn" by default if we wish. At that time, we may want to add some advice about setting fetch.credentialsInUrl=ignore for users who still want to follow this pattern (and not receive the warning). An earlier version of this change injected the logic into url_normalize() in urlmatch.c. While most code paths that parse URLs eventually normalize the URL, that normalization does not happen early enough in the stack to avoid attempting connections to the URL first. By inserting a check into the remote validation, we identify the issue before making a connection. In the old code path, this was revealed by testing the new t5601-clone.sh test under --stress, resulting in an instance where the return code was 13 (SIGPIPE) instead of 128 from the die(). However, we can reuse the parsing information from url_normalize() in order to benefit from its well-worn parsing logic. We can use the struct url_info that is created in that method to replace the password with "<redacted>" in our error messages. This comes with a slight downside that the normalized URL might look slightly different from the input URL (for instance, the normalized version adds a closing slash). This should not hinder users figuring out what the problem is and being able to fix the issue. As an attempt to ensure the parsing logic did not catch any unintentional cases, I modified this change locally to to use the "die" option by default. Running the test suite succeeds except for the explicit username:password URLs used in t5550-http-fetch-dumb.sh and t5541-http-push-smart.sh. This means that all other tested URLs did not trigger this logic. The tests show that the proper error messages appear (or do not appear), but also count the number of error messages. When only warning, each process validates the remote URL and outputs a warning. This happens twice for clone, three times for fetch, and once for push. Helped-by: Junio C Hamano <gitster@pobox.com> Signed-off-by: Derrick Stolee <derrickstolee@github.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-06-06 16:36:16 +02:00
validate_remote_url(ret);
return ret;
}
static inline struct remote *
remotes_remote_get(struct remote_state *remote_state, const char *name)
{
return remotes_remote_get_1(remote_state, name,
remotes_remote_for_branch);
}
struct remote *remote_get(const char *name)
{
read_config(the_repository);
return remotes_remote_get(the_repository->remote_state, name);
}
static inline struct remote *
remotes_pushremote_get(struct remote_state *remote_state, const char *name)
{
return remotes_remote_get_1(remote_state, name,
remotes_pushremote_for_branch);
}
struct remote *pushremote_get(const char *name)
{
read_config(the_repository);
return remotes_pushremote_get(the_repository->remote_state, name);
}
remote rename: more carefully determine whether a remote is configured One of the really nice features of the ~/.gitconfig file is that users can override defaults by their own preferred settings for all of their repositories. One such default that some users like to override is whether the "origin" remote gets auto-pruned or not. The user would simply call git config --global remote.origin.prune true and from now on all "origin" remotes would be pruned automatically when fetching into the local repository. There is just one catch: now Git thinks that the "origin" remote is configured, even if the repository config has no [remote "origin"] section at all, as it does not realize that the "prune" setting was configured globally and that there really is no "origin" remote configured in this repository. That is a problem e.g. when renaming a remote to a new name, when Git may be fooled into thinking that there is already a remote of that new name. Let's fix this by paying more attention to *where* the remote settings came from: if they are configured in the local repository config, we must not overwrite them. If they were configured elsewhere, we cannot overwrite them to begin with, as we only write the repository config. There is only one caller of remote_is_configured() (in `git fetch`) that may want to take remotes into account even if they were configured outside the repository config; all other callers essentially try to prevent the Git command from overwriting settings in the repository config. To accommodate that fact, the remote_is_configured() function now requires a parameter that states whether the caller is interested in all remotes, or only in those that were configured in the repository config. Many thanks to Jeff King whose tireless review helped with settling for nothing less than the current strategy. This fixes https://github.com/git-for-windows/git/issues/888 Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-01-19 22:20:02 +01:00
int remote_is_configured(struct remote *remote, int in_repo)
{
remote rename: more carefully determine whether a remote is configured One of the really nice features of the ~/.gitconfig file is that users can override defaults by their own preferred settings for all of their repositories. One such default that some users like to override is whether the "origin" remote gets auto-pruned or not. The user would simply call git config --global remote.origin.prune true and from now on all "origin" remotes would be pruned automatically when fetching into the local repository. There is just one catch: now Git thinks that the "origin" remote is configured, even if the repository config has no [remote "origin"] section at all, as it does not realize that the "prune" setting was configured globally and that there really is no "origin" remote configured in this repository. That is a problem e.g. when renaming a remote to a new name, when Git may be fooled into thinking that there is already a remote of that new name. Let's fix this by paying more attention to *where* the remote settings came from: if they are configured in the local repository config, we must not overwrite them. If they were configured elsewhere, we cannot overwrite them to begin with, as we only write the repository config. There is only one caller of remote_is_configured() (in `git fetch`) that may want to take remotes into account even if they were configured outside the repository config; all other callers essentially try to prevent the Git command from overwriting settings in the repository config. To accommodate that fact, the remote_is_configured() function now requires a parameter that states whether the caller is interested in all remotes, or only in those that were configured in the repository config. Many thanks to Jeff King whose tireless review helped with settling for nothing less than the current strategy. This fixes https://github.com/git-for-windows/git/issues/888 Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-01-19 22:20:02 +01:00
if (!remote)
return 0;
if (in_repo)
return remote->configured_in_repo;
return !!remote->origin;
}
int for_each_remote(each_remote_fn fn, void *priv)
{
int i, result = 0;
read_config(the_repository);
for (i = 0; i < the_repository->remote_state->remotes_nr && !result;
i++) {
struct remote *remote =
the_repository->remote_state->remotes[i];
if (!remote)
continue;
result = fn(remote, priv);
}
return result;
}
static void handle_duplicate(struct ref *ref1, struct ref *ref2)
{
if (strcmp(ref1->name, ref2->name)) {
if (ref1->fetch_head_status != FETCH_HEAD_IGNORE &&
ref2->fetch_head_status != FETCH_HEAD_IGNORE) {
die(_("Cannot fetch both %s and %s to %s"),
ref1->name, ref2->name, ref2->peer_ref->name);
} else if (ref1->fetch_head_status != FETCH_HEAD_IGNORE &&
ref2->fetch_head_status == FETCH_HEAD_IGNORE) {
warning(_("%s usually tracks %s, not %s"),
ref2->peer_ref->name, ref2->name, ref1->name);
} else if (ref1->fetch_head_status == FETCH_HEAD_IGNORE &&
ref2->fetch_head_status == FETCH_HEAD_IGNORE) {
die(_("%s tracks both %s and %s"),
ref2->peer_ref->name, ref1->name, ref2->name);
} else {
/*
* This last possibility doesn't occur because
* FETCH_HEAD_IGNORE entries always appear at
* the end of the list.
*/
BUG("Internal error");
}
}
free(ref2->peer_ref);
free(ref2);
}
struct ref *ref_remove_duplicates(struct ref *ref_map)
{
struct string_list refs = STRING_LIST_INIT_NODUP;
struct ref *retval = NULL;
struct ref **p = &retval;
while (ref_map) {
struct ref *ref = ref_map;
ref_map = ref_map->next;
ref->next = NULL;
if (!ref->peer_ref) {
*p = ref;
p = &ref->next;
} else {
struct string_list_item *item =
string_list_insert(&refs, ref->peer_ref->name);
if (item->util) {
/* Entry already existed */
handle_duplicate((struct ref *)item->util, ref);
} else {
*p = ref;
p = &ref->next;
item->util = ref;
}
}
}
string_list_clear(&refs, 0);
return retval;
}
int remote_has_url(struct remote *remote, const char *url)
{
int i;
for (i = 0; i < remote->url_nr; i++) {
if (!strcmp(remote->url[i], url))
return 1;
}
return 0;
}
static int match_name_with_pattern(const char *key, const char *name,
const char *value, char **result)
{
const char *kstar = strchr(key, '*');
size_t klen;
size_t ksuffixlen;
size_t namelen;
int ret;
if (!kstar)
die(_("key '%s' of pattern had no '*'"), key);
klen = kstar - key;
ksuffixlen = strlen(kstar + 1);
namelen = strlen(name);
ret = !strncmp(name, key, klen) && namelen >= klen + ksuffixlen &&
!memcmp(name + namelen - ksuffixlen, kstar + 1, ksuffixlen);
if (ret && value) {
struct strbuf sb = STRBUF_INIT;
const char *vstar = strchr(value, '*');
if (!vstar)
die(_("value '%s' of pattern has no '*'"), value);
strbuf_add(&sb, value, vstar - value);
strbuf_add(&sb, name + klen, namelen - klen - ksuffixlen);
strbuf_addstr(&sb, vstar + 1);
*result = strbuf_detach(&sb, NULL);
}
return ret;
}
refspec: add support for negative refspecs Both fetch and push support pattern refspecs which allow fetching or pushing references that match a specific pattern. Because these patterns are globs, they have somewhat limited ability to express more complex situations. For example, suppose you wish to fetch all branches from a remote except for a specific one. To allow this, you must setup a set of refspecs which match only the branches you want. Because refspecs are either explicit name matches, or simple globs, many patterns cannot be expressed. Add support for a new type of refspec, referred to as "negative" refspecs. These are prefixed with a '^' and mean "exclude any ref matching this refspec". They can only have one "side" which always refers to the source. During a fetch, this refers to the name of the ref on the remote. During a push, this refers to the name of the ref on the local side. With negative refspecs, users can express more complex patterns. For example: git fetch origin refs/heads/*:refs/remotes/origin/* ^refs/heads/dontwant will fetch all branches on origin into remotes/origin, but will exclude fetching the branch named dontwant. Refspecs today are commutative, meaning that order doesn't expressly matter. Rather than forcing an implied order, negative refspecs will always be applied last. That is, in order to match, a ref must match at least one positive refspec, and match none of the negative refspecs. This is similar to how negative pathspecs work. Signed-off-by: Jacob Keller <jacob.keller@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-09-30 23:25:29 +02:00
static int refspec_match(const struct refspec_item *refspec,
const char *name)
{
if (refspec->pattern)
return match_name_with_pattern(refspec->src, name, NULL, NULL);
return !strcmp(refspec->src, name);
}
remote: handle negative refspecs in git remote show By default, the git remote show command will query data from remotes to show data about what might be done on a future git fetch. This process currently does not handle negative refspecs. This can be confusing, because the show command will list refs as if they would be fetched. For example if the fetch refspec "^refs/heads/pr/*", it still displays the following: * remote jdk19 Fetch URL: git@github.com:openjdk/jdk19.git Push URL: git@github.com:openjdk/jdk19.git HEAD branch: master Remote branches: master tracked pr/1 new (next fetch will store in remotes/jdk19) pr/2 new (next fetch will store in remotes/jdk19) pr/3 new (next fetch will store in remotes/jdk19) Local ref configured for 'git push': master pushes to master (fast-forwardable) Fix this by adding an additional check inside of get_ref_states. If a ref matches one of the negative refspecs, mark it as skipped instead of marking it as new or tracked. With this change, we now report remote branches that are skipped due to negative refspecs properly: * remote jdk19 Fetch URL: git@github.com:openjdk/jdk19.git Push URL: git@github.com:openjdk/jdk19.git HEAD branch: master Remote branches: master tracked pr/1 skipped pr/2 skipped pr/3 skipped Local ref configured for 'git push': master pushes to master (fast-forwardable) By showing the refs as skipped, it helps clarify that these references won't actually be fetched. This does not properly handle refs going stale due to a newly added negative refspec. In addition, git remote prune doesn't handle that negative refspec case either. Fixing that requires digging into get_stale_heads and handling the case of a ref which exists on the remote but is omitted due to a negative refspec locally. Add a new test case which covers the functionality above, as well as a new expected failure indicating the poor overlap with stale refs. Reported-by: Pavel Rappo <pavel.rappo@gmail.com> Signed-off-by: Jacob Keller <jacob.e.keller@intel.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-06-17 02:20:31 +02:00
int omit_name_by_refspec(const char *name, struct refspec *rs)
refspec: add support for negative refspecs Both fetch and push support pattern refspecs which allow fetching or pushing references that match a specific pattern. Because these patterns are globs, they have somewhat limited ability to express more complex situations. For example, suppose you wish to fetch all branches from a remote except for a specific one. To allow this, you must setup a set of refspecs which match only the branches you want. Because refspecs are either explicit name matches, or simple globs, many patterns cannot be expressed. Add support for a new type of refspec, referred to as "negative" refspecs. These are prefixed with a '^' and mean "exclude any ref matching this refspec". They can only have one "side" which always refers to the source. During a fetch, this refers to the name of the ref on the remote. During a push, this refers to the name of the ref on the local side. With negative refspecs, users can express more complex patterns. For example: git fetch origin refs/heads/*:refs/remotes/origin/* ^refs/heads/dontwant will fetch all branches on origin into remotes/origin, but will exclude fetching the branch named dontwant. Refspecs today are commutative, meaning that order doesn't expressly matter. Rather than forcing an implied order, negative refspecs will always be applied last. That is, in order to match, a ref must match at least one positive refspec, and match none of the negative refspecs. This is similar to how negative pathspecs work. Signed-off-by: Jacob Keller <jacob.keller@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-09-30 23:25:29 +02:00
{
int i;
for (i = 0; i < rs->nr; i++) {
if (rs->items[i].negative && refspec_match(&rs->items[i], name))
return 1;
}
return 0;
}
struct ref *apply_negative_refspecs(struct ref *ref_map, struct refspec *rs)
{
struct ref **tail;
for (tail = &ref_map; *tail; ) {
struct ref *ref = *tail;
if (omit_name_by_refspec(ref->name, rs)) {
*tail = ref->next;
free(ref->peer_ref);
free(ref);
} else
tail = &ref->next;
}
return ref_map;
}
static int query_matches_negative_refspec(struct refspec *rs, struct refspec_item *query)
{
int i, matched_negative = 0;
int find_src = !query->src;
struct string_list reversed = STRING_LIST_INIT_NODUP;
const char *needle = find_src ? query->dst : query->src;
/*
* Check whether the queried ref matches any negative refpsec. If so,
* then we should ultimately treat this as not matching the query at
* all.
*
* Note that negative refspecs always match the source, but the query
* item uses the destination. To handle this, we apply pattern
* refspecs in reverse to figure out if the query source matches any
* of the negative refspecs.
*
* The first loop finds and expands all positive refspecs
* matched by the queried ref.
*
* The second loop checks if any of the results of the first loop
* match any negative refspec.
refspec: add support for negative refspecs Both fetch and push support pattern refspecs which allow fetching or pushing references that match a specific pattern. Because these patterns are globs, they have somewhat limited ability to express more complex situations. For example, suppose you wish to fetch all branches from a remote except for a specific one. To allow this, you must setup a set of refspecs which match only the branches you want. Because refspecs are either explicit name matches, or simple globs, many patterns cannot be expressed. Add support for a new type of refspec, referred to as "negative" refspecs. These are prefixed with a '^' and mean "exclude any ref matching this refspec". They can only have one "side" which always refers to the source. During a fetch, this refers to the name of the ref on the remote. During a push, this refers to the name of the ref on the local side. With negative refspecs, users can express more complex patterns. For example: git fetch origin refs/heads/*:refs/remotes/origin/* ^refs/heads/dontwant will fetch all branches on origin into remotes/origin, but will exclude fetching the branch named dontwant. Refspecs today are commutative, meaning that order doesn't expressly matter. Rather than forcing an implied order, negative refspecs will always be applied last. That is, in order to match, a ref must match at least one positive refspec, and match none of the negative refspecs. This is similar to how negative pathspecs work. Signed-off-by: Jacob Keller <jacob.keller@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-09-30 23:25:29 +02:00
*/
for (i = 0; i < rs->nr; i++) {
struct refspec_item *refspec = &rs->items[i];
char *expn_name;
if (refspec->negative)
continue;
/* Note the reversal of src and dst */
if (refspec->pattern) {
const char *key = refspec->dst ? refspec->dst : refspec->src;
const char *value = refspec->src;
if (match_name_with_pattern(key, needle, value, &expn_name))
string_list_append_nodup(&reversed, expn_name);
} else if (refspec->matching) {
/* For the special matching refspec, any query should match */
string_list_append(&reversed, needle);
} else if (!refspec->src) {
BUG("refspec->src should not be null here");
} else if (!strcmp(needle, refspec->src)) {
string_list_append(&reversed, refspec->src);
refspec: add support for negative refspecs Both fetch and push support pattern refspecs which allow fetching or pushing references that match a specific pattern. Because these patterns are globs, they have somewhat limited ability to express more complex situations. For example, suppose you wish to fetch all branches from a remote except for a specific one. To allow this, you must setup a set of refspecs which match only the branches you want. Because refspecs are either explicit name matches, or simple globs, many patterns cannot be expressed. Add support for a new type of refspec, referred to as "negative" refspecs. These are prefixed with a '^' and mean "exclude any ref matching this refspec". They can only have one "side" which always refers to the source. During a fetch, this refers to the name of the ref on the remote. During a push, this refers to the name of the ref on the local side. With negative refspecs, users can express more complex patterns. For example: git fetch origin refs/heads/*:refs/remotes/origin/* ^refs/heads/dontwant will fetch all branches on origin into remotes/origin, but will exclude fetching the branch named dontwant. Refspecs today are commutative, meaning that order doesn't expressly matter. Rather than forcing an implied order, negative refspecs will always be applied last. That is, in order to match, a ref must match at least one positive refspec, and match none of the negative refspecs. This is similar to how negative pathspecs work. Signed-off-by: Jacob Keller <jacob.keller@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-09-30 23:25:29 +02:00
}
}
for (i = 0; !matched_negative && i < reversed.nr; i++) {
if (omit_name_by_refspec(reversed.items[i].string, rs))
matched_negative = 1;
}
string_list_clear(&reversed, 0);
return matched_negative;
}
static void query_refspecs_multiple(struct refspec *rs,
struct refspec_item *query,
struct string_list *results)
{
int i;
int find_src = !query->src;
if (find_src && !query->dst)
BUG("query_refspecs_multiple: need either src or dst");
refspec: add support for negative refspecs Both fetch and push support pattern refspecs which allow fetching or pushing references that match a specific pattern. Because these patterns are globs, they have somewhat limited ability to express more complex situations. For example, suppose you wish to fetch all branches from a remote except for a specific one. To allow this, you must setup a set of refspecs which match only the branches you want. Because refspecs are either explicit name matches, or simple globs, many patterns cannot be expressed. Add support for a new type of refspec, referred to as "negative" refspecs. These are prefixed with a '^' and mean "exclude any ref matching this refspec". They can only have one "side" which always refers to the source. During a fetch, this refers to the name of the ref on the remote. During a push, this refers to the name of the ref on the local side. With negative refspecs, users can express more complex patterns. For example: git fetch origin refs/heads/*:refs/remotes/origin/* ^refs/heads/dontwant will fetch all branches on origin into remotes/origin, but will exclude fetching the branch named dontwant. Refspecs today are commutative, meaning that order doesn't expressly matter. Rather than forcing an implied order, negative refspecs will always be applied last. That is, in order to match, a ref must match at least one positive refspec, and match none of the negative refspecs. This is similar to how negative pathspecs work. Signed-off-by: Jacob Keller <jacob.keller@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-09-30 23:25:29 +02:00
if (query_matches_negative_refspec(rs, query))
return;
for (i = 0; i < rs->nr; i++) {
struct refspec_item *refspec = &rs->items[i];
const char *key = find_src ? refspec->dst : refspec->src;
const char *value = find_src ? refspec->src : refspec->dst;
const char *needle = find_src ? query->dst : query->src;
char **result = find_src ? &query->src : &query->dst;
refspec: add support for negative refspecs Both fetch and push support pattern refspecs which allow fetching or pushing references that match a specific pattern. Because these patterns are globs, they have somewhat limited ability to express more complex situations. For example, suppose you wish to fetch all branches from a remote except for a specific one. To allow this, you must setup a set of refspecs which match only the branches you want. Because refspecs are either explicit name matches, or simple globs, many patterns cannot be expressed. Add support for a new type of refspec, referred to as "negative" refspecs. These are prefixed with a '^' and mean "exclude any ref matching this refspec". They can only have one "side" which always refers to the source. During a fetch, this refers to the name of the ref on the remote. During a push, this refers to the name of the ref on the local side. With negative refspecs, users can express more complex patterns. For example: git fetch origin refs/heads/*:refs/remotes/origin/* ^refs/heads/dontwant will fetch all branches on origin into remotes/origin, but will exclude fetching the branch named dontwant. Refspecs today are commutative, meaning that order doesn't expressly matter. Rather than forcing an implied order, negative refspecs will always be applied last. That is, in order to match, a ref must match at least one positive refspec, and match none of the negative refspecs. This is similar to how negative pathspecs work. Signed-off-by: Jacob Keller <jacob.keller@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-09-30 23:25:29 +02:00
if (!refspec->dst || refspec->negative)
continue;
if (refspec->pattern) {
if (match_name_with_pattern(key, needle, value, result))
string_list_append_nodup(results, *result);
} else if (!strcmp(needle, key)) {
string_list_append(results, value);
}
}
}
int query_refspecs(struct refspec *rs, struct refspec_item *query)
{
int i;
int find_src = !query->src;
const char *needle = find_src ? query->dst : query->src;
char **result = find_src ? &query->src : &query->dst;
if (find_src && !query->dst)
BUG("query_refspecs: need either src or dst");
refspec: add support for negative refspecs Both fetch and push support pattern refspecs which allow fetching or pushing references that match a specific pattern. Because these patterns are globs, they have somewhat limited ability to express more complex situations. For example, suppose you wish to fetch all branches from a remote except for a specific one. To allow this, you must setup a set of refspecs which match only the branches you want. Because refspecs are either explicit name matches, or simple globs, many patterns cannot be expressed. Add support for a new type of refspec, referred to as "negative" refspecs. These are prefixed with a '^' and mean "exclude any ref matching this refspec". They can only have one "side" which always refers to the source. During a fetch, this refers to the name of the ref on the remote. During a push, this refers to the name of the ref on the local side. With negative refspecs, users can express more complex patterns. For example: git fetch origin refs/heads/*:refs/remotes/origin/* ^refs/heads/dontwant will fetch all branches on origin into remotes/origin, but will exclude fetching the branch named dontwant. Refspecs today are commutative, meaning that order doesn't expressly matter. Rather than forcing an implied order, negative refspecs will always be applied last. That is, in order to match, a ref must match at least one positive refspec, and match none of the negative refspecs. This is similar to how negative pathspecs work. Signed-off-by: Jacob Keller <jacob.keller@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-09-30 23:25:29 +02:00
if (query_matches_negative_refspec(rs, query))
return -1;
for (i = 0; i < rs->nr; i++) {
struct refspec_item *refspec = &rs->items[i];
const char *key = find_src ? refspec->dst : refspec->src;
const char *value = find_src ? refspec->src : refspec->dst;
refspec: add support for negative refspecs Both fetch and push support pattern refspecs which allow fetching or pushing references that match a specific pattern. Because these patterns are globs, they have somewhat limited ability to express more complex situations. For example, suppose you wish to fetch all branches from a remote except for a specific one. To allow this, you must setup a set of refspecs which match only the branches you want. Because refspecs are either explicit name matches, or simple globs, many patterns cannot be expressed. Add support for a new type of refspec, referred to as "negative" refspecs. These are prefixed with a '^' and mean "exclude any ref matching this refspec". They can only have one "side" which always refers to the source. During a fetch, this refers to the name of the ref on the remote. During a push, this refers to the name of the ref on the local side. With negative refspecs, users can express more complex patterns. For example: git fetch origin refs/heads/*:refs/remotes/origin/* ^refs/heads/dontwant will fetch all branches on origin into remotes/origin, but will exclude fetching the branch named dontwant. Refspecs today are commutative, meaning that order doesn't expressly matter. Rather than forcing an implied order, negative refspecs will always be applied last. That is, in order to match, a ref must match at least one positive refspec, and match none of the negative refspecs. This is similar to how negative pathspecs work. Signed-off-by: Jacob Keller <jacob.keller@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-09-30 23:25:29 +02:00
if (!refspec->dst || refspec->negative)
continue;
if (refspec->pattern) {
if (match_name_with_pattern(key, needle, value, result)) {
query->force = refspec->force;
return 0;
}
} else if (!strcmp(needle, key)) {
*result = xstrdup(value);
query->force = refspec->force;
return 0;
}
}
return -1;
}
char *apply_refspecs(struct refspec *rs, const char *name)
{
struct refspec_item query;
memset(&query, 0, sizeof(struct refspec_item));
query.src = (char *)name;
if (query_refspecs(rs, &query))
return NULL;
return query.dst;
}
int remote_find_tracking(struct remote *remote, struct refspec_item *refspec)
{
return query_refspecs(&remote->fetch, refspec);
}
static struct ref *alloc_ref_with_prefix(const char *prefix, size_t prefixlen,
const char *name)
{
size_t len = strlen(name);
struct ref *ref = xcalloc(1, st_add4(sizeof(*ref), prefixlen, len, 1));
memcpy(ref->name, prefix, prefixlen);
memcpy(ref->name + prefixlen, name, len);
return ref;
}
struct ref *alloc_ref(const char *name)
{
return alloc_ref_with_prefix("", 0, name);
}
struct ref *copy_ref(const struct ref *ref)
{
struct ref *cpy;
size_t len;
if (!ref)
return NULL;
len = st_add3(sizeof(struct ref), strlen(ref->name), 1);
cpy = xmalloc(len);
memcpy(cpy, ref, len);
cpy->next = NULL;
cpy->symref = xstrdup_or_null(ref->symref);
cpy->remote_status = xstrdup_or_null(ref->remote_status);
cpy->peer_ref = copy_ref(ref->peer_ref);
return cpy;
}
struct ref *copy_ref_list(const struct ref *ref)
{
struct ref *ret = NULL;
struct ref **tail = &ret;
while (ref) {
*tail = copy_ref(ref);
ref = ref->next;
tail = &((*tail)->next);
}
return ret;
}
void free_one_ref(struct ref *ref)
{
if (!ref)
return;
free_one_ref(ref->peer_ref);
free(ref->remote_status);
free(ref->symref);
free(ref);
}
void free_refs(struct ref *ref)
{
struct ref *next;
while (ref) {
next = ref->next;
free_one_ref(ref);
ref = next;
}
}
push: use remote.$name.push as a refmap Since f2690487 (fetch: opportunistically update tracking refs, 2013-05-11), we stopped taking a non-storing refspec given on the command line of "git fetch" literally, and instead started mapping it via remote.$name.fetch refspecs. This allows $ git fetch origin master from the 'origin' repository, which is configured with [remote "origin"] fetch = +refs/heads/*:refs/remotes/origin/* to update refs/remotes/origin/master with the result, as if the command line were $ git fetch origin +master:refs/remotes/origin/master to reduce surprises and improve usability. Before that change, a refspec on the command line without a colon was only to fetch the history and leave the result in FETCH_HEAD, without updating the remote-tracking branches. When you are simulating a fetch from you by your mothership with a push by you into your mothership, instead of having: [remote "satellite"] fetch = +refs/heads/*:refs/remotes/satellite/* on the mothership repository and running: mothership$ git fetch satellite you would have: [remote "mothership"] push = +refs/heads/*:refs/remotes/satellite/* on your satellite machine, and run: satellite$ git push mothership Because we so far did not make the corresponding change to the push side, this command: satellite$ git push mothership master does _not_ allow you on the satellite to only push 'master' out but still to the usual destination (i.e. refs/remotes/satellite/master). Implement the logic to map an unqualified refspec given on the command line via the remote.$name.push refspec. This will bring a bit more symmetry between "fetch" and "push". Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-12-04 00:41:15 +01:00
int count_refspec_match(const char *pattern,
struct ref *refs,
struct ref **matched_ref)
{
int patlen = strlen(pattern);
struct ref *matched_weak = NULL;
struct ref *matched = NULL;
int weak_match = 0;
int match = 0;
for (weak_match = match = 0; refs; refs = refs->next) {
char *name = refs->name;
int namelen = strlen(name);
if (!refname_match(pattern, name))
continue;
/* A match is "weak" if it is with refs outside
* heads or tags, and did not specify the pattern
* in full (e.g. "refs/remotes/origin/master") or at
* least from the toplevel (e.g. "remotes/origin/master");
* otherwise "git push $URL master" would result in
* ambiguity between remotes/origin/master and heads/master
* at the remote site.
*/
if (namelen != patlen &&
patlen != namelen - 5 &&
!starts_with(name, "refs/heads/") &&
!starts_with(name, "refs/tags/")) {
/* We want to catch the case where only weak
* matches are found and there are multiple
* matches, and where more than one strong
* matches are found, as ambiguous. One
* strong match with zero or more weak matches
* are acceptable as a unique match.
*/
matched_weak = refs;
weak_match++;
}
else {
matched = refs;
match++;
}
}
if (!matched) {
if (matched_ref)
*matched_ref = matched_weak;
return weak_match;
}
else {
if (matched_ref)
*matched_ref = matched;
return match;
}
}
static void tail_link_ref(struct ref *ref, struct ref ***tail)
{
**tail = ref;
while (ref->next)
ref = ref->next;
*tail = &ref->next;
}
static struct ref *alloc_delete_ref(void)
{
struct ref *ref = alloc_ref("(delete)");
oidclr(&ref->new_oid);
return ref;
}
static int try_explicit_object_name(const char *name,
struct ref **match)
{
struct object_id oid;
if (!*name) {
if (match)
*match = alloc_delete_ref();
return 0;
}
if (repo_get_oid(the_repository, name, &oid))
return -1;
if (match) {
*match = alloc_ref(name);
oidcpy(&(*match)->new_oid, &oid);
}
return 0;
}
static struct ref *make_linked_ref(const char *name, struct ref ***tail)
{
struct ref *ret = alloc_ref(name);
tail_link_ref(ret, tail);
return ret;
}
static char *guess_ref(const char *name, struct ref *peer)
{
struct strbuf buf = STRBUF_INIT;
const char *r = resolve_ref_unsafe(peer->name, RESOLVE_REF_READING,
NULL, NULL);
if (!r)
return NULL;
if (starts_with(r, "refs/heads/")) {
strbuf_addstr(&buf, "refs/heads/");
} else if (starts_with(r, "refs/tags/")) {
strbuf_addstr(&buf, "refs/tags/");
} else {
return NULL;
}
strbuf_addstr(&buf, name);
return strbuf_detach(&buf, NULL);
}
static int match_explicit_lhs(struct ref *src,
struct refspec_item *rs,
struct ref **match,
int *allocated_match)
{
switch (count_refspec_match(rs->src, src, match)) {
case 1:
if (allocated_match)
*allocated_match = 0;
return 0;
case 0:
/* The source could be in the get_sha1() format
* not a reference name. :refs/other is a
* way to delete 'other' ref at the remote end.
*/
if (try_explicit_object_name(rs->src, match) < 0)
return error(_("src refspec %s does not match any"), rs->src);
if (allocated_match)
*allocated_match = 1;
return 0;
default:
return error(_("src refspec %s matches more than one"), rs->src);
}
}
static void show_push_unqualified_ref_name_error(const char *dst_value,
const char *matched_src_name)
{
struct object_id oid;
enum object_type type;
/*
* TRANSLATORS: "matches '%s'%" is the <dst> part of "git push
* <remote> <src>:<dst>" push, and "being pushed ('%s')" is
* the <src>.
*/
error(_("The destination you provided is not a full refname (i.e.,\n"
"starting with \"refs/\"). We tried to guess what you meant by:\n"
"\n"
"- Looking for a ref that matches '%s' on the remote side.\n"
"- Checking if the <src> being pushed ('%s')\n"
" is a ref in \"refs/{heads,tags}/\". If so we add a corresponding\n"
" refs/{heads,tags}/ prefix on the remote side.\n"
"\n"
"Neither worked, so we gave up. You must fully qualify the ref."),
dst_value, matched_src_name);
if (!advice_enabled(ADVICE_PUSH_UNQUALIFIED_REF_NAME))
return;
if (repo_get_oid(the_repository, matched_src_name, &oid))
BUG("'%s' is not a valid object, "
"match_explicit_lhs() should catch this!",
matched_src_name);
type = oid_object_info(the_repository, &oid, NULL);
if (type == OBJ_COMMIT) {
advise(_("The <src> part of the refspec is a commit object.\n"
"Did you mean to create a new branch by pushing to\n"
"'%s:refs/heads/%s'?"),
matched_src_name, dst_value);
} else if (type == OBJ_TAG) {
advise(_("The <src> part of the refspec is a tag object.\n"
"Did you mean to create a new tag by pushing to\n"
"'%s:refs/tags/%s'?"),
matched_src_name, dst_value);
} else if (type == OBJ_TREE) {
advise(_("The <src> part of the refspec is a tree object.\n"
"Did you mean to tag a new tree by pushing to\n"
"'%s:refs/tags/%s'?"),
matched_src_name, dst_value);
} else if (type == OBJ_BLOB) {
advise(_("The <src> part of the refspec is a blob object.\n"
"Did you mean to tag a new blob by pushing to\n"
"'%s:refs/tags/%s'?"),
matched_src_name, dst_value);
} else {
BUG("'%s' should be commit/tag/tree/blob, is '%d'",
matched_src_name, type);
}
}
static int match_explicit(struct ref *src, struct ref *dst,
struct ref ***dst_tail,
struct refspec_item *rs)
{
struct ref *matched_src, *matched_dst;
int allocated_src;
const char *dst_value = rs->dst;
char *dst_guess;
refspec: add support for negative refspecs Both fetch and push support pattern refspecs which allow fetching or pushing references that match a specific pattern. Because these patterns are globs, they have somewhat limited ability to express more complex situations. For example, suppose you wish to fetch all branches from a remote except for a specific one. To allow this, you must setup a set of refspecs which match only the branches you want. Because refspecs are either explicit name matches, or simple globs, many patterns cannot be expressed. Add support for a new type of refspec, referred to as "negative" refspecs. These are prefixed with a '^' and mean "exclude any ref matching this refspec". They can only have one "side" which always refers to the source. During a fetch, this refers to the name of the ref on the remote. During a push, this refers to the name of the ref on the local side. With negative refspecs, users can express more complex patterns. For example: git fetch origin refs/heads/*:refs/remotes/origin/* ^refs/heads/dontwant will fetch all branches on origin into remotes/origin, but will exclude fetching the branch named dontwant. Refspecs today are commutative, meaning that order doesn't expressly matter. Rather than forcing an implied order, negative refspecs will always be applied last. That is, in order to match, a ref must match at least one positive refspec, and match none of the negative refspecs. This is similar to how negative pathspecs work. Signed-off-by: Jacob Keller <jacob.keller@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-09-30 23:25:29 +02:00
if (rs->pattern || rs->matching || rs->negative)
clean up error conventions of remote.c:match_explicit match_explicit is called for each push refspec to try to fully resolve the source and destination sides of the refspec. Currently, we look at each refspec and report errors on both the source and the dest side before aborting. It makes sense to report errors for each refspec, since an error in one is independent of an error in the other. However, reporting errors on the 'dst' side of a refspec if there has been an error on the 'src' side does not necessarily make sense, since the interpretation of the 'dst' side depends on the 'src' side (for example, when creating a new unqualified remote ref, we use the same type as the src ref). This patch lets match_explicit return early when the src side of the refspec is bogus. We still look at all of the refspecs before aborting the push, though. At the same time, we clean up the call signature, which previously took an extra "errs" flag. This was pointless, as we didn't act on that flag, but rather just passed it back to the caller. Instead, we now use the more traditional "return -1" to signal an error, and the caller aggregates the error count. This change fixes two bugs, as well: - the early return avoids a segfault when passing a NULL matched_src to guess_ref() - the check for multiple sources pointing to a single dest aborted if the "err" flag was set. Presumably the intent was not to bother with the check if we had no matched_src. However, since the err flag was passed in from the caller, we might abort the check just because a previous refspec had a problem, which doesn't make sense. In practice, this didn't matter, since due to the error flag we end up aborting the push anyway. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2008-06-16 18:15:02 +02:00
return 0;
matched_src = matched_dst = NULL;
if (match_explicit_lhs(src, rs, &matched_src, &allocated_src) < 0)
return -1;
if (!dst_value) {
int flag;
dst_value = resolve_ref_unsafe(matched_src->name,
RESOLVE_REF_READING,
NULL, &flag);
if (!dst_value ||
((flag & REF_ISSYMREF) &&
!starts_with(dst_value, "refs/heads/")))
die(_("%s cannot be resolved to branch"),
matched_src->name);
}
switch (count_refspec_match(dst_value, dst, &matched_dst)) {
case 1:
break;
case 0:
if (starts_with(dst_value, "refs/")) {
matched_dst = make_linked_ref(dst_value, dst_tail);
} else if (is_null_oid(&matched_src->new_oid)) {
error(_("unable to delete '%s': remote ref does not exist"),
push: don't guess at qualifying remote refs on deletion When we try to push a ref and the right-hand side of the refspec does not find a match, we try to create it. If it is not fully qualified, we try to guess where it would go in the refs hierarchy based on the left-hand source side. If the source side is not a ref, then we give up and give a long explanatory message. For deletions, however, this doesn't make any sense. We would never want to create on the remote side, and if an unqualified ref can't be matched, it is simply an error. The current code handles this already because the left-hand side is empty, and therefore does not give us a hint as to where the right-hand side should go, and we properly error out. Unfortunately, the error message is the long "we tried to qualify this, but the source side didn't let us guess" message, which is quite confusing. Instead, we can just be more succinct and say "we can't delete this because we couldn't find it". So before: $ git push origin :bogus error: unable to push to unqualified destination: bogus The destination refspec neither matches an existing ref on the remote nor begins with refs/, and we are unable to guess a prefix based on the source ref. error: failed to push some refs to '$URL' and now: $ git push origin :bogus error: unable to delete 'bogus': remote ref does not exist error: failed to push some refs to '$URL' It is tempting to also catch a fully-qualified ref like "refs/heads/bogus" and generate the same error message. However, that currently does not error out at all, and instead gets sent to the remote side, which typically generates a warning: $ git push origin:refs/heads/bogus remote: warning: Deleting a non-existent ref. To $URL - [deleted] bogus While it would be nice to catch this error early, a client-side error would mean aborting the push entirely and changing push's exit code. For example, right now you can do: $ git push origin refs/heads/foo refs/heads/bar and end up in a state where "foo" and "bar" are deleted, whether both of them currently exist or not (and see an error only if we actually failed to contact the server). Generating an error would cause a regression for this use case. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2012-07-03 20:04:39 +02:00
dst_value);
} else if ((dst_guess = guess_ref(dst_value, matched_src))) {
matched_dst = make_linked_ref(dst_guess, dst_tail);
free(dst_guess);
} else {
show_push_unqualified_ref_name_error(dst_value,
matched_src->name);
}
break;
default:
matched_dst = NULL;
error(_("dst refspec %s matches more than one"),
dst_value);
break;
}
clean up error conventions of remote.c:match_explicit match_explicit is called for each push refspec to try to fully resolve the source and destination sides of the refspec. Currently, we look at each refspec and report errors on both the source and the dest side before aborting. It makes sense to report errors for each refspec, since an error in one is independent of an error in the other. However, reporting errors on the 'dst' side of a refspec if there has been an error on the 'src' side does not necessarily make sense, since the interpretation of the 'dst' side depends on the 'src' side (for example, when creating a new unqualified remote ref, we use the same type as the src ref). This patch lets match_explicit return early when the src side of the refspec is bogus. We still look at all of the refspecs before aborting the push, though. At the same time, we clean up the call signature, which previously took an extra "errs" flag. This was pointless, as we didn't act on that flag, but rather just passed it back to the caller. Instead, we now use the more traditional "return -1" to signal an error, and the caller aggregates the error count. This change fixes two bugs, as well: - the early return avoids a segfault when passing a NULL matched_src to guess_ref() - the check for multiple sources pointing to a single dest aborted if the "err" flag was set. Presumably the intent was not to bother with the check if we had no matched_src. However, since the err flag was passed in from the caller, we might abort the check just because a previous refspec had a problem, which doesn't make sense. In practice, this didn't matter, since due to the error flag we end up aborting the push anyway. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2008-06-16 18:15:02 +02:00
if (!matched_dst)
return -1;
if (matched_dst->peer_ref)
return error(_("dst ref %s receives from more than one src"),
matched_dst->name);
else {
matched_dst->peer_ref = allocated_src ?
matched_src :
copy_ref(matched_src);
matched_dst->force = rs->force;
}
clean up error conventions of remote.c:match_explicit match_explicit is called for each push refspec to try to fully resolve the source and destination sides of the refspec. Currently, we look at each refspec and report errors on both the source and the dest side before aborting. It makes sense to report errors for each refspec, since an error in one is independent of an error in the other. However, reporting errors on the 'dst' side of a refspec if there has been an error on the 'src' side does not necessarily make sense, since the interpretation of the 'dst' side depends on the 'src' side (for example, when creating a new unqualified remote ref, we use the same type as the src ref). This patch lets match_explicit return early when the src side of the refspec is bogus. We still look at all of the refspecs before aborting the push, though. At the same time, we clean up the call signature, which previously took an extra "errs" flag. This was pointless, as we didn't act on that flag, but rather just passed it back to the caller. Instead, we now use the more traditional "return -1" to signal an error, and the caller aggregates the error count. This change fixes two bugs, as well: - the early return avoids a segfault when passing a NULL matched_src to guess_ref() - the check for multiple sources pointing to a single dest aborted if the "err" flag was set. Presumably the intent was not to bother with the check if we had no matched_src. However, since the err flag was passed in from the caller, we might abort the check just because a previous refspec had a problem, which doesn't make sense. In practice, this didn't matter, since due to the error flag we end up aborting the push anyway. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2008-06-16 18:15:02 +02:00
return 0;
}
static int match_explicit_refs(struct ref *src, struct ref *dst,
struct ref ***dst_tail, struct refspec *rs)
{
int i, errs;
for (i = errs = 0; i < rs->nr; i++)
errs += match_explicit(src, dst, dst_tail, &rs->items[i]);
clean up error conventions of remote.c:match_explicit match_explicit is called for each push refspec to try to fully resolve the source and destination sides of the refspec. Currently, we look at each refspec and report errors on both the source and the dest side before aborting. It makes sense to report errors for each refspec, since an error in one is independent of an error in the other. However, reporting errors on the 'dst' side of a refspec if there has been an error on the 'src' side does not necessarily make sense, since the interpretation of the 'dst' side depends on the 'src' side (for example, when creating a new unqualified remote ref, we use the same type as the src ref). This patch lets match_explicit return early when the src side of the refspec is bogus. We still look at all of the refspecs before aborting the push, though. At the same time, we clean up the call signature, which previously took an extra "errs" flag. This was pointless, as we didn't act on that flag, but rather just passed it back to the caller. Instead, we now use the more traditional "return -1" to signal an error, and the caller aggregates the error count. This change fixes two bugs, as well: - the early return avoids a segfault when passing a NULL matched_src to guess_ref() - the check for multiple sources pointing to a single dest aborted if the "err" flag was set. Presumably the intent was not to bother with the check if we had no matched_src. However, since the err flag was passed in from the caller, we might abort the check just because a previous refspec had a problem, which doesn't make sense. In practice, this didn't matter, since due to the error flag we end up aborting the push anyway. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2008-06-16 18:15:02 +02:00
return errs;
}
static char *get_ref_match(const struct refspec *rs, const struct ref *ref,
int send_mirror, int direction,
const struct refspec_item **ret_pat)
{
const struct refspec_item *pat;
char *name;
int i;
int matching_refs = -1;
for (i = 0; i < rs->nr; i++) {
const struct refspec_item *item = &rs->items[i];
refspec: add support for negative refspecs Both fetch and push support pattern refspecs which allow fetching or pushing references that match a specific pattern. Because these patterns are globs, they have somewhat limited ability to express more complex situations. For example, suppose you wish to fetch all branches from a remote except for a specific one. To allow this, you must setup a set of refspecs which match only the branches you want. Because refspecs are either explicit name matches, or simple globs, many patterns cannot be expressed. Add support for a new type of refspec, referred to as "negative" refspecs. These are prefixed with a '^' and mean "exclude any ref matching this refspec". They can only have one "side" which always refers to the source. During a fetch, this refers to the name of the ref on the remote. During a push, this refers to the name of the ref on the local side. With negative refspecs, users can express more complex patterns. For example: git fetch origin refs/heads/*:refs/remotes/origin/* ^refs/heads/dontwant will fetch all branches on origin into remotes/origin, but will exclude fetching the branch named dontwant. Refspecs today are commutative, meaning that order doesn't expressly matter. Rather than forcing an implied order, negative refspecs will always be applied last. That is, in order to match, a ref must match at least one positive refspec, and match none of the negative refspecs. This is similar to how negative pathspecs work. Signed-off-by: Jacob Keller <jacob.keller@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-09-30 23:25:29 +02:00
if (item->negative)
continue;
if (item->matching &&
(matching_refs == -1 || item->force)) {
matching_refs = i;
continue;
}
if (item->pattern) {
const char *dst_side = item->dst ? item->dst : item->src;
int match;
if (direction == FROM_SRC)
match = match_name_with_pattern(item->src, ref->name, dst_side, &name);
else
match = match_name_with_pattern(dst_side, ref->name, item->src, &name);
if (match) {
matching_refs = i;
break;
}
}
}
if (matching_refs == -1)
return NULL;
pat = &rs->items[matching_refs];
if (pat->matching) {
/*
* "matching refs"; traditionally we pushed everything
* including refs outside refs/heads/ hierarchy, but
* that does not make much sense these days.
*/
if (!send_mirror && !starts_with(ref->name, "refs/heads/"))
return NULL;
name = xstrdup(ref->name);
}
if (ret_pat)
*ret_pat = pat;
return name;
}
static struct ref **tail_ref(struct ref **head)
{
struct ref **tail = head;
while (*tail)
tail = &((*tail)->next);
return tail;
}
struct tips {
struct commit **tip;
int nr, alloc;
};
static void add_to_tips(struct tips *tips, const struct object_id *oid)
{
struct commit *commit;
if (is_null_oid(oid))
return;
commit = lookup_commit_reference_gently(the_repository, oid, 1);
if (!commit || (commit->object.flags & TMP_MARK))
return;
commit->object.flags |= TMP_MARK;
ALLOC_GROW(tips->tip, tips->nr + 1, tips->alloc);
tips->tip[tips->nr++] = commit;
}
static void add_missing_tags(struct ref *src, struct ref **dst, struct ref ***dst_tail)
{
struct string_list dst_tag = STRING_LIST_INIT_NODUP;
struct string_list src_tag = STRING_LIST_INIT_NODUP;
struct string_list_item *item;
struct ref *ref;
struct tips sent_tips;
/*
* Collect everything we know they would have at the end of
* this push, and collect all tags they have.
*/
memset(&sent_tips, 0, sizeof(sent_tips));
for (ref = *dst; ref; ref = ref->next) {
if (ref->peer_ref &&
!is_null_oid(&ref->peer_ref->new_oid))
add_to_tips(&sent_tips, &ref->peer_ref->new_oid);
else
add_to_tips(&sent_tips, &ref->old_oid);
if (starts_with(ref->name, "refs/tags/"))
string_list_append(&dst_tag, ref->name);
}
clear_commit_marks_many(sent_tips.nr, sent_tips.tip, TMP_MARK);
string_list_sort(&dst_tag);
/* Collect tags they do not have. */
for (ref = src; ref; ref = ref->next) {
if (!starts_with(ref->name, "refs/tags/"))
continue; /* not a tag */
if (string_list_has_string(&dst_tag, ref->name))
continue; /* they already have it */
if (oid_object_info(the_repository, &ref->new_oid, NULL) != OBJ_TAG)
continue; /* be conservative */
item = string_list_append(&src_tag, ref->name);
item->util = ref;
}
string_list_clear(&dst_tag, 0);
/*
* At this point, src_tag lists tags that are missing from
* dst, and sent_tips lists the tips we are pushing or those
* that we know they already have. An element in the src_tag
* that is an ancestor of any of the sent_tips needs to be
* sent to the other side.
*/
if (sent_tips.nr) {
const int reachable_flag = 1;
struct commit_list *found_commits;
struct commit **src_commits;
int nr_src_commits = 0, alloc_src_commits = 16;
ALLOC_ARRAY(src_commits, alloc_src_commits);
for_each_string_list_item(item, &src_tag) {
struct ref *ref = item->util;
struct commit *commit;
if (is_null_oid(&ref->new_oid))
continue;
commit = lookup_commit_reference_gently(the_repository,
&ref->new_oid,
1);
if (!commit)
/* not pushing a commit, which is not an error */
continue;
ALLOC_GROW(src_commits, nr_src_commits + 1, alloc_src_commits);
src_commits[nr_src_commits++] = commit;
}
found_commits = get_reachable_subset(sent_tips.tip, sent_tips.nr,
src_commits, nr_src_commits,
reachable_flag);
for_each_string_list_item(item, &src_tag) {
struct ref *dst_ref;
struct ref *ref = item->util;
struct commit *commit;
if (is_null_oid(&ref->new_oid))
continue;
commit = lookup_commit_reference_gently(the_repository,
&ref->new_oid,
Convert lookup_commit* to struct object_id Convert lookup_commit, lookup_commit_or_die, lookup_commit_reference, and lookup_commit_reference_gently to take struct object_id arguments. Introduce a temporary in parse_object buffer in order to convert this function. This is required since in order to convert parse_object and parse_object_buffer, lookup_commit_reference_gently and lookup_commit_or_die would need to be converted. Not introducing a temporary would therefore require that lookup_commit_or_die take a struct object_id *, but lookup_commit would take unsigned char *, leaving a confusing and hard-to-use interface. parse_object_buffer will lose this temporary in a later patch. This commit was created with manual changes to commit.c, commit.h, and object.c, plus the following semantic patch: @@ expression E1, E2; @@ - lookup_commit_reference_gently(E1.hash, E2) + lookup_commit_reference_gently(&E1, E2) @@ expression E1, E2; @@ - lookup_commit_reference_gently(E1->hash, E2) + lookup_commit_reference_gently(E1, E2) @@ expression E1; @@ - lookup_commit_reference(E1.hash) + lookup_commit_reference(&E1) @@ expression E1; @@ - lookup_commit_reference(E1->hash) + lookup_commit_reference(E1) @@ expression E1; @@ - lookup_commit(E1.hash) + lookup_commit(&E1) @@ expression E1; @@ - lookup_commit(E1->hash) + lookup_commit(E1) @@ expression E1, E2; @@ - lookup_commit_or_die(E1.hash, E2) + lookup_commit_or_die(&E1, E2) @@ expression E1, E2; @@ - lookup_commit_or_die(E1->hash, E2) + lookup_commit_or_die(E1, E2) Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-05-07 00:10:10 +02:00
1);
if (!commit)
/* not pushing a commit, which is not an error */
continue;
/*
* Is this tag, which they do not have, reachable from
* any of the commits we are sending?
*/
if (!(commit->object.flags & reachable_flag))
continue;
/* Add it in */
dst_ref = make_linked_ref(ref->name, dst_tail);
oidcpy(&dst_ref->new_oid, &ref->new_oid);
dst_ref->peer_ref = copy_ref(ref);
}
clear_commit_marks_many(nr_src_commits, src_commits, reachable_flag);
free(src_commits);
free_commit_list(found_commits);
}
string_list_clear(&src_tag, 0);
free(sent_tips.tip);
}
struct ref *find_ref_by_name(const struct ref *list, const char *name)
{
for ( ; list; list = list->next)
if (!strcmp(list->name, name))
return (struct ref *)list;
return NULL;
}
remote.c: avoid O(m*n) behavior in match_push_refs When pushing using a matching refspec or a pattern refspec, each ref in the local repository must be paired with a ref advertised by the remote server. This is accomplished by using the refspec to transform the name of the local ref into the name it should have in the remote repository, and then performing a linear search through the list of remote refs to see if the remote ref was advertised by the remote system. Each of these lookups has O(n) complexity and makes match_push_refs() be an O(m*n) operation, where m is the number of local refs and n is the number of remote refs. If there are many refs 100,000+, then this ref matching can take a significant amount of time. Let's prepare an index of the remote refs to allow searching in O(log n) time and reduce the complexity of match_push_refs() to O(m log n). We prepare the index lazily so that it is only created when necessary. So, there should be no impact when _not_ using a matching or pattern refspec, i.e. when pushing using only explicit refspecs. Dry-run push of a repository with 121,913 local and remote refs: before after real 1m40.582s 0m0.804s user 1m39.914s 0m0.515s sys 0m0.125s 0m0.106s The creation of the index has overhead. So, if there are very few local refs, then it could take longer to create the index than it would have taken to just perform n linear lookups into the remote ref space. Using the index should provide some improvement when the number of local refs is roughly greater than the log of the number of remote refs (i.e. m >= log n). The pathological case is when there is a single local ref and very many remote refs. Dry-run push of a repository with 121,913 remote refs and a single local ref: before after real 0m0.525s 0m0.566s user 0m0.243s 0m0.279s sys 0m0.075s 0m0.099s Using an index takes 41 ms longer, or roughly 7.8% longer. Jeff King measured a no-op push of a single ref into a remote repo with 370,000 refs: before after real 0m1.087s 0m1.156s user 0m1.344s 0m1.412s sys 0m0.288s 0m0.284s Using an index takes 69 ms longer, or roughly 6.3% longer. None of the measurements above required transferring any objects to the remote repository. If the push required transferring objects and updating the refs in the remote repository, the impact of preparing the search index would be even smaller. A similar operation is performed in the reverse direction when pruning using a matching or pattern refspec. Let's avoid O(m*n) behavior in the same way by lazily preparing an index on the local refs. Signed-off-by: Brandon Casey <drafnel@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-07-08 10:58:39 +02:00
static void prepare_ref_index(struct string_list *ref_index, struct ref *ref)
{
for ( ; ref; ref = ref->next)
string_list_append_nodup(ref_index, ref->name)->util = ref;
string_list_sort(ref_index);
remote.c: avoid O(m*n) behavior in match_push_refs When pushing using a matching refspec or a pattern refspec, each ref in the local repository must be paired with a ref advertised by the remote server. This is accomplished by using the refspec to transform the name of the local ref into the name it should have in the remote repository, and then performing a linear search through the list of remote refs to see if the remote ref was advertised by the remote system. Each of these lookups has O(n) complexity and makes match_push_refs() be an O(m*n) operation, where m is the number of local refs and n is the number of remote refs. If there are many refs 100,000+, then this ref matching can take a significant amount of time. Let's prepare an index of the remote refs to allow searching in O(log n) time and reduce the complexity of match_push_refs() to O(m log n). We prepare the index lazily so that it is only created when necessary. So, there should be no impact when _not_ using a matching or pattern refspec, i.e. when pushing using only explicit refspecs. Dry-run push of a repository with 121,913 local and remote refs: before after real 1m40.582s 0m0.804s user 1m39.914s 0m0.515s sys 0m0.125s 0m0.106s The creation of the index has overhead. So, if there are very few local refs, then it could take longer to create the index than it would have taken to just perform n linear lookups into the remote ref space. Using the index should provide some improvement when the number of local refs is roughly greater than the log of the number of remote refs (i.e. m >= log n). The pathological case is when there is a single local ref and very many remote refs. Dry-run push of a repository with 121,913 remote refs and a single local ref: before after real 0m0.525s 0m0.566s user 0m0.243s 0m0.279s sys 0m0.075s 0m0.099s Using an index takes 41 ms longer, or roughly 7.8% longer. Jeff King measured a no-op push of a single ref into a remote repo with 370,000 refs: before after real 0m1.087s 0m1.156s user 0m1.344s 0m1.412s sys 0m0.288s 0m0.284s Using an index takes 69 ms longer, or roughly 6.3% longer. None of the measurements above required transferring any objects to the remote repository. If the push required transferring objects and updating the refs in the remote repository, the impact of preparing the search index would be even smaller. A similar operation is performed in the reverse direction when pruning using a matching or pattern refspec. Let's avoid O(m*n) behavior in the same way by lazily preparing an index on the local refs. Signed-off-by: Brandon Casey <drafnel@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-07-08 10:58:39 +02:00
}
/*
* Given only the set of local refs, sanity-check the set of push
* refspecs. We can't catch all errors that match_push_refs would,
* but we can catch some errors early before even talking to the
* remote side.
*/
int check_push_refs(struct ref *src, struct refspec *rs)
{
int ret = 0;
int i;
for (i = 0; i < rs->nr; i++) {
struct refspec_item *item = &rs->items[i];
refspec: add support for negative refspecs Both fetch and push support pattern refspecs which allow fetching or pushing references that match a specific pattern. Because these patterns are globs, they have somewhat limited ability to express more complex situations. For example, suppose you wish to fetch all branches from a remote except for a specific one. To allow this, you must setup a set of refspecs which match only the branches you want. Because refspecs are either explicit name matches, or simple globs, many patterns cannot be expressed. Add support for a new type of refspec, referred to as "negative" refspecs. These are prefixed with a '^' and mean "exclude any ref matching this refspec". They can only have one "side" which always refers to the source. During a fetch, this refers to the name of the ref on the remote. During a push, this refers to the name of the ref on the local side. With negative refspecs, users can express more complex patterns. For example: git fetch origin refs/heads/*:refs/remotes/origin/* ^refs/heads/dontwant will fetch all branches on origin into remotes/origin, but will exclude fetching the branch named dontwant. Refspecs today are commutative, meaning that order doesn't expressly matter. Rather than forcing an implied order, negative refspecs will always be applied last. That is, in order to match, a ref must match at least one positive refspec, and match none of the negative refspecs. This is similar to how negative pathspecs work. Signed-off-by: Jacob Keller <jacob.keller@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-09-30 23:25:29 +02:00
if (item->pattern || item->matching || item->negative)
continue;
ret |= match_explicit_lhs(src, item, NULL, NULL);
}
return ret;
}
/*
* Given the set of refs the local repository has, the set of refs the
* remote repository has, and the refspec used for push, determine
* what remote refs we will update and with what value by setting
* peer_ref (which object is being pushed) and force (if the push is
* forced) in elements of "dst". The function may add new elements to
* dst (e.g. pushing to a new branch, done in match_explicit_refs).
*/
int match_push_refs(struct ref *src, struct ref **dst,
struct refspec *rs, int flags)
{
int send_all = flags & MATCH_REFS_ALL;
int send_mirror = flags & MATCH_REFS_MIRROR;
int send_prune = flags & MATCH_REFS_PRUNE;
int errs;
struct ref *ref, **dst_tail = tail_ref(dst);
remote.c: avoid O(m*n) behavior in match_push_refs When pushing using a matching refspec or a pattern refspec, each ref in the local repository must be paired with a ref advertised by the remote server. This is accomplished by using the refspec to transform the name of the local ref into the name it should have in the remote repository, and then performing a linear search through the list of remote refs to see if the remote ref was advertised by the remote system. Each of these lookups has O(n) complexity and makes match_push_refs() be an O(m*n) operation, where m is the number of local refs and n is the number of remote refs. If there are many refs 100,000+, then this ref matching can take a significant amount of time. Let's prepare an index of the remote refs to allow searching in O(log n) time and reduce the complexity of match_push_refs() to O(m log n). We prepare the index lazily so that it is only created when necessary. So, there should be no impact when _not_ using a matching or pattern refspec, i.e. when pushing using only explicit refspecs. Dry-run push of a repository with 121,913 local and remote refs: before after real 1m40.582s 0m0.804s user 1m39.914s 0m0.515s sys 0m0.125s 0m0.106s The creation of the index has overhead. So, if there are very few local refs, then it could take longer to create the index than it would have taken to just perform n linear lookups into the remote ref space. Using the index should provide some improvement when the number of local refs is roughly greater than the log of the number of remote refs (i.e. m >= log n). The pathological case is when there is a single local ref and very many remote refs. Dry-run push of a repository with 121,913 remote refs and a single local ref: before after real 0m0.525s 0m0.566s user 0m0.243s 0m0.279s sys 0m0.075s 0m0.099s Using an index takes 41 ms longer, or roughly 7.8% longer. Jeff King measured a no-op push of a single ref into a remote repo with 370,000 refs: before after real 0m1.087s 0m1.156s user 0m1.344s 0m1.412s sys 0m0.288s 0m0.284s Using an index takes 69 ms longer, or roughly 6.3% longer. None of the measurements above required transferring any objects to the remote repository. If the push required transferring objects and updating the refs in the remote repository, the impact of preparing the search index would be even smaller. A similar operation is performed in the reverse direction when pruning using a matching or pattern refspec. Let's avoid O(m*n) behavior in the same way by lazily preparing an index on the local refs. Signed-off-by: Brandon Casey <drafnel@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-07-08 10:58:39 +02:00
struct string_list dst_ref_index = STRING_LIST_INIT_NODUP;
/* If no refspec is provided, use the default ":" */
if (!rs->nr)
refspec_append(rs, ":");
errs = match_explicit_refs(src, *dst, &dst_tail, rs);
/* pick the remainder */
for (ref = src; ref; ref = ref->next) {
remote.c: avoid O(m*n) behavior in match_push_refs When pushing using a matching refspec or a pattern refspec, each ref in the local repository must be paired with a ref advertised by the remote server. This is accomplished by using the refspec to transform the name of the local ref into the name it should have in the remote repository, and then performing a linear search through the list of remote refs to see if the remote ref was advertised by the remote system. Each of these lookups has O(n) complexity and makes match_push_refs() be an O(m*n) operation, where m is the number of local refs and n is the number of remote refs. If there are many refs 100,000+, then this ref matching can take a significant amount of time. Let's prepare an index of the remote refs to allow searching in O(log n) time and reduce the complexity of match_push_refs() to O(m log n). We prepare the index lazily so that it is only created when necessary. So, there should be no impact when _not_ using a matching or pattern refspec, i.e. when pushing using only explicit refspecs. Dry-run push of a repository with 121,913 local and remote refs: before after real 1m40.582s 0m0.804s user 1m39.914s 0m0.515s sys 0m0.125s 0m0.106s The creation of the index has overhead. So, if there are very few local refs, then it could take longer to create the index than it would have taken to just perform n linear lookups into the remote ref space. Using the index should provide some improvement when the number of local refs is roughly greater than the log of the number of remote refs (i.e. m >= log n). The pathological case is when there is a single local ref and very many remote refs. Dry-run push of a repository with 121,913 remote refs and a single local ref: before after real 0m0.525s 0m0.566s user 0m0.243s 0m0.279s sys 0m0.075s 0m0.099s Using an index takes 41 ms longer, or roughly 7.8% longer. Jeff King measured a no-op push of a single ref into a remote repo with 370,000 refs: before after real 0m1.087s 0m1.156s user 0m1.344s 0m1.412s sys 0m0.288s 0m0.284s Using an index takes 69 ms longer, or roughly 6.3% longer. None of the measurements above required transferring any objects to the remote repository. If the push required transferring objects and updating the refs in the remote repository, the impact of preparing the search index would be even smaller. A similar operation is performed in the reverse direction when pruning using a matching or pattern refspec. Let's avoid O(m*n) behavior in the same way by lazily preparing an index on the local refs. Signed-off-by: Brandon Casey <drafnel@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-07-08 10:58:39 +02:00
struct string_list_item *dst_item;
struct ref *dst_peer;
const struct refspec_item *pat = NULL;
char *dst_name;
dst_name = get_ref_match(rs, ref, send_mirror, FROM_SRC, &pat);
if (!dst_name)
continue;
remote.c: avoid O(m*n) behavior in match_push_refs When pushing using a matching refspec or a pattern refspec, each ref in the local repository must be paired with a ref advertised by the remote server. This is accomplished by using the refspec to transform the name of the local ref into the name it should have in the remote repository, and then performing a linear search through the list of remote refs to see if the remote ref was advertised by the remote system. Each of these lookups has O(n) complexity and makes match_push_refs() be an O(m*n) operation, where m is the number of local refs and n is the number of remote refs. If there are many refs 100,000+, then this ref matching can take a significant amount of time. Let's prepare an index of the remote refs to allow searching in O(log n) time and reduce the complexity of match_push_refs() to O(m log n). We prepare the index lazily so that it is only created when necessary. So, there should be no impact when _not_ using a matching or pattern refspec, i.e. when pushing using only explicit refspecs. Dry-run push of a repository with 121,913 local and remote refs: before after real 1m40.582s 0m0.804s user 1m39.914s 0m0.515s sys 0m0.125s 0m0.106s The creation of the index has overhead. So, if there are very few local refs, then it could take longer to create the index than it would have taken to just perform n linear lookups into the remote ref space. Using the index should provide some improvement when the number of local refs is roughly greater than the log of the number of remote refs (i.e. m >= log n). The pathological case is when there is a single local ref and very many remote refs. Dry-run push of a repository with 121,913 remote refs and a single local ref: before after real 0m0.525s 0m0.566s user 0m0.243s 0m0.279s sys 0m0.075s 0m0.099s Using an index takes 41 ms longer, or roughly 7.8% longer. Jeff King measured a no-op push of a single ref into a remote repo with 370,000 refs: before after real 0m1.087s 0m1.156s user 0m1.344s 0m1.412s sys 0m0.288s 0m0.284s Using an index takes 69 ms longer, or roughly 6.3% longer. None of the measurements above required transferring any objects to the remote repository. If the push required transferring objects and updating the refs in the remote repository, the impact of preparing the search index would be even smaller. A similar operation is performed in the reverse direction when pruning using a matching or pattern refspec. Let's avoid O(m*n) behavior in the same way by lazily preparing an index on the local refs. Signed-off-by: Brandon Casey <drafnel@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-07-08 10:58:39 +02:00
if (!dst_ref_index.nr)
prepare_ref_index(&dst_ref_index, *dst);
dst_item = string_list_lookup(&dst_ref_index, dst_name);
dst_peer = dst_item ? dst_item->util : NULL;
if (dst_peer) {
if (dst_peer->peer_ref)
/* We're already sending something to this ref. */
goto free_name;
} else {
if (pat->matching && !(send_all || send_mirror))
/*
* Remote doesn't have it, and we have no
* explicit pattern, and we don't have
* --all or --mirror.
*/
goto free_name;
/* Create a new one and link it */
dst_peer = make_linked_ref(dst_name, &dst_tail);
oidcpy(&dst_peer->new_oid, &ref->new_oid);
remote.c: avoid O(m*n) behavior in match_push_refs When pushing using a matching refspec or a pattern refspec, each ref in the local repository must be paired with a ref advertised by the remote server. This is accomplished by using the refspec to transform the name of the local ref into the name it should have in the remote repository, and then performing a linear search through the list of remote refs to see if the remote ref was advertised by the remote system. Each of these lookups has O(n) complexity and makes match_push_refs() be an O(m*n) operation, where m is the number of local refs and n is the number of remote refs. If there are many refs 100,000+, then this ref matching can take a significant amount of time. Let's prepare an index of the remote refs to allow searching in O(log n) time and reduce the complexity of match_push_refs() to O(m log n). We prepare the index lazily so that it is only created when necessary. So, there should be no impact when _not_ using a matching or pattern refspec, i.e. when pushing using only explicit refspecs. Dry-run push of a repository with 121,913 local and remote refs: before after real 1m40.582s 0m0.804s user 1m39.914s 0m0.515s sys 0m0.125s 0m0.106s The creation of the index has overhead. So, if there are very few local refs, then it could take longer to create the index than it would have taken to just perform n linear lookups into the remote ref space. Using the index should provide some improvement when the number of local refs is roughly greater than the log of the number of remote refs (i.e. m >= log n). The pathological case is when there is a single local ref and very many remote refs. Dry-run push of a repository with 121,913 remote refs and a single local ref: before after real 0m0.525s 0m0.566s user 0m0.243s 0m0.279s sys 0m0.075s 0m0.099s Using an index takes 41 ms longer, or roughly 7.8% longer. Jeff King measured a no-op push of a single ref into a remote repo with 370,000 refs: before after real 0m1.087s 0m1.156s user 0m1.344s 0m1.412s sys 0m0.288s 0m0.284s Using an index takes 69 ms longer, or roughly 6.3% longer. None of the measurements above required transferring any objects to the remote repository. If the push required transferring objects and updating the refs in the remote repository, the impact of preparing the search index would be even smaller. A similar operation is performed in the reverse direction when pruning using a matching or pattern refspec. Let's avoid O(m*n) behavior in the same way by lazily preparing an index on the local refs. Signed-off-by: Brandon Casey <drafnel@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-07-08 10:58:39 +02:00
string_list_insert(&dst_ref_index,
dst_peer->name)->util = dst_peer;
}
dst_peer->peer_ref = copy_ref(ref);
dst_peer->force = pat->force;
free_name:
free(dst_name);
}
remote.c: avoid O(m*n) behavior in match_push_refs When pushing using a matching refspec or a pattern refspec, each ref in the local repository must be paired with a ref advertised by the remote server. This is accomplished by using the refspec to transform the name of the local ref into the name it should have in the remote repository, and then performing a linear search through the list of remote refs to see if the remote ref was advertised by the remote system. Each of these lookups has O(n) complexity and makes match_push_refs() be an O(m*n) operation, where m is the number of local refs and n is the number of remote refs. If there are many refs 100,000+, then this ref matching can take a significant amount of time. Let's prepare an index of the remote refs to allow searching in O(log n) time and reduce the complexity of match_push_refs() to O(m log n). We prepare the index lazily so that it is only created when necessary. So, there should be no impact when _not_ using a matching or pattern refspec, i.e. when pushing using only explicit refspecs. Dry-run push of a repository with 121,913 local and remote refs: before after real 1m40.582s 0m0.804s user 1m39.914s 0m0.515s sys 0m0.125s 0m0.106s The creation of the index has overhead. So, if there are very few local refs, then it could take longer to create the index than it would have taken to just perform n linear lookups into the remote ref space. Using the index should provide some improvement when the number of local refs is roughly greater than the log of the number of remote refs (i.e. m >= log n). The pathological case is when there is a single local ref and very many remote refs. Dry-run push of a repository with 121,913 remote refs and a single local ref: before after real 0m0.525s 0m0.566s user 0m0.243s 0m0.279s sys 0m0.075s 0m0.099s Using an index takes 41 ms longer, or roughly 7.8% longer. Jeff King measured a no-op push of a single ref into a remote repo with 370,000 refs: before after real 0m1.087s 0m1.156s user 0m1.344s 0m1.412s sys 0m0.288s 0m0.284s Using an index takes 69 ms longer, or roughly 6.3% longer. None of the measurements above required transferring any objects to the remote repository. If the push required transferring objects and updating the refs in the remote repository, the impact of preparing the search index would be even smaller. A similar operation is performed in the reverse direction when pruning using a matching or pattern refspec. Let's avoid O(m*n) behavior in the same way by lazily preparing an index on the local refs. Signed-off-by: Brandon Casey <drafnel@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-07-08 10:58:39 +02:00
string_list_clear(&dst_ref_index, 0);
if (flags & MATCH_REFS_FOLLOW_TAGS)
add_missing_tags(src, dst, &dst_tail);
if (send_prune) {
remote.c: avoid O(m*n) behavior in match_push_refs When pushing using a matching refspec or a pattern refspec, each ref in the local repository must be paired with a ref advertised by the remote server. This is accomplished by using the refspec to transform the name of the local ref into the name it should have in the remote repository, and then performing a linear search through the list of remote refs to see if the remote ref was advertised by the remote system. Each of these lookups has O(n) complexity and makes match_push_refs() be an O(m*n) operation, where m is the number of local refs and n is the number of remote refs. If there are many refs 100,000+, then this ref matching can take a significant amount of time. Let's prepare an index of the remote refs to allow searching in O(log n) time and reduce the complexity of match_push_refs() to O(m log n). We prepare the index lazily so that it is only created when necessary. So, there should be no impact when _not_ using a matching or pattern refspec, i.e. when pushing using only explicit refspecs. Dry-run push of a repository with 121,913 local and remote refs: before after real 1m40.582s 0m0.804s user 1m39.914s 0m0.515s sys 0m0.125s 0m0.106s The creation of the index has overhead. So, if there are very few local refs, then it could take longer to create the index than it would have taken to just perform n linear lookups into the remote ref space. Using the index should provide some improvement when the number of local refs is roughly greater than the log of the number of remote refs (i.e. m >= log n). The pathological case is when there is a single local ref and very many remote refs. Dry-run push of a repository with 121,913 remote refs and a single local ref: before after real 0m0.525s 0m0.566s user 0m0.243s 0m0.279s sys 0m0.075s 0m0.099s Using an index takes 41 ms longer, or roughly 7.8% longer. Jeff King measured a no-op push of a single ref into a remote repo with 370,000 refs: before after real 0m1.087s 0m1.156s user 0m1.344s 0m1.412s sys 0m0.288s 0m0.284s Using an index takes 69 ms longer, or roughly 6.3% longer. None of the measurements above required transferring any objects to the remote repository. If the push required transferring objects and updating the refs in the remote repository, the impact of preparing the search index would be even smaller. A similar operation is performed in the reverse direction when pruning using a matching or pattern refspec. Let's avoid O(m*n) behavior in the same way by lazily preparing an index on the local refs. Signed-off-by: Brandon Casey <drafnel@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-07-08 10:58:39 +02:00
struct string_list src_ref_index = STRING_LIST_INIT_NODUP;
/* check for missing refs on the remote */
for (ref = *dst; ref; ref = ref->next) {
char *src_name;
if (ref->peer_ref)
/* We're already sending something to this ref. */
continue;
src_name = get_ref_match(rs, ref, send_mirror, FROM_DST, NULL);
if (src_name) {
remote.c: avoid O(m*n) behavior in match_push_refs When pushing using a matching refspec or a pattern refspec, each ref in the local repository must be paired with a ref advertised by the remote server. This is accomplished by using the refspec to transform the name of the local ref into the name it should have in the remote repository, and then performing a linear search through the list of remote refs to see if the remote ref was advertised by the remote system. Each of these lookups has O(n) complexity and makes match_push_refs() be an O(m*n) operation, where m is the number of local refs and n is the number of remote refs. If there are many refs 100,000+, then this ref matching can take a significant amount of time. Let's prepare an index of the remote refs to allow searching in O(log n) time and reduce the complexity of match_push_refs() to O(m log n). We prepare the index lazily so that it is only created when necessary. So, there should be no impact when _not_ using a matching or pattern refspec, i.e. when pushing using only explicit refspecs. Dry-run push of a repository with 121,913 local and remote refs: before after real 1m40.582s 0m0.804s user 1m39.914s 0m0.515s sys 0m0.125s 0m0.106s The creation of the index has overhead. So, if there are very few local refs, then it could take longer to create the index than it would have taken to just perform n linear lookups into the remote ref space. Using the index should provide some improvement when the number of local refs is roughly greater than the log of the number of remote refs (i.e. m >= log n). The pathological case is when there is a single local ref and very many remote refs. Dry-run push of a repository with 121,913 remote refs and a single local ref: before after real 0m0.525s 0m0.566s user 0m0.243s 0m0.279s sys 0m0.075s 0m0.099s Using an index takes 41 ms longer, or roughly 7.8% longer. Jeff King measured a no-op push of a single ref into a remote repo with 370,000 refs: before after real 0m1.087s 0m1.156s user 0m1.344s 0m1.412s sys 0m0.288s 0m0.284s Using an index takes 69 ms longer, or roughly 6.3% longer. None of the measurements above required transferring any objects to the remote repository. If the push required transferring objects and updating the refs in the remote repository, the impact of preparing the search index would be even smaller. A similar operation is performed in the reverse direction when pruning using a matching or pattern refspec. Let's avoid O(m*n) behavior in the same way by lazily preparing an index on the local refs. Signed-off-by: Brandon Casey <drafnel@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-07-08 10:58:39 +02:00
if (!src_ref_index.nr)
prepare_ref_index(&src_ref_index, src);
if (!string_list_has_string(&src_ref_index,
src_name))
ref->peer_ref = alloc_delete_ref();
free(src_name);
}
}
remote.c: avoid O(m*n) behavior in match_push_refs When pushing using a matching refspec or a pattern refspec, each ref in the local repository must be paired with a ref advertised by the remote server. This is accomplished by using the refspec to transform the name of the local ref into the name it should have in the remote repository, and then performing a linear search through the list of remote refs to see if the remote ref was advertised by the remote system. Each of these lookups has O(n) complexity and makes match_push_refs() be an O(m*n) operation, where m is the number of local refs and n is the number of remote refs. If there are many refs 100,000+, then this ref matching can take a significant amount of time. Let's prepare an index of the remote refs to allow searching in O(log n) time and reduce the complexity of match_push_refs() to O(m log n). We prepare the index lazily so that it is only created when necessary. So, there should be no impact when _not_ using a matching or pattern refspec, i.e. when pushing using only explicit refspecs. Dry-run push of a repository with 121,913 local and remote refs: before after real 1m40.582s 0m0.804s user 1m39.914s 0m0.515s sys 0m0.125s 0m0.106s The creation of the index has overhead. So, if there are very few local refs, then it could take longer to create the index than it would have taken to just perform n linear lookups into the remote ref space. Using the index should provide some improvement when the number of local refs is roughly greater than the log of the number of remote refs (i.e. m >= log n). The pathological case is when there is a single local ref and very many remote refs. Dry-run push of a repository with 121,913 remote refs and a single local ref: before after real 0m0.525s 0m0.566s user 0m0.243s 0m0.279s sys 0m0.075s 0m0.099s Using an index takes 41 ms longer, or roughly 7.8% longer. Jeff King measured a no-op push of a single ref into a remote repo with 370,000 refs: before after real 0m1.087s 0m1.156s user 0m1.344s 0m1.412s sys 0m0.288s 0m0.284s Using an index takes 69 ms longer, or roughly 6.3% longer. None of the measurements above required transferring any objects to the remote repository. If the push required transferring objects and updating the refs in the remote repository, the impact of preparing the search index would be even smaller. A similar operation is performed in the reverse direction when pruning using a matching or pattern refspec. Let's avoid O(m*n) behavior in the same way by lazily preparing an index on the local refs. Signed-off-by: Brandon Casey <drafnel@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-07-08 10:58:39 +02:00
string_list_clear(&src_ref_index, 0);
}
refspec: add support for negative refspecs Both fetch and push support pattern refspecs which allow fetching or pushing references that match a specific pattern. Because these patterns are globs, they have somewhat limited ability to express more complex situations. For example, suppose you wish to fetch all branches from a remote except for a specific one. To allow this, you must setup a set of refspecs which match only the branches you want. Because refspecs are either explicit name matches, or simple globs, many patterns cannot be expressed. Add support for a new type of refspec, referred to as "negative" refspecs. These are prefixed with a '^' and mean "exclude any ref matching this refspec". They can only have one "side" which always refers to the source. During a fetch, this refers to the name of the ref on the remote. During a push, this refers to the name of the ref on the local side. With negative refspecs, users can express more complex patterns. For example: git fetch origin refs/heads/*:refs/remotes/origin/* ^refs/heads/dontwant will fetch all branches on origin into remotes/origin, but will exclude fetching the branch named dontwant. Refspecs today are commutative, meaning that order doesn't expressly matter. Rather than forcing an implied order, negative refspecs will always be applied last. That is, in order to match, a ref must match at least one positive refspec, and match none of the negative refspecs. This is similar to how negative pathspecs work. Signed-off-by: Jacob Keller <jacob.keller@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-09-30 23:25:29 +02:00
*dst = apply_negative_refspecs(*dst, rs);
if (errs)
return -1;
return 0;
}
void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,
int force_update)
{
struct ref *ref;
for (ref = remote_refs; ref; ref = ref->next) {
int force_ref_update = ref->force || force_update;
int reject_reason = 0;
if (ref->peer_ref)
oidcpy(&ref->new_oid, &ref->peer_ref->new_oid);
else if (!send_mirror)
continue;
ref->deletion = is_null_oid(&ref->new_oid);
if (!ref->deletion &&
oideq(&ref->old_oid, &ref->new_oid)) {
ref->status = REF_STATUS_UPTODATE;
continue;
}
/*
* If the remote ref has moved and is now different
* from what we expect, reject any push.
*
* It also is an error if the user told us to check
* with the remote-tracking branch to find the value
* to expect, but we did not have such a tracking
* branch.
*
* If the tip of the remote-tracking ref is unreachable
* from any reflog entry of its local ref indicating a
* possible update since checkout; reject the push.
*/
if (ref->expect_old_sha1) {
if (!oideq(&ref->old_oid, &ref->old_oid_expect))
reject_reason = REF_STATUS_REJECT_STALE;
else if (ref->check_reachable && ref->unreachable)
reject_reason =
REF_STATUS_REJECT_REMOTE_UPDATED;
else
/*
* If the ref isn't stale, and is reachable
* from one of the reflog entries of
* the local branch, force the update.
*/
force_ref_update = 1;
}
/*
* If the update isn't already rejected then check
* the usual "must fast-forward" rules.
*
push: fix "refs/tags/ hierarchy cannot be updated without --force" When pushing to update a branch with a commit that is not a descendant of the commit at the tip, a wrong message "already exists" was given, instead of the correct "non-fast-forward", if we do not have the object sitting in the destination repository at the tip of the ref we are updating. The primary cause of the bug is that the check in a new helper function is_forwardable() assumed both old and new objects are available and can be checked, which is not always the case. The way the caller uses the result of this function is also wrong. If the helper says "we do not want to let this push go through", the caller unconditionally translates it into "we blocked it because the destination already exists", which is not true at all in this case. Fix this by doing these three things: * Remove unnecessary not_forwardable from "struct ref"; it is only used inside set_ref_status_for_push(); * Make "refs/tags/" the only hierarchy that cannot be replaced without --force; * Remove the misguided attempt to force that everything that updates an existing ref has to be a commit outside "refs/tags/" hierarchy. The policy last one tried to implement may later be resurrected and extended to ensure fast-forwardness (defined as "not losing objects", extending from the traditional "not losing commits from the resulting history") when objects that are not commit are involved (e.g. an annotated tag in hierarchies outside refs/tags), but such a logic belongs to "is this a fast-forward?" check that is done by ref_newer(); is_forwardable(), which is now removed, was not the right place to do so. Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-01-16 22:02:27 +01:00
* Decide whether an individual refspec A:B can be
* pushed. The push will succeed if any of the
* following are true:
*
* (1) the remote reference B does not exist
*
* (2) the remote reference B is being removed (i.e.,
* pushing :B where no source is specified)
*
push: fix "refs/tags/ hierarchy cannot be updated without --force" When pushing to update a branch with a commit that is not a descendant of the commit at the tip, a wrong message "already exists" was given, instead of the correct "non-fast-forward", if we do not have the object sitting in the destination repository at the tip of the ref we are updating. The primary cause of the bug is that the check in a new helper function is_forwardable() assumed both old and new objects are available and can be checked, which is not always the case. The way the caller uses the result of this function is also wrong. If the helper says "we do not want to let this push go through", the caller unconditionally translates it into "we blocked it because the destination already exists", which is not true at all in this case. Fix this by doing these three things: * Remove unnecessary not_forwardable from "struct ref"; it is only used inside set_ref_status_for_push(); * Make "refs/tags/" the only hierarchy that cannot be replaced without --force; * Remove the misguided attempt to force that everything that updates an existing ref has to be a commit outside "refs/tags/" hierarchy. The policy last one tried to implement may later be resurrected and extended to ensure fast-forwardness (defined as "not losing objects", extending from the traditional "not losing commits from the resulting history") when objects that are not commit are involved (e.g. an annotated tag in hierarchies outside refs/tags), but such a logic belongs to "is this a fast-forward?" check that is done by ref_newer(); is_forwardable(), which is now removed, was not the right place to do so. Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-01-16 22:02:27 +01:00
* (3) the destination is not under refs/tags/, and
* if the old and new value is a commit, the new
* is a descendant of the old.
*
* (4) it is forced using the +A:B notation, or by
* passing the --force argument
*/
if (!reject_reason && !ref->deletion && !is_null_oid(&ref->old_oid)) {
if (starts_with(ref->name, "refs/tags/"))
reject_reason = REF_STATUS_REJECT_ALREADY_EXISTS;
else if (!repo_has_object_file(the_repository, &ref->old_oid))
reject_reason = REF_STATUS_REJECT_FETCH_FIRST;
else if (!lookup_commit_reference_gently(the_repository, &ref->old_oid, 1) ||
!lookup_commit_reference_gently(the_repository, &ref->new_oid, 1))
reject_reason = REF_STATUS_REJECT_NEEDS_FORCE;
else if (!ref_newer(&ref->new_oid, &ref->old_oid))
reject_reason = REF_STATUS_REJECT_NONFASTFORWARD;
}
/*
* "--force" will defeat any rejection implemented
* by the rules above.
*/
if (!force_ref_update)
ref->status = reject_reason;
else if (reject_reason)
ref->forced_update = 1;
}
}
static void set_merge(struct remote_state *remote_state, struct branch *ret)
{
remote.c: drop "remote" pointer from "struct branch" When we create each branch struct, we fill in the "remote_name" field from the config, and then fill in the actual "remote" field (with a "struct remote") based on that name. However, it turns out that nobody really cares about the latter field. The only two sites that access it at all are: 1. git-merge, which uses it to notice when the branch does not have a remote defined. But we can easily replace this with looking at remote_name instead. 2. remote.c itself, when setting up the @{upstream} merge config. But we don't need to save the "remote" in the "struct branch" for that; we can just look it up for the duration of the operation. So there is no need to have both fields; they are redundant with each other (the struct remote contains the name, or you can look up the struct from the name). It would be nice to simplify this, especially as we are going to add matching pushremote config in a future patch (and it would be nice to keep them consistent). So which one do we keep and which one do we get rid of? If we had a lot of callers accessing the struct, it would be more efficient to keep it (since you have to do a lookup to go from the name to the struct, but not vice versa). But we don't have a lot of callers; we have exactly one, so efficiency doesn't matter. We can decide this based on simplicity and readability. And the meaning of the struct value is somewhat unclear. Is it always the remote matching remote_name? If remote_name is NULL (i.e., no per-branch config), does the struct fall back to the "origin" remote, or is it also NULL? These questions will get even more tricky with pushremotes, whose fallback behavior is more complicated. So let's just store the name, which pretty clearly represents the branch.*.remote config. Any lookup or fallback behavior can then be implemented in helper functions. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2015-05-21 06:45:13 +02:00
struct remote *remote;
char *ref;
struct object_id oid;
int i;
remote.c: refactor setup of branch->merge list When we call branch_get() to lookup or create a "struct branch", we make sure the "merge" field is filled in so that callers can access it. But the conditions under which we do so are a little confusing, and can lead to two funny situations: 1. If there's no branch.*.remote config, we cannot provide branch->merge (because it is really just an application of branch.*.merge to our remote's refspecs). But branch->merge_nr may be non-zero, leading callers to be believe they can access branch->merge (e.g., in branch_merge_matches and elsewhere). It doesn't look like this can cause a segfault in practice, as most code paths dealing with merge config will bail early if there is no remote defined. But it's a bit of a dangerous construct. We can fix this by setting merge_nr to "0" explicitly when we realize that we have no merge config. Note that merge_nr also counts the "merge_name" fields (which we _do_ have; that's how merge_nr got incremented), so we will "lose" access to them, in the sense that we forget how many we had. But no callers actually care; we use merge_name only while iteratively reading the config, and then convert it to the final "merge" form the first time somebody calls branch_get(). 2. We set up the "merge" field every time branch_get is called, even if it has already been done. This leaks memory. It's not a big deal in practice, since most code paths will access only one branch, or perhaps each branch only one time. But if you want to be pathological, you can leak arbitrary memory with: yes @{upstream} | head -1000 | git rev-list --stdin We can fix this by skipping setup when branch->merge is already non-NULL. In addition to those two fixes, this patch pushes the "do we need to setup merge?" logic down into set_merge, where it is a bit easier to follow. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2015-05-21 06:45:09 +02:00
if (!ret)
return; /* no branch */
if (ret->merge)
return; /* already run */
if (!ret->remote_name || !ret->merge_nr) {
/*
* no merge config; let's make sure we don't confuse callers
* with a non-zero merge_nr but a NULL merge
*/
ret->merge_nr = 0;
return;
}
remote = remotes_remote_get(remote_state, ret->remote_name);
remote.c: drop "remote" pointer from "struct branch" When we create each branch struct, we fill in the "remote_name" field from the config, and then fill in the actual "remote" field (with a "struct remote") based on that name. However, it turns out that nobody really cares about the latter field. The only two sites that access it at all are: 1. git-merge, which uses it to notice when the branch does not have a remote defined. But we can easily replace this with looking at remote_name instead. 2. remote.c itself, when setting up the @{upstream} merge config. But we don't need to save the "remote" in the "struct branch" for that; we can just look it up for the duration of the operation. So there is no need to have both fields; they are redundant with each other (the struct remote contains the name, or you can look up the struct from the name). It would be nice to simplify this, especially as we are going to add matching pushremote config in a future patch (and it would be nice to keep them consistent). So which one do we keep and which one do we get rid of? If we had a lot of callers accessing the struct, it would be more efficient to keep it (since you have to do a lookup to go from the name to the struct, but not vice versa). But we don't have a lot of callers; we have exactly one, so efficiency doesn't matter. We can decide this based on simplicity and readability. And the meaning of the struct value is somewhat unclear. Is it always the remote matching remote_name? If remote_name is NULL (i.e., no per-branch config), does the struct fall back to the "origin" remote, or is it also NULL? These questions will get even more tricky with pushremotes, whose fallback behavior is more complicated. So let's just store the name, which pretty clearly represents the branch.*.remote config. Any lookup or fallback behavior can then be implemented in helper functions. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2015-05-21 06:45:13 +02:00
CALLOC_ARRAY(ret->merge, ret->merge_nr);
for (i = 0; i < ret->merge_nr; i++) {
ret->merge[i] = xcalloc(1, sizeof(**ret->merge));
ret->merge[i]->src = xstrdup(ret->merge_name[i]);
remote.c: drop "remote" pointer from "struct branch" When we create each branch struct, we fill in the "remote_name" field from the config, and then fill in the actual "remote" field (with a "struct remote") based on that name. However, it turns out that nobody really cares about the latter field. The only two sites that access it at all are: 1. git-merge, which uses it to notice when the branch does not have a remote defined. But we can easily replace this with looking at remote_name instead. 2. remote.c itself, when setting up the @{upstream} merge config. But we don't need to save the "remote" in the "struct branch" for that; we can just look it up for the duration of the operation. So there is no need to have both fields; they are redundant with each other (the struct remote contains the name, or you can look up the struct from the name). It would be nice to simplify this, especially as we are going to add matching pushremote config in a future patch (and it would be nice to keep them consistent). So which one do we keep and which one do we get rid of? If we had a lot of callers accessing the struct, it would be more efficient to keep it (since you have to do a lookup to go from the name to the struct, but not vice versa). But we don't have a lot of callers; we have exactly one, so efficiency doesn't matter. We can decide this based on simplicity and readability. And the meaning of the struct value is somewhat unclear. Is it always the remote matching remote_name? If remote_name is NULL (i.e., no per-branch config), does the struct fall back to the "origin" remote, or is it also NULL? These questions will get even more tricky with pushremotes, whose fallback behavior is more complicated. So let's just store the name, which pretty clearly represents the branch.*.remote config. Any lookup or fallback behavior can then be implemented in helper functions. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2015-05-21 06:45:13 +02:00
if (!remote_find_tracking(remote, ret->merge[i]) ||
strcmp(ret->remote_name, "."))
continue;
if (repo_dwim_ref(the_repository, ret->merge_name[i],
strlen(ret->merge_name[i]), &oid, &ref,
0) == 1)
ret->merge[i]->dst = ref;
else
ret->merge[i]->dst = xstrdup(ret->merge_name[i]);
}
}
struct branch *branch_get(const char *name)
{
struct branch *ret;
read_config(the_repository);
if (!name || !*name || !strcmp(name, "HEAD"))
ret = the_repository->remote_state->current_branch;
else
ret = make_branch(the_repository->remote_state, name,
strlen(name));
set_merge(the_repository->remote_state, ret);
return ret;
}
int branch_has_merge_config(struct branch *branch)
{
return branch && !!branch->merge;
}
Correct handling of branch.$name.merge in builtin-fetch My prior bug fix for git-push titled "Don't configure remote "." to fetch everything to itself" actually broke t5520 as we were unable to evaluate a branch configuration of: [branch "copy"] remote = . merge = refs/heads/master as remote "." did not have a "remote...fetch" configuration entry to offer up refs/heads/master as a possible candidate available to be fetched and merged. In shell script git-fetch and prior to the above mentioned commit this was hardcoded for a url of "." to be the set of local branches. Chasing down this bug led me to the conclusion that our prior behavior with regards to branch.$name.merge was incorrect. In the shell script based git-fetch implementation we only fetched and merged a branch if it appeared both in branch.$name.merge *and* in remote.$r.fetch, where $r = branch.$name.remote. In other words in the following config file: [remote "origin"] url = git://git.kernel.org/pub/scm/git/git.git fetch = refs/heads/master:refs/remotes/origin/master [branch "master"] remote = origin merge = refs/heads/master [branch "pu"] remote = origin merge = refs/heads/pu Attempting to run `git pull` while on branch "pu" would always give the user "Already up-to-date" as git-fetch did not fetch pu and thus did not mark it for merge in .git/FETCH_HEAD. The configured merge would always be ignored and the user would be left scratching her confused head wondering why merge did not work on "pu" but worked fine on "master". If we are using the "default fetch" specification for the current branch and the current branch has a branch.$name.merge configured we now union it with the list of refs in remote.$r.fetch. This way the above configuration does what the user expects it to do, which is to fetch only "master" by default but when on "pu" to fetch both "master" and "pu". This uncovered some breakage in the test suite where old-style Cogito branches (.git/branches/$r) did not fetch the branches listed in .git/config for merging and thus did not actually merge them if the user tried to use `git pull` on that branch. Junio and I discussed it on list and felt that the union approach here makes more sense to DWIM for the end-user than silently ignoring their configured request so the test vectors for t5515 have been updated to include for-merge lines in .git/FETCH_HEAD where they have been configured for-merge in .git/config. Since we are now performing a union of the fetch specification and the merge specification and we cannot allow a branch to be listed twice (otherwise it comes out twice in .git/FETCH_HEAD) we need to perform a double loop here over all of the branch.$name.merge lines and try to set their merge flag if we have already schedule that branch for fetching by remote.$r.fetch. If no match is found then we must add new specifications to fetch the branch but not store it as no local tracking branch has been designated. Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
2007-09-18 10:54:53 +02:00
int branch_merge_matches(struct branch *branch,
int i,
const char *refname)
{
Correct handling of branch.$name.merge in builtin-fetch My prior bug fix for git-push titled "Don't configure remote "." to fetch everything to itself" actually broke t5520 as we were unable to evaluate a branch configuration of: [branch "copy"] remote = . merge = refs/heads/master as remote "." did not have a "remote...fetch" configuration entry to offer up refs/heads/master as a possible candidate available to be fetched and merged. In shell script git-fetch and prior to the above mentioned commit this was hardcoded for a url of "." to be the set of local branches. Chasing down this bug led me to the conclusion that our prior behavior with regards to branch.$name.merge was incorrect. In the shell script based git-fetch implementation we only fetched and merged a branch if it appeared both in branch.$name.merge *and* in remote.$r.fetch, where $r = branch.$name.remote. In other words in the following config file: [remote "origin"] url = git://git.kernel.org/pub/scm/git/git.git fetch = refs/heads/master:refs/remotes/origin/master [branch "master"] remote = origin merge = refs/heads/master [branch "pu"] remote = origin merge = refs/heads/pu Attempting to run `git pull` while on branch "pu" would always give the user "Already up-to-date" as git-fetch did not fetch pu and thus did not mark it for merge in .git/FETCH_HEAD. The configured merge would always be ignored and the user would be left scratching her confused head wondering why merge did not work on "pu" but worked fine on "master". If we are using the "default fetch" specification for the current branch and the current branch has a branch.$name.merge configured we now union it with the list of refs in remote.$r.fetch. This way the above configuration does what the user expects it to do, which is to fetch only "master" by default but when on "pu" to fetch both "master" and "pu". This uncovered some breakage in the test suite where old-style Cogito branches (.git/branches/$r) did not fetch the branches listed in .git/config for merging and thus did not actually merge them if the user tried to use `git pull` on that branch. Junio and I discussed it on list and felt that the union approach here makes more sense to DWIM for the end-user than silently ignoring their configured request so the test vectors for t5515 have been updated to include for-merge lines in .git/FETCH_HEAD where they have been configured for-merge in .git/config. Since we are now performing a union of the fetch specification and the merge specification and we cannot allow a branch to be listed twice (otherwise it comes out twice in .git/FETCH_HEAD) we need to perform a double loop here over all of the branch.$name.merge lines and try to set their merge flag if we have already schedule that branch for fetching by remote.$r.fetch. If no match is found then we must add new specifications to fetch the branch but not store it as no local tracking branch has been designated. Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
2007-09-18 10:54:53 +02:00
if (!branch || i < 0 || i >= branch->merge_nr)
return 0;
return refname_match(branch->merge[i]->src, refname);
}
__attribute__((format (printf,2,3)))
static const char *error_buf(struct strbuf *err, const char *fmt, ...)
{
if (err) {
va_list ap;
va_start(ap, fmt);
strbuf_vaddf(err, fmt, ap);
va_end(ap);
}
return NULL;
}
const char *branch_get_upstream(struct branch *branch, struct strbuf *err)
{
if (!branch)
return error_buf(err, _("HEAD does not point to a branch"));
if (!branch->merge || !branch->merge[0]) {
/*
* no merge config; is it because the user didn't define any,
* or because it is not a real branch, and get_branch
* auto-vivified it?
*/
if (!ref_exists(branch->refname))
return error_buf(err, _("no such branch: '%s'"),
branch->name);
return error_buf(err,
_("no upstream configured for branch '%s'"),
branch->name);
}
if (!branch->merge[0]->dst)
return error_buf(err,
_("upstream branch '%s' not stored as a remote-tracking branch"),
branch->merge[0]->src);
return branch->merge[0]->dst;
}
static const char *tracking_for_push_dest(struct remote *remote,
const char *refname,
struct strbuf *err)
{
char *ret;
ret = apply_refspecs(&remote->fetch, refname);
if (!ret)
return error_buf(err,
_("push destination '%s' on remote '%s' has no local tracking branch"),
refname, remote->name);
return ret;
}
static const char *branch_get_push_1(struct remote_state *remote_state,
struct branch *branch, struct strbuf *err)
{
struct remote *remote;
remote = remotes_remote_get(
remote_state,
remotes_pushremote_for_branch(remote_state, branch, NULL));
if (!remote)
return error_buf(err,
_("branch '%s' has no remote for pushing"),
branch->name);
if (remote->push.nr) {
char *dst;
const char *ret;
dst = apply_refspecs(&remote->push, branch->refname);
if (!dst)
return error_buf(err,
_("push refspecs for '%s' do not include '%s'"),
remote->name, branch->name);
ret = tracking_for_push_dest(remote, dst, err);
free(dst);
return ret;
}
if (remote->mirror)
return tracking_for_push_dest(remote, branch->refname, err);
switch (push_default) {
case PUSH_DEFAULT_NOTHING:
return error_buf(err, _("push has no destination (push.default is 'nothing')"));
case PUSH_DEFAULT_MATCHING:
case PUSH_DEFAULT_CURRENT:
return tracking_for_push_dest(remote, branch->refname, err);
case PUSH_DEFAULT_UPSTREAM:
return branch_get_upstream(branch, err);
case PUSH_DEFAULT_UNSPECIFIED:
case PUSH_DEFAULT_SIMPLE:
{
const char *up, *cur;
up = branch_get_upstream(branch, err);
if (!up)
return NULL;
cur = tracking_for_push_dest(remote, branch->refname, err);
if (!cur)
return NULL;
if (strcmp(cur, up))
return error_buf(err,
_("cannot resolve 'simple' push to a single destination"));
return cur;
}
}
BUG("unhandled push situation");
}
const char *branch_get_push(struct branch *branch, struct strbuf *err)
{
read_config(the_repository);
die_on_missing_branch(the_repository, branch);
if (!branch)
return error_buf(err, _("HEAD does not point to a branch"));
if (!branch->push_tracking_ref)
branch->push_tracking_ref = branch_get_push_1(
the_repository->remote_state, branch, err);
return branch->push_tracking_ref;
}
static int ignore_symref_update(const char *refname, struct strbuf *scratch)
{
return !refs_read_symbolic_ref(get_main_ref_store(the_repository), refname, scratch);
}
/*
* Create and return a list of (struct ref) consisting of copies of
* each remote_ref that matches refspec. refspec must be a pattern.
* Fill in the copies' peer_ref to describe the local tracking refs to
* which they map. Omit any references that would map to an existing
* local symbolic ref.
*/
static struct ref *get_expanded_map(const struct ref *remote_refs,
const struct refspec_item *refspec)
{
struct strbuf scratch = STRBUF_INIT;
const struct ref *ref;
struct ref *ret = NULL;
struct ref **tail = &ret;
for (ref = remote_refs; ref; ref = ref->next) {
char *expn_name = NULL;
strbuf_reset(&scratch);
if (strchr(ref->name, '^'))
continue; /* a dereference item */
if (match_name_with_pattern(refspec->src, ref->name,
refspec->dst, &expn_name) &&
!ignore_symref_update(expn_name, &scratch)) {
struct ref *cpy = copy_ref(ref);
cpy->peer_ref = alloc_ref(expn_name);
if (refspec->force)
cpy->peer_ref->force = 1;
*tail = cpy;
tail = &cpy->next;
}
free(expn_name);
}
strbuf_release(&scratch);
return ret;
}
static const struct ref *find_ref_by_name_abbrev(const struct ref *refs, const char *name)
{
const struct ref *ref;
remote: make refspec follow the same disambiguation rule as local refs When matching a non-wildcard LHS of a refspec against a list of refs, find_ref_by_name_abbrev() returns the first ref that matches using any DWIM rules used by refname_match() in refs.c, even if a better match occurs later in the list of refs. This causes unexpected behavior when (for example) fetching using the refspec "refs/heads/s:<something>" from a remote with both "refs/heads/refs/heads/s" and "refs/heads/s"; even if the former was inadvertently created, one would still expect the latter to be fetched. Similarly, when both a tag T and a branch T exist, fetching T should favor the tag, just like how local refname disambiguation rule works. But because the code walks over ls-remote output from the remote, which happens to be sorted in alphabetical order and has refs/heads/T before refs/tags/T, a request to fetch T is (mis)interpreted as fetching refs/heads/T. Update refname_match(), all of whose current callers care only if it returns non-zero (i.e. matches) to see if an abbreviated name can mean the full name being tested, so that it returns a positive integer whose magnitude can be used to tell the precedence, and fix the find_ref_by_name_abbrev() function not to stop at the first match but find the match with the highest precedence. This is based on an earlier work, which special cased only the exact matches, by Jonathan Tan. Helped-by: Jonathan Tan <jonathantanmy@google.com> Helped-by: Jonathan Nieder <jrnieder@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-08-01 18:22:37 +02:00
const struct ref *best_match = NULL;
int best_score = 0;
for (ref = refs; ref; ref = ref->next) {
remote: make refspec follow the same disambiguation rule as local refs When matching a non-wildcard LHS of a refspec against a list of refs, find_ref_by_name_abbrev() returns the first ref that matches using any DWIM rules used by refname_match() in refs.c, even if a better match occurs later in the list of refs. This causes unexpected behavior when (for example) fetching using the refspec "refs/heads/s:<something>" from a remote with both "refs/heads/refs/heads/s" and "refs/heads/s"; even if the former was inadvertently created, one would still expect the latter to be fetched. Similarly, when both a tag T and a branch T exist, fetching T should favor the tag, just like how local refname disambiguation rule works. But because the code walks over ls-remote output from the remote, which happens to be sorted in alphabetical order and has refs/heads/T before refs/tags/T, a request to fetch T is (mis)interpreted as fetching refs/heads/T. Update refname_match(), all of whose current callers care only if it returns non-zero (i.e. matches) to see if an abbreviated name can mean the full name being tested, so that it returns a positive integer whose magnitude can be used to tell the precedence, and fix the find_ref_by_name_abbrev() function not to stop at the first match but find the match with the highest precedence. This is based on an earlier work, which special cased only the exact matches, by Jonathan Tan. Helped-by: Jonathan Tan <jonathantanmy@google.com> Helped-by: Jonathan Nieder <jrnieder@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-08-01 18:22:37 +02:00
int score = refname_match(name, ref->name);
if (best_score < score) {
best_match = ref;
best_score = score;
}
}
remote: make refspec follow the same disambiguation rule as local refs When matching a non-wildcard LHS of a refspec against a list of refs, find_ref_by_name_abbrev() returns the first ref that matches using any DWIM rules used by refname_match() in refs.c, even if a better match occurs later in the list of refs. This causes unexpected behavior when (for example) fetching using the refspec "refs/heads/s:<something>" from a remote with both "refs/heads/refs/heads/s" and "refs/heads/s"; even if the former was inadvertently created, one would still expect the latter to be fetched. Similarly, when both a tag T and a branch T exist, fetching T should favor the tag, just like how local refname disambiguation rule works. But because the code walks over ls-remote output from the remote, which happens to be sorted in alphabetical order and has refs/heads/T before refs/tags/T, a request to fetch T is (mis)interpreted as fetching refs/heads/T. Update refname_match(), all of whose current callers care only if it returns non-zero (i.e. matches) to see if an abbreviated name can mean the full name being tested, so that it returns a positive integer whose magnitude can be used to tell the precedence, and fix the find_ref_by_name_abbrev() function not to stop at the first match but find the match with the highest precedence. This is based on an earlier work, which special cased only the exact matches, by Jonathan Tan. Helped-by: Jonathan Tan <jonathantanmy@google.com> Helped-by: Jonathan Nieder <jrnieder@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-08-01 18:22:37 +02:00
return best_match;
}
struct ref *get_remote_ref(const struct ref *remote_refs, const char *name)
{
const struct ref *ref = find_ref_by_name_abbrev(remote_refs, name);
if (!ref)
return NULL;
return copy_ref(ref);
}
static struct ref *get_local_ref(const char *name)
{
if (!name || name[0] == '\0')
return NULL;
if (starts_with(name, "refs/"))
return alloc_ref(name);
if (starts_with(name, "heads/") ||
starts_with(name, "tags/") ||
starts_with(name, "remotes/"))
return alloc_ref_with_prefix("refs/", 5, name);
return alloc_ref_with_prefix("refs/heads/", 11, name);
}
int get_fetch_map(const struct ref *remote_refs,
const struct refspec_item *refspec,
struct ref ***tail,
int missing_ok)
{
struct ref *ref_map, **rmp;
refspec: add support for negative refspecs Both fetch and push support pattern refspecs which allow fetching or pushing references that match a specific pattern. Because these patterns are globs, they have somewhat limited ability to express more complex situations. For example, suppose you wish to fetch all branches from a remote except for a specific one. To allow this, you must setup a set of refspecs which match only the branches you want. Because refspecs are either explicit name matches, or simple globs, many patterns cannot be expressed. Add support for a new type of refspec, referred to as "negative" refspecs. These are prefixed with a '^' and mean "exclude any ref matching this refspec". They can only have one "side" which always refers to the source. During a fetch, this refers to the name of the ref on the remote. During a push, this refers to the name of the ref on the local side. With negative refspecs, users can express more complex patterns. For example: git fetch origin refs/heads/*:refs/remotes/origin/* ^refs/heads/dontwant will fetch all branches on origin into remotes/origin, but will exclude fetching the branch named dontwant. Refspecs today are commutative, meaning that order doesn't expressly matter. Rather than forcing an implied order, negative refspecs will always be applied last. That is, in order to match, a ref must match at least one positive refspec, and match none of the negative refspecs. This is similar to how negative pathspecs work. Signed-off-by: Jacob Keller <jacob.keller@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-09-30 23:25:29 +02:00
if (refspec->negative)
return 0;
if (refspec->pattern) {
ref_map = get_expanded_map(remote_refs, refspec);
} else {
const char *name = refspec->src[0] ? refspec->src : "HEAD";
if (refspec->exact_sha1) {
ref_map = alloc_ref(name);
get_oid_hex(name, &ref_map->old_oid);
ref_map->exact_oid = 1;
} else {
ref_map = get_remote_ref(remote_refs, name);
}
if (!missing_ok && !ref_map)
die(_("couldn't find remote ref %s"), name);
if (ref_map) {
ref_map->peer_ref = get_local_ref(refspec->dst);
if (ref_map->peer_ref && refspec->force)
ref_map->peer_ref->force = 1;
}
}
for (rmp = &ref_map; *rmp; ) {
if ((*rmp)->peer_ref) {
if (!starts_with((*rmp)->peer_ref->name, "refs/") ||
check_refname_format((*rmp)->peer_ref->name, 0)) {
struct ref *ignore = *rmp;
error(_("* Ignoring funny ref '%s' locally"),
(*rmp)->peer_ref->name);
*rmp = (*rmp)->next;
free(ignore->peer_ref);
free(ignore);
continue;
}
}
rmp = &((*rmp)->next);
}
if (ref_map)
tail_link_ref(ref_map, tail);
return 0;
}
int resolve_remote_symref(struct ref *ref, struct ref *list)
{
if (!ref->symref)
return 0;
for (; list; list = list->next)
if (!strcmp(ref->symref, list->name)) {
oidcpy(&ref->old_oid, &list->old_oid);
return 0;
}
return 1;
}
/*
* Compute the commit ahead/behind values for the pair branch_name, base.
*
* If abf is AHEAD_BEHIND_FULL, compute the full ahead/behind and return the
* counts in *num_ours and *num_theirs. If abf is AHEAD_BEHIND_QUICK, skip
* the (potentially expensive) a/b computation (*num_ours and *num_theirs are
* set to zero).
*
* Returns -1 if num_ours and num_theirs could not be filled in (e.g., ref
* does not exist). Returns 0 if the commits are identical. Returns 1 if
* commits are different.
*/
static int stat_branch_pair(const char *branch_name, const char *base,
int *num_ours, int *num_theirs,
enum ahead_behind_flags abf)
{
struct object_id oid;
struct commit *ours, *theirs;
struct rev_info revs;
struct setup_revision_opt opt = {
.free_removed_argv_elements = 1,
};
struct strvec argv = STRVEC_INIT;
/* Cannot stat if what we used to build on no longer exists */
if (read_ref(base, &oid))
return -1;
theirs = lookup_commit_reference(the_repository, &oid);
if (!theirs)
return -1;
if (read_ref(branch_name, &oid))
return -1;
ours = lookup_commit_reference(the_repository, &oid);
if (!ours)
return -1;
*num_theirs = *num_ours = 0;
/* are we the same? */
if (theirs == ours)
return 0;
if (abf == AHEAD_BEHIND_QUICK)
return 1;
if (abf != AHEAD_BEHIND_FULL)
BUG("stat_branch_pair: invalid abf '%d'", abf);
/* Run "rev-list --left-right ours...theirs" internally... */
strvec_push(&argv, ""); /* ignored */
strvec_push(&argv, "--left-right");
strvec_pushf(&argv, "%s...%s",
oid_to_hex(&ours->object.oid),
oid_to_hex(&theirs->object.oid));
strvec_push(&argv, "--");
repo_init_revisions(the_repository, &revs, NULL);
setup_revisions(argv.nr, argv.v, &revs, &opt);
if (prepare_revision_walk(&revs))
die(_("revision walk setup failed"));
/* ... and count the commits on each side. */
while (1) {
struct commit *c = get_revision(&revs);
if (!c)
break;
if (c->object.flags & SYMMETRIC_LEFT)
(*num_ours)++;
else
(*num_theirs)++;
}
/* clear object flags smudged by the above traversal */
clear_commit_marks(ours, ALL_REV_FLAGS);
clear_commit_marks(theirs, ALL_REV_FLAGS);
strvec_clear(&argv);
release_revisions(&revs);
return 1;
}
/*
* Lookup the tracking branch for the given branch and if present, optionally
* compute the commit ahead/behind values for the pair.
*
* If for_push is true, the tracking branch refers to the push branch,
* otherwise it refers to the upstream branch.
*
* The name of the tracking branch (or NULL if it is not defined) is
* returned via *tracking_name, if it is not itself NULL.
*
* If abf is AHEAD_BEHIND_FULL, compute the full ahead/behind and return the
* counts in *num_ours and *num_theirs. If abf is AHEAD_BEHIND_QUICK, skip
* the (potentially expensive) a/b computation (*num_ours and *num_theirs are
* set to zero).
*
* Returns -1 if num_ours and num_theirs could not be filled in (e.g., no
* upstream defined, or ref does not exist). Returns 0 if the commits are
* identical. Returns 1 if commits are different.
*/
int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
const char **tracking_name, int for_push,
enum ahead_behind_flags abf)
{
const char *base;
/* Cannot stat unless we are marked to build on top of somebody else. */
base = for_push ? branch_get_push(branch, NULL) :
branch_get_upstream(branch, NULL);
if (tracking_name)
*tracking_name = base;
if (!base)
return -1;
return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
}
/*
* Return true when there is anything to report, otherwise false.
*/
int format_tracking_info(struct branch *branch, struct strbuf *sb,
enum ahead_behind_flags abf)
{
int ours, theirs, sti;
const char *full_base;
char *base;
int upstream_is_gone = 0;
sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
if (sti < 0) {
if (!full_base)
return 0;
upstream_is_gone = 1;
}
base = shorten_unambiguous_ref(full_base, 0);
if (upstream_is_gone) {
strbuf_addf(sb,
_("Your branch is based on '%s', but the upstream is gone.\n"),
base);
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git branch --unset-upstream\" to fixup)\n"));
} else if (!sti) {
strbuf_addf(sb,
_("Your branch is up to date with '%s'.\n"),
base);
} else if (abf == AHEAD_BEHIND_QUICK) {
strbuf_addf(sb,
_("Your branch and '%s' refer to different commits.\n"),
base);
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addf(sb, _(" (use \"%s\" for details)\n"),
"git status --ahead-behind");
} else if (!theirs) {
strbuf_addf(sb,
Q_("Your branch is ahead of '%s' by %d commit.\n",
"Your branch is ahead of '%s' by %d commits.\n",
ours),
base, ours);
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git push\" to publish your local commits)\n"));
} else if (!ours) {
strbuf_addf(sb,
Q_("Your branch is behind '%s' by %d commit, "
"and can be fast-forwarded.\n",
"Your branch is behind '%s' by %d commits, "
"and can be fast-forwarded.\n",
theirs),
base, theirs);
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git pull\" to update your local branch)\n"));
} else {
strbuf_addf(sb,
Q_("Your branch and '%s' have diverged,\n"
"and have %d and %d different commit each, "
"respectively.\n",
"Your branch and '%s' have diverged,\n"
"and have %d and %d different commits each, "
"respectively.\n",
ours + theirs),
base, ours, theirs);
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git pull\" to merge the remote branch into yours)\n"));
}
free(base);
return 1;
}
static int one_local_ref(const char *refname, const struct object_id *oid,
int flag UNUSED,
void *cb_data)
{
struct ref ***local_tail = cb_data;
struct ref *ref;
/* we already know it starts with refs/ to get here */
if (check_refname_format(refname + 5, 0))
return 0;
ref = alloc_ref(refname);
oidcpy(&ref->new_oid, oid);
**local_tail = ref;
*local_tail = &ref->next;
return 0;
}
struct ref *get_local_heads(void)
{
struct ref *local_refs = NULL, **local_tail = &local_refs;
for_each_ref(one_local_ref, &local_tail);
return local_refs;
}
struct ref *guess_remote_head(const struct ref *head,
const struct ref *refs,
int all)
{
const struct ref *r;
struct ref *list = NULL;
struct ref **tail = &list;
if (!head)
return NULL;
/*
* Some transports support directly peeking at
* where HEAD points; if that is the case, then
* we don't have to guess.
*/
if (head->symref)
return copy_ref(find_ref_by_name(refs, head->symref));
/* If a remote branch exists with the default branch name, let's use it. */
if (!all) {
char *ref = xstrfmt("refs/heads/%s",
git_default_branch_name(0));
r = find_ref_by_name(refs, ref);
free(ref);
if (r && oideq(&r->old_oid, &head->old_oid))
return copy_ref(r);
/* Fall back to the hard-coded historical default */
r = find_ref_by_name(refs, "refs/heads/master");
if (r && oideq(&r->old_oid, &head->old_oid))
return copy_ref(r);
}
/* Look for another ref that points there */
for (r = refs; r; r = r->next) {
if (r != head &&
starts_with(r->name, "refs/heads/") &&
oideq(&r->old_oid, &head->old_oid)) {
*tail = copy_ref(r);
tail = &((*tail)->next);
if (!all)
break;
}
}
return list;
}
struct stale_heads_info {
struct string_list *ref_names;
struct ref **stale_refs_tail;
struct refspec *rs;
};
static int get_stale_heads_cb(const char *refname, const struct object_id *oid,
int flags, void *cb_data)
{
struct stale_heads_info *info = cb_data;
struct string_list matches = STRING_LIST_INIT_DUP;
struct refspec_item query;
int i, stale = 1;
memset(&query, 0, sizeof(struct refspec_item));
query.dst = (char *)refname;
query_refspecs_multiple(info->rs, &query, &matches);
if (matches.nr == 0)
goto clean_exit; /* No matches */
/*
* If we did find a suitable refspec and it's not a symref and
* it's not in the list of refs that currently exist in that
* remote, we consider it to be stale. In order to deal with
* overlapping refspecs, we need to go over all of the
* matching refs.
*/
if (flags & REF_ISSYMREF)
goto clean_exit;
for (i = 0; stale && i < matches.nr; i++)
if (string_list_has_string(info->ref_names, matches.items[i].string))
stale = 0;
if (stale) {
struct ref *ref = make_linked_ref(refname, &info->stale_refs_tail);
oidcpy(&ref->new_oid, oid);
}
clean_exit:
string_list_clear(&matches, 0);
return 0;
}
struct ref *get_stale_heads(struct refspec *rs, struct ref *fetch_map)
{
struct ref *ref, *stale_refs = NULL;
struct string_list ref_names = STRING_LIST_INIT_NODUP;
struct stale_heads_info info;
info.ref_names = &ref_names;
info.stale_refs_tail = &stale_refs;
info.rs = rs;
for (ref = fetch_map; ref; ref = ref->next)
string_list_append(&ref_names, ref->name);
string_list_sort(&ref_names);
for_each_ref(get_stale_heads_cb, &info);
string_list_clear(&ref_names, 0);
return stale_refs;
}
remote.c: add command line option parser for "--force-with-lease" Update "git push" and "git send-pack" to parse this commnd line option. The intended sematics is: * "--force-with-lease" alone, without specifying the details, will protect _all_ remote refs that are going to be updated by requiring their current value to be the same as some reasonable default, unless otherwise specified; * "--force-with-lease=refname", without specifying the expected value, will protect that refname, if it is going to be updated, by requiring its current value to be the same as some reasonable default. * "--force-with-lease=refname:value" will protect that refname, if it is going to be updated, by requiring its current value to be the same as the specified value; and * "--no-force-with-lease" will cancel all the previous --force-with-lease on the command line. For now, "some reasonable default" is tentatively defined as "the value of the remote-tracking branch we have for the ref of the remote being updated", and it is an error if we do not have such a remote-tracking branch. But this is known to be fragile, its use is not yet recommended, and hopefully we will find more reasonable default as we gain experience with this feature. The manual marks the feature as experimental unless the expected value is specified explicitly for this reason. Because the command line options are parsed _before_ we know which remote we are pushing to, there needs further processing to the parsed data after we instantiate the transport object to: * expand "refname" given by the user to a full refname to be matched with the list of "struct ref" used in match_push_refs() and set_ref_status_for_push(); and * learning the actual local ref that is the remote-tracking branch for the specified remote ref. Further, some processing need to be deferred until we find the set of remote refs and match_push_refs() returns in order to find the ones that need to be checked after explicit ones have been processed for "--force-with-lease" (no specific details). These post-processing will be the topic of the next patch. This option was originally called "cas" (for "compare and swap"), the name which nobody liked because it was too technical. The second attempt called it "lockref" (because it is conceptually like pushing after taking a lock) but the word "lock" was hated because it implied that it may reject push by others, which is not the way this option works. This round calls it "force-with-lease". You assume you took the lease on the ref when you fetched to decide what the rebased history should be, and you can push back only if the lease has not been broken. Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-07-09 00:34:36 +02:00
/*
* Compare-and-swap
*/
static void clear_cas_option(struct push_cas_option *cas)
remote.c: add command line option parser for "--force-with-lease" Update "git push" and "git send-pack" to parse this commnd line option. The intended sematics is: * "--force-with-lease" alone, without specifying the details, will protect _all_ remote refs that are going to be updated by requiring their current value to be the same as some reasonable default, unless otherwise specified; * "--force-with-lease=refname", without specifying the expected value, will protect that refname, if it is going to be updated, by requiring its current value to be the same as some reasonable default. * "--force-with-lease=refname:value" will protect that refname, if it is going to be updated, by requiring its current value to be the same as the specified value; and * "--no-force-with-lease" will cancel all the previous --force-with-lease on the command line. For now, "some reasonable default" is tentatively defined as "the value of the remote-tracking branch we have for the ref of the remote being updated", and it is an error if we do not have such a remote-tracking branch. But this is known to be fragile, its use is not yet recommended, and hopefully we will find more reasonable default as we gain experience with this feature. The manual marks the feature as experimental unless the expected value is specified explicitly for this reason. Because the command line options are parsed _before_ we know which remote we are pushing to, there needs further processing to the parsed data after we instantiate the transport object to: * expand "refname" given by the user to a full refname to be matched with the list of "struct ref" used in match_push_refs() and set_ref_status_for_push(); and * learning the actual local ref that is the remote-tracking branch for the specified remote ref. Further, some processing need to be deferred until we find the set of remote refs and match_push_refs() returns in order to find the ones that need to be checked after explicit ones have been processed for "--force-with-lease" (no specific details). These post-processing will be the topic of the next patch. This option was originally called "cas" (for "compare and swap"), the name which nobody liked because it was too technical. The second attempt called it "lockref" (because it is conceptually like pushing after taking a lock) but the word "lock" was hated because it implied that it may reject push by others, which is not the way this option works. This round calls it "force-with-lease". You assume you took the lease on the ref when you fetched to decide what the rebased history should be, and you can push back only if the lease has not been broken. Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-07-09 00:34:36 +02:00
{
int i;
for (i = 0; i < cas->nr; i++)
free(cas->entry[i].refname);
free(cas->entry);
memset(cas, 0, sizeof(*cas));
}
static struct push_cas *add_cas_entry(struct push_cas_option *cas,
const char *refname,
size_t refnamelen)
{
struct push_cas *entry;
ALLOC_GROW(cas->entry, cas->nr + 1, cas->alloc);
entry = &cas->entry[cas->nr++];
memset(entry, 0, sizeof(*entry));
entry->refname = xmemdupz(refname, refnamelen);
return entry;
}
static int parse_push_cas_option(struct push_cas_option *cas, const char *arg, int unset)
remote.c: add command line option parser for "--force-with-lease" Update "git push" and "git send-pack" to parse this commnd line option. The intended sematics is: * "--force-with-lease" alone, without specifying the details, will protect _all_ remote refs that are going to be updated by requiring their current value to be the same as some reasonable default, unless otherwise specified; * "--force-with-lease=refname", without specifying the expected value, will protect that refname, if it is going to be updated, by requiring its current value to be the same as some reasonable default. * "--force-with-lease=refname:value" will protect that refname, if it is going to be updated, by requiring its current value to be the same as the specified value; and * "--no-force-with-lease" will cancel all the previous --force-with-lease on the command line. For now, "some reasonable default" is tentatively defined as "the value of the remote-tracking branch we have for the ref of the remote being updated", and it is an error if we do not have such a remote-tracking branch. But this is known to be fragile, its use is not yet recommended, and hopefully we will find more reasonable default as we gain experience with this feature. The manual marks the feature as experimental unless the expected value is specified explicitly for this reason. Because the command line options are parsed _before_ we know which remote we are pushing to, there needs further processing to the parsed data after we instantiate the transport object to: * expand "refname" given by the user to a full refname to be matched with the list of "struct ref" used in match_push_refs() and set_ref_status_for_push(); and * learning the actual local ref that is the remote-tracking branch for the specified remote ref. Further, some processing need to be deferred until we find the set of remote refs and match_push_refs() returns in order to find the ones that need to be checked after explicit ones have been processed for "--force-with-lease" (no specific details). These post-processing will be the topic of the next patch. This option was originally called "cas" (for "compare and swap"), the name which nobody liked because it was too technical. The second attempt called it "lockref" (because it is conceptually like pushing after taking a lock) but the word "lock" was hated because it implied that it may reject push by others, which is not the way this option works. This round calls it "force-with-lease". You assume you took the lease on the ref when you fetched to decide what the rebased history should be, and you can push back only if the lease has not been broken. Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-07-09 00:34:36 +02:00
{
const char *colon;
struct push_cas *entry;
if (unset) {
/* "--no-<option>" */
clear_cas_option(cas);
return 0;
}
if (!arg) {
/* just "--<option>" */
cas->use_tracking_for_rest = 1;
return 0;
}
/* "--<option>=refname" or "--<option>=refname:value" */
colon = strchrnul(arg, ':');
entry = add_cas_entry(cas, arg, colon - arg);
if (!*colon)
entry->use_tracking = 1;
else if (!colon[1])
oidclr(&entry->expect);
else if (repo_get_oid(the_repository, colon + 1, &entry->expect))
return error(_("cannot parse expected object name '%s'"),
colon + 1);
remote.c: add command line option parser for "--force-with-lease" Update "git push" and "git send-pack" to parse this commnd line option. The intended sematics is: * "--force-with-lease" alone, without specifying the details, will protect _all_ remote refs that are going to be updated by requiring their current value to be the same as some reasonable default, unless otherwise specified; * "--force-with-lease=refname", without specifying the expected value, will protect that refname, if it is going to be updated, by requiring its current value to be the same as some reasonable default. * "--force-with-lease=refname:value" will protect that refname, if it is going to be updated, by requiring its current value to be the same as the specified value; and * "--no-force-with-lease" will cancel all the previous --force-with-lease on the command line. For now, "some reasonable default" is tentatively defined as "the value of the remote-tracking branch we have for the ref of the remote being updated", and it is an error if we do not have such a remote-tracking branch. But this is known to be fragile, its use is not yet recommended, and hopefully we will find more reasonable default as we gain experience with this feature. The manual marks the feature as experimental unless the expected value is specified explicitly for this reason. Because the command line options are parsed _before_ we know which remote we are pushing to, there needs further processing to the parsed data after we instantiate the transport object to: * expand "refname" given by the user to a full refname to be matched with the list of "struct ref" used in match_push_refs() and set_ref_status_for_push(); and * learning the actual local ref that is the remote-tracking branch for the specified remote ref. Further, some processing need to be deferred until we find the set of remote refs and match_push_refs() returns in order to find the ones that need to be checked after explicit ones have been processed for "--force-with-lease" (no specific details). These post-processing will be the topic of the next patch. This option was originally called "cas" (for "compare and swap"), the name which nobody liked because it was too technical. The second attempt called it "lockref" (because it is conceptually like pushing after taking a lock) but the word "lock" was hated because it implied that it may reject push by others, which is not the way this option works. This round calls it "force-with-lease". You assume you took the lease on the ref when you fetched to decide what the rebased history should be, and you can push back only if the lease has not been broken. Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-07-09 00:34:36 +02:00
return 0;
}
int parseopt_push_cas_option(const struct option *opt, const char *arg, int unset)
{
return parse_push_cas_option(opt->value, arg, unset);
}
int is_empty_cas(const struct push_cas_option *cas)
{
return !cas->use_tracking_for_rest && !cas->nr;
}
/*
* Look at remote.fetch refspec and see if we have a remote
* tracking branch for the refname there. Fill the name of
* the remote-tracking branch in *dst_refname, and the name
* of the commit object at its tip in oid[].
* If we cannot do so, return negative to signal an error.
*/
static int remote_tracking(struct remote *remote, const char *refname,
struct object_id *oid, char **dst_refname)
{
char *dst;
dst = apply_refspecs(&remote->fetch, refname);
if (!dst)
return -1; /* no tracking ref for refname at remote */
if (read_ref(dst, oid))
return -1; /* we know what the tracking ref is but we cannot read it */
*dst_refname = dst;
return 0;
}
/*
* The struct "reflog_commit_array" and related helper functions
* are used for collecting commits into an array during reflog
* traversals in "check_and_collect_until()".
*/
struct reflog_commit_array {
struct commit **item;
size_t nr, alloc;
};
#define REFLOG_COMMIT_ARRAY_INIT { 0 }
/* Append a commit to the array. */
static void append_commit(struct reflog_commit_array *arr,
struct commit *commit)
{
ALLOC_GROW(arr->item, arr->nr + 1, arr->alloc);
arr->item[arr->nr++] = commit;
}
/* Free and reset the array. */
static void free_commit_array(struct reflog_commit_array *arr)
{
FREE_AND_NULL(arr->item);
arr->nr = arr->alloc = 0;
}
struct check_and_collect_until_cb_data {
struct commit *remote_commit;
struct reflog_commit_array *local_commits;
timestamp_t remote_reflog_timestamp;
};
/* Get the timestamp of the latest entry. */
static int peek_reflog(struct object_id *o_oid UNUSED,
struct object_id *n_oid UNUSED,
const char *ident UNUSED,
timestamp_t timestamp, int tz UNUSED,
const char *message UNUSED, void *cb_data)
{
timestamp_t *ts = cb_data;
*ts = timestamp;
return 1;
}
static int check_and_collect_until(struct object_id *o_oid UNUSED,
struct object_id *n_oid,
const char *ident UNUSED,
timestamp_t timestamp, int tz UNUSED,
const char *message UNUSED, void *cb_data)
{
struct commit *commit;
struct check_and_collect_until_cb_data *cb = cb_data;
/* An entry was found. */
if (oideq(n_oid, &cb->remote_commit->object.oid))
return 1;
if ((commit = lookup_commit_reference(the_repository, n_oid)))
append_commit(cb->local_commits, commit);
/*
* If the reflog entry timestamp is older than the remote ref's
* latest reflog entry, there is no need to check or collect
* entries older than this one.
*/
if (timestamp < cb->remote_reflog_timestamp)
return -1;
return 0;
}
#define MERGE_BASES_BATCH_SIZE 8
/*
* Iterate through the reflog of the local ref to check if there is an entry
* for the given remote-tracking ref; runs until the timestamp of an entry is
* older than latest timestamp of remote-tracking ref's reflog. Any commits
* are that seen along the way are collected into an array to check if the
* remote-tracking ref is reachable from any of them.
*/
static int is_reachable_in_reflog(const char *local, const struct ref *remote)
{
timestamp_t date;
struct commit *commit;
struct commit **chunk;
struct check_and_collect_until_cb_data cb;
struct reflog_commit_array arr = REFLOG_COMMIT_ARRAY_INIT;
size_t size = 0;
int ret = 0;
commit = lookup_commit_reference(the_repository, &remote->old_oid);
if (!commit)
goto cleanup_return;
/*
* Get the timestamp from the latest entry
* of the remote-tracking ref's reflog.
*/
for_each_reflog_ent_reverse(remote->tracking_ref, peek_reflog, &date);
cb.remote_commit = commit;
cb.local_commits = &arr;
cb.remote_reflog_timestamp = date;
ret = for_each_reflog_ent_reverse(local, check_and_collect_until, &cb);
/* We found an entry in the reflog. */
if (ret > 0)
goto cleanup_return;
/*
* Check if the remote commit is reachable from any
* of the commits in the collected array, in batches.
*/
for (chunk = arr.item; chunk < arr.item + arr.nr; chunk += size) {
size = arr.item + arr.nr - chunk;
if (MERGE_BASES_BATCH_SIZE < size)
size = MERGE_BASES_BATCH_SIZE;
if ((ret = repo_in_merge_bases_many(the_repository, commit, size, chunk)))
break;
}
cleanup_return:
free_commit_array(&arr);
return ret;
}
/*
* Check for reachability of a remote-tracking
* ref in the reflog entries of its local ref.
*/
static void check_if_includes_upstream(struct ref *remote)
{
struct ref *local = get_local_ref(remote->name);
if (!local)
return;
if (is_reachable_in_reflog(local->name, remote) <= 0)
remote->unreachable = 1;
}
static void apply_cas(struct push_cas_option *cas,
struct remote *remote,
struct ref *ref)
{
int i;
/* Find an explicit --<option>=<name>[:<value>] entry */
for (i = 0; i < cas->nr; i++) {
struct push_cas *entry = &cas->entry[i];
if (!refname_match(entry->refname, ref->name))
continue;
ref->expect_old_sha1 = 1;
if (!entry->use_tracking)
oidcpy(&ref->old_oid_expect, &entry->expect);
else if (remote_tracking(remote, ref->name,
&ref->old_oid_expect,
&ref->tracking_ref))
oidclr(&ref->old_oid_expect);
else
ref->check_reachable = cas->use_force_if_includes;
return;
}
/* Are we using "--<option>" to cover all? */
if (!cas->use_tracking_for_rest)
return;
ref->expect_old_sha1 = 1;
if (remote_tracking(remote, ref->name,
&ref->old_oid_expect,
&ref->tracking_ref))
oidclr(&ref->old_oid_expect);
else
ref->check_reachable = cas->use_force_if_includes;
}
void apply_push_cas(struct push_cas_option *cas,
struct remote *remote,
struct ref *remote_refs)
{
struct ref *ref;
for (ref = remote_refs; ref; ref = ref->next) {
apply_cas(cas, remote, ref);
/*
* If "compare-and-swap" is in "use_tracking[_for_rest]"
* mode, and if "--force-if-includes" was specified, run
* the check.
*/
if (ref->check_reachable)
check_if_includes_upstream(ref);
}
}
struct remote_state *remote_state_new(void)
{
struct remote_state *r = xmalloc(sizeof(*r));
memset(r, 0, sizeof(*r));
hashmap_init(&r->remotes_hash, remotes_hash_cmp, NULL, 0);
hashmap_init(&r->branches_hash, branches_hash_cmp, NULL, 0);
return r;
}
void remote_state_clear(struct remote_state *remote_state)
{
int i;
for (i = 0; i < remote_state->remotes_nr; i++)
remote_clear(remote_state->remotes[i]);
FREE_AND_NULL(remote_state->remotes);
remote_state->remotes_alloc = 0;
remote_state->remotes_nr = 0;
hashmap_clear_and_free(&remote_state->remotes_hash, struct remote, ent);
hashmap_clear_and_free(&remote_state->branches_hash, struct remote, ent);
}
/*
* Returns 1 if it was the last chop before ':'.
*/
static int chop_last_dir(char **remoteurl, int is_relative)
{
char *rfind = find_last_dir_sep(*remoteurl);
if (rfind) {
*rfind = '\0';
return 0;
}
rfind = strrchr(*remoteurl, ':');
if (rfind) {
*rfind = '\0';
return 1;
}
if (is_relative || !strcmp(".", *remoteurl))
die(_("cannot strip one component off url '%s'"),
*remoteurl);
free(*remoteurl);
*remoteurl = xstrdup(".");
return 0;
}
char *relative_url(const char *remote_url, const char *url,
const char *up_path)
{
int is_relative = 0;
int colonsep = 0;
char *out;
char *remoteurl;
struct strbuf sb = STRBUF_INIT;
size_t len;
if (!url_is_local_not_ssh(url) || is_absolute_path(url))
return xstrdup(url);
len = strlen(remote_url);
if (!len)
BUG("invalid empty remote_url");
remoteurl = xstrdup(remote_url);
if (is_dir_sep(remoteurl[len-1]))
remoteurl[len-1] = '\0';
if (!url_is_local_not_ssh(remoteurl) || is_absolute_path(remoteurl))
is_relative = 0;
else {
is_relative = 1;
/*
* Prepend a './' to ensure all relative
* remoteurls start with './' or '../'
*/
if (!starts_with_dot_slash_native(remoteurl) &&
!starts_with_dot_dot_slash_native(remoteurl)) {
strbuf_reset(&sb);
strbuf_addf(&sb, "./%s", remoteurl);
free(remoteurl);
remoteurl = strbuf_detach(&sb, NULL);
}
}
/*
* When the url starts with '../', remove that and the
* last directory in remoteurl.
*/
while (*url) {
if (starts_with_dot_dot_slash_native(url)) {
url += 3;
colonsep |= chop_last_dir(&remoteurl, is_relative);
} else if (starts_with_dot_slash_native(url))
url += 2;
else
break;
}
strbuf_reset(&sb);
strbuf_addf(&sb, "%s%s%s", remoteurl, colonsep ? ":" : "/", url);
if (ends_with(url, "/"))
strbuf_setlen(&sb, sb.len - 1);
free(remoteurl);
if (starts_with_dot_slash_native(sb.buf))
out = xstrdup(sb.buf + 2);
else
out = xstrdup(sb.buf);
if (!up_path || !is_relative) {
strbuf_release(&sb);
return out;
}
strbuf_reset(&sb);
strbuf_addf(&sb, "%s%s", up_path, out);
free(out);
return strbuf_detach(&sb, NULL);
}