2023-03-21 14:25:53 +08:00
|
|
|
#include "cache.h"
|
2023-03-21 14:25:58 +08:00
|
|
|
#include "abspath.h"
|
2023-02-24 08:09:24 +08:00
|
|
|
#include "alloc.h"
|
2017-06-15 02:07:36 +08:00
|
|
|
#include "config.h"
|
2023-03-21 14:26:03 +08:00
|
|
|
#include "environment.h"
|
2023-03-21 14:25:54 +08:00
|
|
|
#include "gettext.h"
|
2023-02-24 08:09:27 +08:00
|
|
|
#include "hex.h"
|
2007-05-12 23:45:53 +08:00
|
|
|
#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 22:36:16 +08:00
|
|
|
#include "urlmatch.h"
|
2007-05-12 23:45:53 +08:00
|
|
|
#include "refs.h"
|
2018-05-17 06:57:48 +08:00
|
|
|
#include "refspec.h"
|
2018-05-16 07:42:15 +08:00
|
|
|
#include "object-store.h"
|
2008-07-02 15:51:18 +08:00
|
|
|
#include "commit.h"
|
|
|
|
#include "diff.h"
|
|
|
|
#include "revision.h"
|
2009-01-10 20:07:50 +08:00
|
|
|
#include "dir.h"
|
2009-02-25 16:32:12 +08:00
|
|
|
#include "tag.h"
|
2023-03-21 14:26:05 +08:00
|
|
|
#include "setup.h"
|
2009-10-26 05:28:11 +08:00
|
|
|
#include "string-list.h"
|
2020-07-29 04:23:39 +08:00
|
|
|
#include "strvec.h"
|
2018-07-21 00:33:04 +08:00
|
|
|
#include "commit-reach.h"
|
push: add an advice on unqualified <dst> push
Add an advice to the recently improved error message added in
f8aae12034 ("push: allow unqualified dest refspecs to DWIM",
2008-04-23).
Now with advice.pushUnqualifiedRefName=true (on by default) we show a
hint about how to proceed:
$ ./git-push avar v2.19.0^{commit}:newbranch -n
error: The destination you provided is not a full refname (i.e.,
starting with "refs/"). We tried to guess what you meant by:
- Looking for a ref that matches 'newbranch' on the remote side.
- Checking if the <src> being pushed ('v2.19.0^{commit}')
is a ref in "refs/{heads,tags}/". If so we add a corresponding
refs/{heads,tags}/ prefix on the remote side.
Neither worked, so we gave up. You must fully qualify the ref.
hint: The <src> part of the refspec is a commit object.
hint: Did you mean to create a new branch by pushing to
hint: 'v2.19.0^{commit}:refs/heads/newbranch'?
error: failed to push some refs to 'git@github.com:avar/git.git'
When trying to push a tag, tree or a blob we suggest that perhaps the
user meant to push them to refs/tags/ instead.
The if/else duplication for all of OBJ_{COMMIT,TAG,TREE,BLOB} is
unfortunate, but is required to correctly mark the messages for
translation. See the discussion in
<87r2gxebsi.fsf@evledraar.gmail.com> about that.
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-11-14 03:52:43 +08:00
|
|
|
#include "advice.h"
|
2022-05-17 04:11:03 +08:00
|
|
|
#include "connect.h"
|
treewide: include parse-options.h in source files
The builtins 'ls-remote', 'pack-objects', 'receive-pack', 'reflog' and
'send-pack' use parse_options(), but their source files don't directly
include 'parse-options.h'. Furthermore, the source files
'diagnose.c', 'list-objects-filter-options.c', 'remote.c' and
'send-pack.c' define option parsing callback functions, while
'revision.c' defines an option parsing helper function, and thus need
access to various fields in 'struct option' and 'struct
parse_opt_ctx_t', but they don't directly include 'parse-options.h'
either. They all can still be built, of course, because they include
one of the header files that does include 'parse-options.h' (though
unnecessarily, see the next commit).
Add those missing includes to these files, as our general rule is that
"a C file must directly include the header files that declare the
functions and the types it uses".
Signed-off-by: SZEDER Gábor <szeder.dev@gmail.com>
Reviewed-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2023-03-20 00:27:11 +08:00
|
|
|
#include "parse-options.h"
|
2007-05-12 23:45:53 +08:00
|
|
|
|
2012-02-23 06:43:41 +08:00
|
|
|
enum map_direction { FROM_SRC, FROM_DST };
|
|
|
|
|
2008-02-25 14:25:04 +08:00
|
|
|
struct counted_string {
|
|
|
|
size_t len;
|
|
|
|
const char *s;
|
|
|
|
};
|
2008-02-21 02:43:53 +08:00
|
|
|
|
2009-11-18 09:42:23 +08:00
|
|
|
static int valid_remote(const struct remote *remote)
|
|
|
|
{
|
2009-11-18 09:42:25 +08:00
|
|
|
return (!!remote->url) || (!!remote->foreign_vcs);
|
2009-11-18 09:42:23 +08:00
|
|
|
}
|
|
|
|
|
2009-09-07 16:56:00 +08:00
|
|
|
static const char *alias_url(const char *url, struct rewrites *r)
|
2008-02-21 02:43:53 +08:00
|
|
|
{
|
|
|
|
int i, j;
|
2008-02-25 14:25:04 +08:00
|
|
|
struct counted_string *longest;
|
|
|
|
int longest_i;
|
|
|
|
|
|
|
|
longest = NULL;
|
|
|
|
longest_i = -1;
|
2009-09-07 16:56:00 +08:00
|
|
|
for (i = 0; i < r->rewrite_nr; i++) {
|
|
|
|
if (!r->rewrite[i])
|
2008-02-21 02:43:53 +08:00
|
|
|
continue;
|
2009-09-07 16:56:00 +08:00
|
|
|
for (j = 0; j < r->rewrite[i]->instead_of_nr; j++) {
|
2013-12-01 04:55:40 +08:00
|
|
|
if (starts_with(url, r->rewrite[i]->instead_of[j].s) &&
|
2008-02-25 14:25:04 +08:00
|
|
|
(!longest ||
|
2009-09-07 16:56:00 +08:00
|
|
|
longest->len < r->rewrite[i]->instead_of[j].len)) {
|
|
|
|
longest = &(r->rewrite[i]->instead_of[j]);
|
2008-02-25 14:25:04 +08:00
|
|
|
longest_i = i;
|
2008-02-21 02:43:53 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2008-02-25 14:25:04 +08:00
|
|
|
if (!longest)
|
|
|
|
return url;
|
|
|
|
|
2015-09-25 05:07:03 +08:00
|
|
|
return xstrfmt("%s%s", r->rewrite[longest_i]->base, url + longest->len);
|
2008-02-21 02:43:53 +08:00
|
|
|
}
|
|
|
|
|
2007-09-19 12:49:27 +08:00
|
|
|
static void add_url(struct remote *remote, const char *url)
|
2007-05-12 23:45:53 +08:00
|
|
|
{
|
2008-02-19 12:41:41 +08:00
|
|
|
ALLOC_GROW(remote->url, remote->url_nr + 1, remote->url_alloc);
|
|
|
|
remote->url[remote->url_nr++] = url;
|
2007-05-12 23:45:53 +08:00
|
|
|
}
|
|
|
|
|
2009-06-10 00:01:34 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2021-11-18 08:53:23 +08:00
|
|
|
static void add_pushurl_alias(struct remote_state *remote_state,
|
|
|
|
struct remote *remote, const char *url)
|
2009-09-07 16:56:33 +08:00
|
|
|
{
|
2021-11-18 08:53:23 +08:00
|
|
|
const char *pushurl = alias_url(url, &remote_state->rewrites_push);
|
2009-09-07 16:56:33 +08:00
|
|
|
if (pushurl != url)
|
|
|
|
add_pushurl(remote, pushurl);
|
|
|
|
}
|
|
|
|
|
2021-11-18 08:53:23 +08:00
|
|
|
static void add_url_alias(struct remote_state *remote_state,
|
|
|
|
struct remote *remote, const char *url)
|
2009-09-07 16:56:33 +08:00
|
|
|
{
|
2021-11-18 08:53:23 +08:00
|
|
|
add_url(remote, alias_url(url, &remote_state->rewrites));
|
|
|
|
add_pushurl_alias(remote_state, remote, url);
|
2009-09-07 16:56:33 +08:00
|
|
|
}
|
|
|
|
|
2014-07-29 22:43:39 +08:00
|
|
|
struct remotes_hash_key {
|
|
|
|
const char *str;
|
|
|
|
int len;
|
|
|
|
};
|
|
|
|
|
2022-08-26 01:09:48 +08:00
|
|
|
static int remotes_hash_cmp(const void *cmp_data UNUSED,
|
2019-10-07 07:30:37 +08:00
|
|
|
const struct hashmap_entry *eptr,
|
|
|
|
const struct hashmap_entry *entry_or_key,
|
2017-07-01 08:28:35 +08:00
|
|
|
const void *keydata)
|
2014-07-29 22:43:39 +08:00
|
|
|
{
|
2019-10-07 07:30:37 +08:00
|
|
|
const struct remote *a, *b;
|
2017-07-01 08:28:35 +08:00
|
|
|
const struct remotes_hash_key *key = keydata;
|
|
|
|
|
2019-10-07 07:30:37 +08:00
|
|
|
a = container_of(eptr, const struct remote, ent);
|
|
|
|
b = container_of(entry_or_key, const struct remote, ent);
|
|
|
|
|
2014-07-29 22:43:39 +08:00
|
|
|
if (key)
|
|
|
|
return strncmp(a->name, key->str, key->len) || a->name[key->len];
|
|
|
|
else
|
|
|
|
return strcmp(a->name, b->name);
|
|
|
|
}
|
|
|
|
|
2021-11-18 08:53:23 +08:00
|
|
|
static struct remote *make_remote(struct remote_state *remote_state,
|
|
|
|
const char *name, int len)
|
2007-05-12 23:45:53 +08:00
|
|
|
{
|
2021-09-02 16:52:53 +08:00
|
|
|
struct remote *ret;
|
2014-07-29 22:43:39 +08:00
|
|
|
struct remotes_hash_key lookup;
|
2019-10-07 07:30:36 +08:00
|
|
|
struct hashmap_entry lookup_entry, *e;
|
2007-05-12 23:45:53 +08:00
|
|
|
|
2014-07-29 22:43:39 +08:00
|
|
|
if (!len)
|
|
|
|
len = strlen(name);
|
|
|
|
|
|
|
|
lookup.str = name;
|
|
|
|
lookup.len = len;
|
|
|
|
hashmap_entry_init(&lookup_entry, memhash(name, len));
|
|
|
|
|
2021-11-18 08:53:23 +08:00
|
|
|
e = hashmap_get(&remote_state->remotes_hash, &lookup_entry, &lookup);
|
2019-10-07 07:30:36 +08:00
|
|
|
if (e)
|
|
|
|
return container_of(e, struct remote, ent);
|
2007-05-12 23:45:53 +08:00
|
|
|
|
2021-03-14 00:17:22 +08:00
|
|
|
CALLOC_ARRAY(ret, 1);
|
2013-07-13 17:36:24 +08:00
|
|
|
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-10 04:32:15 +08:00
|
|
|
ret->prune_tags = -1; /* unspecified */
|
2018-05-17 06:58:00 +08:00
|
|
|
ret->name = xstrndup(name, len);
|
|
|
|
refspec_init(&ret->push, REFSPEC_PUSH);
|
2018-05-17 06:58:01 +08:00
|
|
|
refspec_init(&ret->fetch, REFSPEC_FETCH);
|
2018-05-17 06:58:00 +08:00
|
|
|
|
2021-11-18 08:53:23 +08:00
|
|
|
ALLOC_GROW(remote_state->remotes, remote_state->remotes_nr + 1,
|
|
|
|
remote_state->remotes_alloc);
|
|
|
|
remote_state->remotes[remote_state->remotes_nr++] = ret;
|
2014-07-29 22:43:39 +08:00
|
|
|
|
2019-10-07 07:30:27 +08:00
|
|
|
hashmap_entry_init(&ret->ent, lookup_entry.hash);
|
2021-11-18 08:53:23 +08:00
|
|
|
if (hashmap_put_entry(&remote_state->remotes_hash, ret, ent))
|
2021-09-02 16:52:53 +08:00
|
|
|
BUG("hashmap_put overwrote entry after hashmap_get returned NULL");
|
2008-02-19 12:41:41 +08:00
|
|
|
return ret;
|
2007-05-12 23:45:53 +08:00
|
|
|
}
|
|
|
|
|
2021-11-18 08:53:22 +08:00
|
|
|
static void remote_clear(struct remote *remote)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
free((char *)remote->name);
|
|
|
|
free((char *)remote->foreign_vcs);
|
|
|
|
|
2022-06-07 23:50:03 +08:00
|
|
|
for (i = 0; i < remote->url_nr; i++)
|
2021-11-18 08:53:22 +08:00
|
|
|
free((char *)remote->url[i]);
|
2022-06-07 23:50:04 +08:00
|
|
|
FREE_AND_NULL(remote->url);
|
2021-11-18 08:53:22 +08:00
|
|
|
|
2022-06-07 23:50:03 +08:00
|
|
|
for (i = 0; i < remote->pushurl_nr; i++)
|
2021-11-18 08:53:22 +08:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2007-09-11 11:02:56 +08:00
|
|
|
static void add_merge(struct branch *branch, const char *name)
|
|
|
|
{
|
2008-02-19 12:41:41 +08:00
|
|
|
ALLOC_GROW(branch->merge_name, branch->merge_nr + 1,
|
|
|
|
branch->merge_alloc);
|
|
|
|
branch->merge_name[branch->merge_nr++] = name;
|
2007-09-11 11:02:56 +08:00
|
|
|
}
|
|
|
|
|
2021-11-18 08:53:25 +08:00
|
|
|
struct branches_hash_key {
|
|
|
|
const char *str;
|
|
|
|
int len;
|
|
|
|
};
|
|
|
|
|
2022-08-26 01:09:48 +08:00
|
|
|
static int branches_hash_cmp(const void *cmp_data UNUSED,
|
2021-11-18 08:53:25 +08:00
|
|
|
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 07:12:33 +08:00
|
|
|
branch != find_branch(repo->remote_state, branch->name,
|
|
|
|
strlen(branch->name)))
|
2021-11-18 08:53:25 +08:00
|
|
|
die("branch %s was not found in the repository", branch->name);
|
|
|
|
}
|
|
|
|
|
2021-11-18 08:53:23 +08:00
|
|
|
static struct branch *make_branch(struct remote_state *remote_state,
|
|
|
|
const char *name, size_t len)
|
2007-09-11 11:02:56 +08:00
|
|
|
{
|
2008-02-19 12:41:41 +08:00
|
|
|
struct branch *ret;
|
2007-09-11 11:02:56 +08:00
|
|
|
|
2021-11-18 08:53:25 +08:00
|
|
|
ret = find_branch(remote_state, name, len);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
2007-09-11 11:02:56 +08:00
|
|
|
|
2021-03-14 00:17:22 +08:00
|
|
|
CALLOC_ARRAY(ret, 1);
|
2020-04-11 03:43:41 +08:00
|
|
|
ret->name = xstrndup(name, len);
|
2014-06-19 04:02:13 +08:00
|
|
|
ret->refname = xstrfmt("refs/heads/%s", ret->name);
|
2007-09-11 11:02:56 +08:00
|
|
|
|
2021-11-18 08:53:25 +08:00
|
|
|
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");
|
2008-02-19 12:41:41 +08:00
|
|
|
return ret;
|
2007-09-11 11:02:56 +08:00
|
|
|
}
|
|
|
|
|
2020-04-11 03:43:41 +08:00
|
|
|
static struct rewrite *make_rewrite(struct rewrites *r,
|
|
|
|
const char *base, size_t len)
|
2008-02-21 02:43:53 +08:00
|
|
|
{
|
|
|
|
struct rewrite *ret;
|
|
|
|
int i;
|
|
|
|
|
2009-09-07 16:56:00 +08:00
|
|
|
for (i = 0; i < r->rewrite_nr; i++) {
|
2020-04-11 03:43:41 +08:00
|
|
|
if (len == r->rewrite[i]->baselen &&
|
|
|
|
!strncmp(base, r->rewrite[i]->base, len))
|
2009-09-07 16:56:00 +08:00
|
|
|
return r->rewrite[i];
|
2008-02-21 02:43:53 +08:00
|
|
|
}
|
|
|
|
|
2009-09-07 16:56:00 +08:00
|
|
|
ALLOC_GROW(r->rewrite, r->rewrite_nr + 1, r->rewrite_alloc);
|
2021-03-14 00:17:22 +08:00
|
|
|
CALLOC_ARRAY(ret, 1);
|
2009-09-07 16:56:00 +08:00
|
|
|
r->rewrite[r->rewrite_nr++] = ret;
|
2020-04-11 03:43:41 +08:00
|
|
|
ret->base = xstrndup(base, len);
|
|
|
|
ret->baselen = len;
|
2008-02-21 02:43:53 +08:00
|
|
|
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);
|
2008-02-25 14:25:04 +08:00
|
|
|
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++;
|
2008-02-21 02:43:53 +08:00
|
|
|
}
|
|
|
|
|
2015-09-25 05:07:20 +08:00
|
|
|
static const char *skip_spaces(const char *s)
|
|
|
|
{
|
|
|
|
while (isspace(*s))
|
|
|
|
s++;
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
2021-11-18 08:53:23 +08:00
|
|
|
static void read_remotes_file(struct remote_state *remote_state,
|
|
|
|
struct remote *remote)
|
2007-05-12 23:45:53 +08:00
|
|
|
{
|
2015-09-25 05:07:20 +08:00
|
|
|
struct strbuf buf = STRBUF_INIT;
|
2017-05-03 18:16:50 +08:00
|
|
|
FILE *f = fopen_or_warn(git_path("remotes/%s", remote->name), "r");
|
2007-05-12 23:45:53 +08:00
|
|
|
|
|
|
|
if (!f)
|
|
|
|
return;
|
2017-01-20 05:20:02 +08:00
|
|
|
remote->configured_in_repo = 1;
|
2008-11-11 04:43:00 +08:00
|
|
|
remote->origin = REMOTE_REMOTES;
|
2015-10-29 04:27:33 +08:00
|
|
|
while (strbuf_getline(&buf, f) != EOF) {
|
2015-09-25 05:07:20 +08:00
|
|
|
const char *v;
|
2007-05-12 23:45:53 +08:00
|
|
|
|
2015-09-25 05:07:20 +08:00
|
|
|
strbuf_rtrim(&buf);
|
2007-05-12 23:45:53 +08:00
|
|
|
|
2015-09-25 05:07:20 +08:00
|
|
|
if (skip_prefix(buf.buf, "URL:", &v))
|
2021-11-18 08:53:23 +08:00
|
|
|
add_url_alias(remote_state, remote,
|
|
|
|
xstrdup(skip_spaces(v)));
|
2015-09-25 05:07:20 +08:00
|
|
|
else if (skip_prefix(buf.buf, "Push:", &v))
|
2018-05-17 06:58:00 +08:00
|
|
|
refspec_append(&remote->push, skip_spaces(v));
|
2015-09-25 05:07:20 +08:00
|
|
|
else if (skip_prefix(buf.buf, "Pull:", &v))
|
2018-05-17 06:58:01 +08:00
|
|
|
refspec_append(&remote->fetch, skip_spaces(v));
|
2007-05-12 23:45:53 +08:00
|
|
|
}
|
2015-09-25 05:07:20 +08:00
|
|
|
strbuf_release(&buf);
|
2007-05-12 23:45:53 +08:00
|
|
|
fclose(f);
|
|
|
|
}
|
|
|
|
|
2021-11-18 08:53:23 +08:00
|
|
|
static void read_branches_file(struct remote_state *remote_state,
|
|
|
|
struct remote *remote)
|
2007-05-12 23:45:53 +08:00
|
|
|
{
|
2007-09-11 11:02:56 +08:00
|
|
|
char *frag;
|
2015-09-25 05:07:18 +08:00
|
|
|
struct strbuf buf = STRBUF_INIT;
|
2017-05-03 18:16:50 +08:00
|
|
|
FILE *f = fopen_or_warn(git_path("branches/%s", remote->name), "r");
|
2007-05-12 23:45:53 +08:00
|
|
|
|
|
|
|
if (!f)
|
|
|
|
return;
|
2015-09-25 05:07:18 +08:00
|
|
|
|
2016-01-14 07:31:17 +08:00
|
|
|
strbuf_getline_lf(&buf, f);
|
2015-10-23 14:02:51 +08:00
|
|
|
fclose(f);
|
2015-09-25 05:07:18 +08:00
|
|
|
strbuf_trim(&buf);
|
|
|
|
if (!buf.len) {
|
|
|
|
strbuf_release(&buf);
|
2007-05-12 23:45:53 +08:00
|
|
|
return;
|
2015-09-25 05:07:18 +08:00
|
|
|
}
|
|
|
|
|
2017-01-20 05:20:02 +08:00
|
|
|
remote->configured_in_repo = 1;
|
2008-11-11 04:43:00 +08:00
|
|
|
remote->origin = REMOTE_BRANCHES;
|
2008-03-26 07:35:28 +08:00
|
|
|
|
|
|
|
/*
|
2013-06-22 15:58:12 +08:00
|
|
|
* The branches file would have URL and optionally
|
2020-06-24 22:46:35 +08:00
|
|
|
* #branch specified. The default (or specified) branch is
|
2015-09-25 05:07:18 +08:00
|
|
|
* fetched and stored in the local branch matching the
|
|
|
|
* remote name.
|
2008-03-26 07:35:28 +08:00
|
|
|
*/
|
2015-09-25 05:07:18 +08:00
|
|
|
frag = strchr(buf.buf, '#');
|
|
|
|
if (frag)
|
2007-09-11 11:02:56 +08:00
|
|
|
*(frag++) = '\0';
|
2015-09-25 05:07:18 +08:00
|
|
|
else
|
2020-12-11 19:36:56 +08:00
|
|
|
frag = (char *)git_default_branch_name(0);
|
2015-09-25 05:07:18 +08:00
|
|
|
|
2021-11-18 08:53:23 +08:00
|
|
|
add_url_alias(remote_state, remote, strbuf_detach(&buf, NULL));
|
2020-09-05 22:49:30 +08:00
|
|
|
refspec_appendf(&remote->fetch, "refs/heads/%s:refs/heads/%s",
|
|
|
|
frag, remote->name);
|
2013-06-22 15:58:12 +08:00
|
|
|
|
2008-11-11 05:47:11 +08:00
|
|
|
/*
|
|
|
|
* Cogito compatible push: push current HEAD to remote #branch
|
|
|
|
* (master if missing)
|
|
|
|
*/
|
2020-09-05 22:49:30 +08:00
|
|
|
refspec_appendf(&remote->push, "HEAD:refs/heads/%s", frag);
|
2007-09-11 11:03:08 +08:00
|
|
|
remote->fetch_tags = 1; /* always auto-follow */
|
2007-05-12 23:45:53 +08:00
|
|
|
}
|
|
|
|
|
2008-05-15 01:46:53 +08:00
|
|
|
static int handle_config(const char *key, const char *value, void *cb)
|
2007-05-12 23:45:53 +08:00
|
|
|
{
|
|
|
|
const char *name;
|
2020-04-11 03:44:28 +08:00
|
|
|
size_t namelen;
|
2007-05-12 23:45:53 +08:00
|
|
|
const char *subkey;
|
|
|
|
struct remote *remote;
|
2007-09-11 11:02:56 +08:00
|
|
|
struct branch *branch;
|
2021-11-18 08:53:23 +08:00
|
|
|
struct remote_state *remote_state = cb;
|
|
|
|
|
2016-02-16 17:47:49 +08:00
|
|
|
if (parse_config_key(key, "branch", &name, &namelen, &subkey) >= 0) {
|
2022-06-01 07:12:34 +08:00
|
|
|
/* There is no subsection. */
|
2016-02-16 17:47:49 +08:00
|
|
|
if (!name)
|
2007-09-11 11:02:56 +08:00
|
|
|
return 0;
|
2022-06-01 07:12:34 +08:00
|
|
|
/* There is a subsection, but it is empty. */
|
|
|
|
if (!namelen)
|
|
|
|
return -1;
|
2021-11-18 08:53:23 +08:00
|
|
|
branch = make_branch(remote_state, name, namelen);
|
2016-02-16 17:47:49 +08:00
|
|
|
if (!strcmp(subkey, "remote")) {
|
2015-05-02 06:44:41 +08:00
|
|
|
return git_config_string(&branch->remote_name, key, value);
|
2016-02-16 17:47:49 +08:00
|
|
|
} else if (!strcmp(subkey, "pushremote")) {
|
2015-05-21 12:45:20 +08:00
|
|
|
return git_config_string(&branch->pushremote_name, key, value);
|
2016-02-16 17:47:49 +08:00
|
|
|
} else if (!strcmp(subkey, "merge")) {
|
2008-02-12 03:00:10 +08:00
|
|
|
if (!value)
|
|
|
|
return config_error_nonbool(key);
|
2007-09-11 11:02:56 +08:00
|
|
|
add_merge(branch, xstrdup(value));
|
2008-02-12 03:00:10 +08:00
|
|
|
}
|
2007-09-11 11:02:56 +08:00
|
|
|
return 0;
|
2007-05-12 23:45:53 +08:00
|
|
|
}
|
2016-02-16 17:47:49 +08:00
|
|
|
if (parse_config_key(key, "url", &name, &namelen, &subkey) >= 0) {
|
2008-02-21 02:43:53 +08:00
|
|
|
struct rewrite *rewrite;
|
2016-02-16 17:47:49 +08:00
|
|
|
if (!name)
|
2008-02-21 02:43:53 +08:00
|
|
|
return 0;
|
2016-02-16 17:47:49 +08:00
|
|
|
if (!strcmp(subkey, "insteadof")) {
|
2009-09-07 16:56:33 +08:00
|
|
|
if (!value)
|
|
|
|
return config_error_nonbool(key);
|
2021-11-18 08:53:23 +08:00
|
|
|
rewrite = make_rewrite(&remote_state->rewrites, name,
|
|
|
|
namelen);
|
2009-09-07 16:56:33 +08:00
|
|
|
add_instead_of(rewrite, xstrdup(value));
|
2016-02-16 17:47:49 +08:00
|
|
|
} else if (!strcmp(subkey, "pushinsteadof")) {
|
2008-02-21 02:43:53 +08:00
|
|
|
if (!value)
|
|
|
|
return config_error_nonbool(key);
|
2021-11-18 08:53:23 +08:00
|
|
|
rewrite = make_rewrite(&remote_state->rewrites_push,
|
|
|
|
name, namelen);
|
2008-02-21 02:43:53 +08:00
|
|
|
add_instead_of(rewrite, xstrdup(value));
|
|
|
|
}
|
|
|
|
}
|
2013-04-02 15:40:33 +08:00
|
|
|
|
2016-02-16 17:47:49 +08:00
|
|
|
if (parse_config_key(key, "remote", &name, &namelen, &subkey) < 0)
|
2007-05-12 23:45:53 +08:00
|
|
|
return 0;
|
2013-04-02 15:40:33 +08:00
|
|
|
|
|
|
|
/* Handle remote.* variables */
|
2016-02-16 17:47:49 +08:00
|
|
|
if (!name && !strcmp(subkey, "pushdefault"))
|
2021-11-18 08:53:23 +08:00
|
|
|
return git_config_string(&remote_state->pushremote_name, key,
|
|
|
|
value);
|
2013-04-02 15:40:33 +08:00
|
|
|
|
2016-02-16 17:47:49 +08:00
|
|
|
if (!name)
|
|
|
|
return 0;
|
2013-04-02 15:40:33 +08:00
|
|
|
/* Handle remote.<name>.* variables */
|
2008-10-15 04:30:21 +08:00
|
|
|
if (*name == '/') {
|
2018-11-10 13:16:09 +08:00
|
|
|
warning(_("config remote shorthand cannot begin with '/': %s"),
|
2008-10-15 04:30:21 +08:00
|
|
|
name);
|
|
|
|
return 0;
|
|
|
|
}
|
2021-11-18 08:53:23 +08:00
|
|
|
remote = make_remote(remote_state, name, namelen);
|
2008-11-11 04:43:00 +08:00
|
|
|
remote->origin = REMOTE_CONFIG;
|
2020-02-10 08:30:54 +08:00
|
|
|
if (current_config_scope() == CONFIG_SCOPE_LOCAL ||
|
2020-12-03 16:00:18 +08:00
|
|
|
current_config_scope() == CONFIG_SCOPE_WORKTREE)
|
2017-01-20 05:20:02 +08:00
|
|
|
remote->configured_in_repo = 1;
|
2016-02-16 17:47:49 +08:00
|
|
|
if (!strcmp(subkey, "mirror"))
|
2008-04-17 19:17:20 +08:00
|
|
|
remote->mirror = git_config_bool(key, value);
|
2016-02-16 17:47:49 +08:00
|
|
|
else if (!strcmp(subkey, "skipdefaultupdate"))
|
2008-04-17 19:17:20 +08:00
|
|
|
remote->skip_default_update = git_config_bool(key, value);
|
2016-02-16 17:47:49 +08:00
|
|
|
else if (!strcmp(subkey, "skipfetchall"))
|
2009-11-10 04:11:06 +08:00
|
|
|
remote->skip_default_update = git_config_bool(key, value);
|
2016-02-16 17:47:49 +08:00
|
|
|
else if (!strcmp(subkey, "prune"))
|
2013-07-13 17:36:24 +08:00
|
|
|
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-10 04:32:15 +08:00
|
|
|
else if (!strcmp(subkey, "prunetags"))
|
|
|
|
remote->prune_tags = git_config_bool(key, value);
|
2016-02-16 17:47:49 +08:00
|
|
|
else if (!strcmp(subkey, "url")) {
|
2008-04-17 19:17:20 +08:00
|
|
|
const char *v;
|
|
|
|
if (git_config_string(&v, key, value))
|
|
|
|
return -1;
|
|
|
|
add_url(remote, v);
|
2016-02-16 17:47:49 +08:00
|
|
|
} else if (!strcmp(subkey, "pushurl")) {
|
2009-06-10 00:01:34 +08:00
|
|
|
const char *v;
|
|
|
|
if (git_config_string(&v, key, value))
|
|
|
|
return -1;
|
|
|
|
add_pushurl(remote, v);
|
2016-02-16 17:47:49 +08:00
|
|
|
} else if (!strcmp(subkey, "push")) {
|
2008-04-17 19:17:20 +08:00
|
|
|
const char *v;
|
|
|
|
if (git_config_string(&v, key, value))
|
|
|
|
return -1;
|
2018-05-17 06:58:00 +08:00
|
|
|
refspec_append(&remote->push, v);
|
|
|
|
free((char *)v);
|
2016-02-16 17:47:49 +08:00
|
|
|
} else if (!strcmp(subkey, "fetch")) {
|
2008-04-17 19:17:20 +08:00
|
|
|
const char *v;
|
|
|
|
if (git_config_string(&v, key, value))
|
|
|
|
return -1;
|
2018-05-17 06:58:01 +08:00
|
|
|
refspec_append(&remote->fetch, v);
|
|
|
|
free((char *)v);
|
2016-02-16 17:47:49 +08:00
|
|
|
} else if (!strcmp(subkey, "receivepack")) {
|
2008-04-17 19:17:20 +08:00
|
|
|
const char *v;
|
|
|
|
if (git_config_string(&v, key, value))
|
|
|
|
return -1;
|
2007-05-12 23:45:53 +08:00
|
|
|
if (!remote->receivepack)
|
2008-04-17 19:17:20 +08:00
|
|
|
remote->receivepack = v;
|
2007-05-12 23:45:53 +08:00
|
|
|
else
|
2018-11-10 13:16:09 +08:00
|
|
|
error(_("more than one receivepack given, using the first"));
|
2016-02-16 17:47:49 +08:00
|
|
|
} else if (!strcmp(subkey, "uploadpack")) {
|
2008-04-17 19:17:20 +08:00
|
|
|
const char *v;
|
|
|
|
if (git_config_string(&v, key, value))
|
|
|
|
return -1;
|
2007-09-11 11:02:51 +08:00
|
|
|
if (!remote->uploadpack)
|
2008-04-17 19:17:20 +08:00
|
|
|
remote->uploadpack = v;
|
2007-09-11 11:02:51 +08:00
|
|
|
else
|
2018-11-10 13:16:09 +08:00
|
|
|
error(_("more than one uploadpack given, using the first"));
|
2016-02-16 17:47:49 +08:00
|
|
|
} else if (!strcmp(subkey, "tagopt")) {
|
2007-09-11 11:03:08 +08:00
|
|
|
if (!strcmp(value, "--no-tags"))
|
|
|
|
remote->fetch_tags = -1;
|
2010-04-20 07:31:25 +08:00
|
|
|
else if (!strcmp(value, "--tags"))
|
|
|
|
remote->fetch_tags = 2;
|
2016-02-16 17:47:49 +08:00
|
|
|
} else if (!strcmp(subkey, "proxy")) {
|
2008-04-17 19:17:20 +08:00
|
|
|
return git_config_string((const char **)&remote->http_proxy,
|
|
|
|
key, value);
|
2016-02-16 17:47:49 +08:00
|
|
|
} else if (!strcmp(subkey, "proxyauthmethod")) {
|
2016-01-26 21:02:47 +08:00
|
|
|
return git_config_string((const char **)&remote->http_proxy_authmethod,
|
|
|
|
key, value);
|
2016-02-16 17:47:49 +08:00
|
|
|
} else if (!strcmp(subkey, "vcs")) {
|
2009-11-18 09:42:25 +08:00
|
|
|
return git_config_string(&remote->foreign_vcs, key, value);
|
2008-04-17 19:17:20 +08:00
|
|
|
}
|
2007-05-12 23:45:53 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-11-18 08:53:23 +08:00
|
|
|
static void alias_all_urls(struct remote_state *remote_state)
|
2008-02-21 02:43:53 +08:00
|
|
|
{
|
|
|
|
int i, j;
|
2021-11-18 08:53:23 +08:00
|
|
|
for (i = 0; i < remote_state->remotes_nr; i++) {
|
2009-09-07 16:56:33 +08:00
|
|
|
int add_pushurl_aliases;
|
2021-11-18 08:53:23 +08:00
|
|
|
if (!remote_state->remotes[i])
|
2008-02-21 02:43:53 +08:00
|
|
|
continue;
|
2021-11-18 08:53:23 +08:00
|
|
|
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);
|
2009-06-10 00:01:34 +08:00
|
|
|
}
|
2021-11-18 08:53:23 +08:00
|
|
|
add_pushurl_aliases = remote_state->remotes[i]->pushurl_nr == 0;
|
|
|
|
for (j = 0; j < remote_state->remotes[i]->url_nr; j++) {
|
2009-09-07 16:56:33 +08:00
|
|
|
if (add_pushurl_aliases)
|
2021-11-18 08:53:22 +08:00
|
|
|
add_pushurl_alias(
|
2021-11-18 08:53:23 +08:00
|
|
|
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);
|
2009-09-07 16:56:33 +08:00
|
|
|
}
|
2008-02-21 02:43:53 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-18 08:53:23 +08:00
|
|
|
static void read_config(struct repository *repo)
|
2007-05-12 23:45:53 +08:00
|
|
|
{
|
|
|
|
int flag;
|
2015-05-02 06:44:41 +08:00
|
|
|
|
2021-11-18 08:53:23 +08:00
|
|
|
if (repo->remote_state->initialized)
|
2007-05-12 23:45:53 +08:00
|
|
|
return;
|
2021-11-18 08:53:23 +08:00
|
|
|
repo->remote_state->initialized = 1;
|
2015-05-02 06:44:41 +08:00
|
|
|
|
2021-11-18 08:53:23 +08:00
|
|
|
repo->remote_state->current_branch = NULL;
|
2016-03-06 06:11:57 +08:00
|
|
|
if (startup_info->have_repository) {
|
2021-11-18 08:53:23 +08:00
|
|
|
const char *head_ref = refs_resolve_ref_unsafe(
|
2022-01-26 22:37:01 +08:00
|
|
|
get_main_ref_store(repo), "HEAD", 0, NULL, &flag);
|
2016-03-06 06:11:57 +08:00
|
|
|
if (head_ref && (flag & REF_ISSYMREF) &&
|
|
|
|
skip_prefix(head_ref, "refs/heads/", &head_ref)) {
|
2021-11-18 08:53:23 +08:00
|
|
|
repo->remote_state->current_branch = make_branch(
|
|
|
|
repo->remote_state, head_ref, strlen(head_ref));
|
2016-03-06 06:11:57 +08:00
|
|
|
}
|
2007-05-12 23:45:53 +08:00
|
|
|
}
|
2021-11-18 08:53:23 +08:00
|
|
|
repo_config(repo, handle_config, repo->remote_state);
|
|
|
|
alias_all_urls(repo->remote_state);
|
2007-05-12 23:45:53 +08:00
|
|
|
}
|
|
|
|
|
2008-02-16 03:14:18 +08:00
|
|
|
static int valid_remote_nick(const char *name)
|
|
|
|
{
|
2009-01-10 20:07:50 +08:00
|
|
|
if (!name[0] || is_dot_or_dotdot(name))
|
2008-02-16 03:14:18 +08:00
|
|
|
return 0;
|
2017-05-25 20:00:13 +08:00
|
|
|
|
|
|
|
/* remote nicknames cannot contain slashes */
|
|
|
|
while (*name)
|
|
|
|
if (is_dir_sep(*name++))
|
|
|
|
return 0;
|
|
|
|
return 1;
|
2008-02-16 03:14:18 +08:00
|
|
|
}
|
|
|
|
|
2021-11-18 08:53:24 +08:00
|
|
|
static const char *remotes_remote_for_branch(struct remote_state *remote_state,
|
|
|
|
struct branch *branch,
|
|
|
|
int *explicit)
|
2015-05-21 12:45:16 +08:00
|
|
|
{
|
|
|
|
if (branch && branch->remote_name) {
|
|
|
|
if (explicit)
|
|
|
|
*explicit = 1;
|
|
|
|
return branch->remote_name;
|
|
|
|
}
|
|
|
|
if (explicit)
|
|
|
|
*explicit = 0;
|
push: default to single remote even when not named origin
With "push.default=current" configured, a simple "git push" will push to
the same-name branch on the current branch's branch.<name>.pushRemote, or
remote.pushDefault, or origin. If none of these are defined, the push will
fail with error "fatal: No configured push destination".
The same "default to origin if no config" behavior applies with
"push.default=matching".
Other commands use "origin" as a default when there are multiple options,
but default to the single remote when there is only one - for example,
"git checkout <something>". This "assume the single remote if there is
only one" behavior is more friendly/useful than a defaulting behavior
that only uses the name "origin" no matter what.
Update "git push" to also default to the single remote (and finally fall
back to "origin" as default if there are several), for
"push.default=current" and for other current and future remote-defaulting
push behaviors.
This change also modifies the behavior of ls-remote in a consistent way,
so defaulting not only supplies 'origin', but any single configured remote
also.
Document the change in behavior, correct incorrect assumptions in related
tests, and add test cases reflecting this new single-remote-defaulting
behavior.
Signed-off-by: Tao Klerks <tao@klerks.biz>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-04-29 17:56:45 +08:00
|
|
|
if (remote_state->remotes_nr == 1)
|
|
|
|
return remote_state->remotes[0]->name;
|
2015-05-21 12:45:16 +08:00
|
|
|
return "origin";
|
|
|
|
}
|
|
|
|
|
2021-11-18 08:53:24 +08:00
|
|
|
const char *remote_for_branch(struct branch *branch, int *explicit)
|
|
|
|
{
|
|
|
|
read_config(the_repository);
|
2021-11-18 08:53:25 +08:00
|
|
|
die_on_missing_branch(the_repository, branch);
|
|
|
|
|
2021-11-18 08:53:24 +08:00
|
|
|
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)
|
2015-05-21 12:45:20 +08:00
|
|
|
{
|
|
|
|
if (branch && branch->pushremote_name) {
|
|
|
|
if (explicit)
|
|
|
|
*explicit = 1;
|
|
|
|
return branch->pushremote_name;
|
|
|
|
}
|
2021-11-18 08:53:24 +08:00
|
|
|
if (remote_state->pushremote_name) {
|
2015-05-21 12:45:20 +08:00
|
|
|
if (explicit)
|
|
|
|
*explicit = 1;
|
2021-11-18 08:53:24 +08:00
|
|
|
return remote_state->pushremote_name;
|
2015-05-21 12:45:20 +08:00
|
|
|
}
|
2021-11-18 08:53:24 +08:00
|
|
|
return remotes_remote_for_branch(remote_state, branch, explicit);
|
2015-05-21 12:45:20 +08:00
|
|
|
}
|
|
|
|
|
2021-11-18 08:53:24 +08:00
|
|
|
const char *pushremote_for_branch(struct branch *branch, int *explicit)
|
|
|
|
{
|
|
|
|
read_config(the_repository);
|
2021-11-18 08:53:25 +08:00
|
|
|
die_on_missing_branch(the_repository, branch);
|
|
|
|
|
2021-11-18 08:53:24 +08:00
|
|
|
return remotes_pushremote_for_branch(the_repository->remote_state,
|
|
|
|
branch, explicit);
|
2015-05-21 12:45:20 +08:00
|
|
|
}
|
|
|
|
|
2021-11-18 08:53:24 +08:00
|
|
|
static struct remote *remotes_remote_get(struct remote_state *remote_state,
|
|
|
|
const char *name);
|
|
|
|
|
2020-03-04 00:12:22 +08:00
|
|
|
const char *remote_ref_for_branch(struct branch *branch, int for_push)
|
2017-11-08 00:31:08 +08:00
|
|
|
{
|
2021-11-18 08:53:24 +08:00
|
|
|
read_config(the_repository);
|
2021-11-18 08:53:25 +08:00
|
|
|
die_on_missing_branch(the_repository, branch);
|
|
|
|
|
2017-11-08 00:31:08 +08:00
|
|
|
if (branch) {
|
|
|
|
if (!for_push) {
|
|
|
|
if (branch->merge_nr) {
|
|
|
|
return branch->merge_name[0];
|
|
|
|
}
|
|
|
|
} else {
|
2021-11-18 08:53:24 +08:00
|
|
|
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);
|
2017-11-08 00:31:08 +08:00
|
|
|
|
2018-05-17 06:58:00 +08:00
|
|
|
if (remote && remote->push.nr &&
|
2018-05-17 06:58:11 +08:00
|
|
|
(dst = apply_refspecs(&remote->push,
|
2017-11-08 00:31:08 +08:00
|
|
|
branch->refname))) {
|
|
|
|
return dst;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-03-04 00:12:22 +08:00
|
|
|
return NULL;
|
2017-11-08 00:31:08 +08:00
|
|
|
}
|
|
|
|
|
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 22:36:16 +08:00
|
|
|
static void validate_remote_url(struct remote *remote)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
const char *value;
|
|
|
|
struct strbuf redacted = STRBUF_INIT;
|
|
|
|
int warn_not_die;
|
|
|
|
|
transfer doc: move fetch.credentialsInUrl to "transfer" config namespace
Rename the "fetch.credentialsInUrl" configuration variable introduced
in 6dcbdc0d661 (remote: create fetch.credentialsInUrl config,
2022-06-06) to "transfer".
There are existing exceptions, but generally speaking the
"<namespace>.<var>" configuration should only apply to command
described in the "namespace" (and its sub-commands, so e.g. "clone.*"
or "fetch.*" might also configure "git-remote-https").
But in the case of "fetch.credentialsInUrl" we've got a configuration
variable that configures the behavior of all of "clone", "push" and
"fetch", someone adjusting "fetch.*" configuration won't expect to
have the behavior of "git push" altered, especially as we have the
pre-existing "{transfer,fetch,receive}.fsckObjects", which configures
different parts of the transfer dialog.
So let's move this configuration variable to the "transfer" namespace
before it's exposed in a release. We could add all of
"{transfer,fetch,pull}.credentialsInUrl" at some other time, but once
we have "fetch" configure "pull" such an arrangement would would be a
confusing mess, as we'd at least need to have "fetch" configure
"push" (but not the other way around), or change existing behavior.
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Acked-by: Derrick Stolee <derrickstolee@github.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-06-15 18:44:12 +08:00
|
|
|
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 22:36:16 +08: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
|
2022-06-17 18:03:09 +08:00
|
|
|
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 22:36:16 +08: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);
|
|
|
|
}
|
|
|
|
|
2021-11-18 08:53:24 +08:00
|
|
|
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 *))
|
2007-05-12 23:45:53 +08:00
|
|
|
{
|
|
|
|
struct remote *ret;
|
Give error when no remote is configured
When there's no explicitly-named remote, we use the remote specified
for the current branch, which in turn defaults to "origin". But it
this case should require the remote to actually be configured, and not
fall back to the path "origin".
Possibly, the config file's "remote = something" should require the
something to be a configured remote instead of a bare repository URL,
but we actually test with a bare repository URL.
In fetch, we were giving the sensible error message when coming up
with a URL failed, but this wasn't actually reachable, so move that
error up and use it when appropriate.
In push, we need a new error message, because the old one (formerly
unreachable without a lot of help) used the repo name, which was NULL.
Signed-off-by: Daniel Barkalow <barkalow@iabervon.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2009-03-11 13:47:20 +08:00
|
|
|
int name_given = 0;
|
2007-05-12 23:45:53 +08:00
|
|
|
|
Give error when no remote is configured
When there's no explicitly-named remote, we use the remote specified
for the current branch, which in turn defaults to "origin". But it
this case should require the remote to actually be configured, and not
fall back to the path "origin".
Possibly, the config file's "remote = something" should require the
something to be a configured remote instead of a bare repository URL,
but we actually test with a bare repository URL.
In fetch, we were giving the sensible error message when coming up
with a URL failed, but this wasn't actually reachable, so move that
error up and use it when appropriate.
In push, we need a new error message, because the old one (formerly
unreachable without a lot of help) used the repo name, which was NULL.
Signed-off-by: Daniel Barkalow <barkalow@iabervon.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2009-03-11 13:47:20 +08:00
|
|
|
if (name)
|
|
|
|
name_given = 1;
|
2015-05-21 12:45:20 +08:00
|
|
|
else
|
2021-11-18 08:53:24 +08:00
|
|
|
name = get_default(remote_state, remote_state->current_branch,
|
2021-11-18 08:53:22 +08:00
|
|
|
&name_given);
|
2009-03-16 15:35:09 +08:00
|
|
|
|
2021-11-18 08:53:24 +08:00
|
|
|
ret = make_remote(remote_state, name, 0);
|
2017-02-15 04:33:28 +08:00
|
|
|
if (valid_remote_nick(name) && have_git_dir()) {
|
2009-11-18 09:42:23 +08:00
|
|
|
if (!valid_remote(ret))
|
2021-11-18 08:53:24 +08:00
|
|
|
read_remotes_file(remote_state, ret);
|
2009-11-18 09:42:23 +08:00
|
|
|
if (!valid_remote(ret))
|
2021-11-18 08:53:24 +08:00
|
|
|
read_branches_file(remote_state, ret);
|
2007-05-12 23:45:53 +08:00
|
|
|
}
|
2009-11-18 09:42:23 +08:00
|
|
|
if (name_given && !valid_remote(ret))
|
2021-11-18 08:53:24 +08:00
|
|
|
add_url_alias(remote_state, ret, name);
|
2009-11-18 09:42:23 +08:00
|
|
|
if (!valid_remote(ret))
|
2007-05-12 23:45:53 +08:00
|
|
|
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 22:36:16 +08:00
|
|
|
|
|
|
|
validate_remote_url(ret);
|
|
|
|
|
2007-05-12 23:45:53 +08:00
|
|
|
return ret;
|
|
|
|
}
|
2007-05-12 23:45:59 +08:00
|
|
|
|
2021-11-18 08:53:24 +08:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
remote.c: introduce a way to have different remotes for fetch/push
Currently, do_push() in push.c calls remote_get(), which gets the
configured remote for fetching and pushing. Replace this call with a
call to pushremote_get() instead, a new function that will return the
remote configured specifically for pushing. This function tries to
work with the string pushremote_name, before falling back to the
codepath of remote_get(). This patch has no visible impact, but
serves to enable future patches to introduce configuration variables
to set pushremote_name. For example, you can now do the following in
handle_config():
if (!strcmp(key, "remote.pushdefault"))
git_config_string(&pushremote_name, key, value);
Then, pushes will automatically go to the remote specified by
remote.pushdefault.
Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com>
Reviewed-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-04-02 15:40:32 +08:00
|
|
|
struct remote *remote_get(const char *name)
|
|
|
|
{
|
2021-11-18 08:53:24 +08:00
|
|
|
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);
|
remote.c: introduce a way to have different remotes for fetch/push
Currently, do_push() in push.c calls remote_get(), which gets the
configured remote for fetching and pushing. Replace this call with a
call to pushremote_get() instead, a new function that will return the
remote configured specifically for pushing. This function tries to
work with the string pushremote_name, before falling back to the
codepath of remote_get(). This patch has no visible impact, but
serves to enable future patches to introduce configuration variables
to set pushremote_name. For example, you can now do the following in
handle_config():
if (!strcmp(key, "remote.pushdefault"))
git_config_string(&pushremote_name, key, value);
Then, pushes will automatically go to the remote specified by
remote.pushdefault.
Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com>
Reviewed-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-04-02 15:40:32 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
struct remote *pushremote_get(const char *name)
|
|
|
|
{
|
2021-11-18 08:53:24 +08:00
|
|
|
read_config(the_repository);
|
|
|
|
return remotes_pushremote_get(the_repository->remote_state, name);
|
remote.c: introduce a way to have different remotes for fetch/push
Currently, do_push() in push.c calls remote_get(), which gets the
configured remote for fetching and pushing. Replace this call with a
call to pushremote_get() instead, a new function that will return the
remote configured specifically for pushing. This function tries to
work with the string pushremote_name, before falling back to the
codepath of remote_get(). This patch has no visible impact, but
serves to enable future patches to introduce configuration variables
to set pushremote_name. For example, you can now do the following in
handle_config():
if (!strcmp(key, "remote.pushdefault"))
git_config_string(&pushremote_name, key, value);
Then, pushes will automatically go to the remote specified by
remote.pushdefault.
Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com>
Reviewed-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-04-02 15:40:32 +08:00
|
|
|
}
|
|
|
|
|
2017-01-20 05:20:02 +08:00
|
|
|
int remote_is_configured(struct remote *remote, int in_repo)
|
2009-04-06 21:41:01 +08:00
|
|
|
{
|
2017-01-20 05:20:02 +08:00
|
|
|
if (!remote)
|
|
|
|
return 0;
|
|
|
|
if (in_repo)
|
|
|
|
return remote->configured_in_repo;
|
|
|
|
return !!remote->origin;
|
2009-04-06 21:41:01 +08:00
|
|
|
}
|
|
|
|
|
2007-07-11 01:48:40 +08:00
|
|
|
int for_each_remote(each_remote_fn fn, void *priv)
|
|
|
|
{
|
|
|
|
int i, result = 0;
|
2021-11-18 08:53:23 +08:00
|
|
|
read_config(the_repository);
|
2021-11-18 08:53:22 +08:00
|
|
|
for (i = 0; i < the_repository->remote_state->remotes_nr && !result;
|
|
|
|
i++) {
|
|
|
|
struct remote *remote =
|
|
|
|
the_repository->remote_state->remotes[i];
|
|
|
|
if (!remote)
|
2007-07-11 01:48:40 +08:00
|
|
|
continue;
|
2021-11-18 08:53:22 +08:00
|
|
|
result = fn(remote, priv);
|
2007-07-11 01:48:40 +08:00
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2013-10-30 13:33:10 +08:00
|
|
|
static void handle_duplicate(struct ref *ref1, struct ref *ref2)
|
|
|
|
{
|
2013-10-30 13:33:12 +08:00
|
|
|
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.
|
|
|
|
*/
|
2018-11-10 13:16:08 +08:00
|
|
|
BUG("Internal error");
|
2013-10-30 13:33:12 +08:00
|
|
|
}
|
|
|
|
}
|
2013-10-30 13:33:10 +08:00
|
|
|
free(ref2->peer_ref);
|
|
|
|
free(ref2);
|
|
|
|
}
|
|
|
|
|
2013-10-30 13:33:09 +08:00
|
|
|
struct ref *ref_remove_duplicates(struct ref *ref_map)
|
2007-10-08 12:25:07 +08:00
|
|
|
{
|
2010-07-05 03:46:19 +08:00
|
|
|
struct string_list refs = STRING_LIST_INIT_NODUP;
|
2013-10-30 13:33:09 +08:00
|
|
|
struct ref *retval = NULL;
|
|
|
|
struct ref **p = &retval;
|
2009-10-26 05:28:11 +08:00
|
|
|
|
2013-10-30 13:33:09 +08:00
|
|
|
while (ref_map) {
|
|
|
|
struct ref *ref = ref_map;
|
|
|
|
|
|
|
|
ref_map = ref_map->next;
|
|
|
|
ref->next = NULL;
|
2009-10-26 05:28:11 +08:00
|
|
|
|
2013-10-30 13:33:09 +08:00
|
|
|
if (!ref->peer_ref) {
|
|
|
|
*p = ref;
|
|
|
|
p = &ref->next;
|
2013-10-30 13:33:07 +08:00
|
|
|
} else {
|
2013-10-30 13:33:09 +08:00
|
|
|
struct string_list_item *item =
|
|
|
|
string_list_insert(&refs, ref->peer_ref->name);
|
|
|
|
|
|
|
|
if (item->util) {
|
|
|
|
/* Entry already existed */
|
2013-10-30 13:33:10 +08:00
|
|
|
handle_duplicate((struct ref *)item->util, ref);
|
2013-10-30 13:33:09 +08:00
|
|
|
} else {
|
|
|
|
*p = ref;
|
|
|
|
p = &ref->next;
|
|
|
|
item->util = ref;
|
|
|
|
}
|
2007-10-08 12:25:07 +08:00
|
|
|
}
|
|
|
|
}
|
2013-10-30 13:33:09 +08:00
|
|
|
|
2009-10-26 05:28:11 +08:00
|
|
|
string_list_clear(&refs, 0);
|
2013-10-30 13:33:09 +08:00
|
|
|
return retval;
|
2007-10-08 12:25:07 +08:00
|
|
|
}
|
|
|
|
|
2007-09-19 12:49:27 +08:00
|
|
|
int remote_has_url(struct remote *remote, const char *url)
|
2007-05-12 23:46:03 +08:00
|
|
|
{
|
|
|
|
int i;
|
2007-09-19 12:49:27 +08:00
|
|
|
for (i = 0; i < remote->url_nr; i++) {
|
|
|
|
if (!strcmp(remote->url[i], url))
|
2007-05-12 23:46:03 +08:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-03-07 14:11:34 +08:00
|
|
|
static int match_name_with_pattern(const char *key, const char *name,
|
|
|
|
const char *value, char **result)
|
2009-03-07 14:11:29 +08:00
|
|
|
{
|
2009-03-07 14:11:36 +08:00
|
|
|
const char *kstar = strchr(key, '*');
|
|
|
|
size_t klen;
|
2009-03-07 14:11:39 +08:00
|
|
|
size_t ksuffixlen;
|
|
|
|
size_t namelen;
|
2009-03-07 14:11:36 +08:00
|
|
|
int ret;
|
|
|
|
if (!kstar)
|
2018-11-10 13:16:09 +08:00
|
|
|
die(_("key '%s' of pattern had no '*'"), key);
|
2009-03-07 14:11:36 +08:00
|
|
|
klen = kstar - key;
|
2009-03-07 14:11:39 +08:00
|
|
|
ksuffixlen = strlen(kstar + 1);
|
|
|
|
namelen = strlen(name);
|
|
|
|
ret = !strncmp(name, key, klen) && namelen >= klen + ksuffixlen &&
|
|
|
|
!memcmp(name + namelen - ksuffixlen, kstar + 1, ksuffixlen);
|
2009-03-07 14:11:34 +08:00
|
|
|
if (ret && value) {
|
2014-09-21 16:23:37 +08:00
|
|
|
struct strbuf sb = STRBUF_INIT;
|
2009-03-07 14:11:36 +08:00
|
|
|
const char *vstar = strchr(value, '*');
|
|
|
|
if (!vstar)
|
2018-11-10 13:16:09 +08:00
|
|
|
die(_("value '%s' of pattern has no '*'"), value);
|
2014-09-21 16:23:37 +08:00
|
|
|
strbuf_add(&sb, value, vstar - value);
|
|
|
|
strbuf_add(&sb, name + klen, namelen - klen - ksuffixlen);
|
|
|
|
strbuf_addstr(&sb, vstar + 1);
|
|
|
|
*result = strbuf_detach(&sb, NULL);
|
2009-03-07 14:11:34 +08:00
|
|
|
}
|
2009-03-07 14:11:29 +08:00
|
|
|
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-10-01 05:25:29 +08: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);
|
|
|
|
}
|
|
|
|
|
2022-06-17 08:20:31 +08: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-10-01 05:25:29 +08: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.
|
2020-12-22 11:58:17 +08:00
|
|
|
*
|
|
|
|
* 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-10-01 05:25:29 +08: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);
|
2020-12-22 11:58:16 +08:00
|
|
|
} 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-10-01 05:25:29 +08: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;
|
|
|
|
}
|
|
|
|
|
2018-05-17 06:58:10 +08:00
|
|
|
static void query_refspecs_multiple(struct refspec *rs,
|
|
|
|
struct refspec_item *query,
|
|
|
|
struct string_list *results)
|
2014-02-27 17:00:10 +08:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
int find_src = !query->src;
|
|
|
|
|
|
|
|
if (find_src && !query->dst)
|
2018-11-10 13:16:08 +08:00
|
|
|
BUG("query_refspecs_multiple: need either src or dst");
|
2014-02-27 17:00:10 +08:00
|
|
|
|
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-10-01 05:25:29 +08:00
|
|
|
if (query_matches_negative_refspec(rs, query))
|
|
|
|
return;
|
|
|
|
|
2018-05-17 06:58:10 +08:00
|
|
|
for (i = 0; i < rs->nr; i++) {
|
|
|
|
struct refspec_item *refspec = &rs->items[i];
|
2014-02-27 17:00:10 +08:00
|
|
|
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-10-01 05:25:29 +08:00
|
|
|
if (!refspec->dst || refspec->negative)
|
2014-02-27 17:00:10 +08:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-17 06:58:12 +08:00
|
|
|
int query_refspecs(struct refspec *rs, struct refspec_item *query)
|
2009-11-18 09:42:28 +08:00
|
|
|
{
|
|
|
|
int i;
|
2011-10-15 13:04:24 +08:00
|
|
|
int find_src = !query->src;
|
2013-10-30 13:33:01 +08:00
|
|
|
const char *needle = find_src ? query->dst : query->src;
|
|
|
|
char **result = find_src ? &query->src : &query->dst;
|
2009-11-18 09:42:28 +08:00
|
|
|
|
2011-10-15 13:04:24 +08:00
|
|
|
if (find_src && !query->dst)
|
2018-11-10 13:16:08 +08:00
|
|
|
BUG("query_refspecs: need either src or dst");
|
2007-07-11 01:48:40 +08:00
|
|
|
|
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-10-01 05:25:29 +08:00
|
|
|
if (query_matches_negative_refspec(rs, query))
|
|
|
|
return -1;
|
|
|
|
|
2018-05-17 06:58:12 +08:00
|
|
|
for (i = 0; i < rs->nr; i++) {
|
|
|
|
struct refspec_item *refspec = &rs->items[i];
|
2011-10-15 13:04:24 +08:00
|
|
|
const char *key = find_src ? refspec->dst : refspec->src;
|
|
|
|
const char *value = find_src ? refspec->src : refspec->dst;
|
2007-07-11 01:48:40 +08:00
|
|
|
|
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-10-01 05:25:29 +08:00
|
|
|
if (!refspec->dst || refspec->negative)
|
2007-05-12 23:46:03 +08:00
|
|
|
continue;
|
2011-10-15 13:04:24 +08:00
|
|
|
if (refspec->pattern) {
|
2009-03-07 14:11:34 +08:00
|
|
|
if (match_name_with_pattern(key, needle, value, result)) {
|
2011-10-15 13:04:24 +08:00
|
|
|
query->force = refspec->force;
|
2007-05-12 23:46:03 +08:00
|
|
|
return 0;
|
|
|
|
}
|
2007-07-11 01:48:40 +08:00
|
|
|
} else if (!strcmp(needle, key)) {
|
|
|
|
*result = xstrdup(value);
|
2011-10-15 13:04:24 +08:00
|
|
|
query->force = refspec->force;
|
2007-07-11 01:48:40 +08:00
|
|
|
return 0;
|
2007-05-12 23:46:03 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2018-05-17 06:58:11 +08:00
|
|
|
char *apply_refspecs(struct refspec *rs, const char *name)
|
2011-10-15 13:04:24 +08:00
|
|
|
{
|
2018-05-17 06:57:49 +08:00
|
|
|
struct refspec_item query;
|
2011-10-15 13:04:24 +08:00
|
|
|
|
2018-05-17 06:57:49 +08:00
|
|
|
memset(&query, 0, sizeof(struct refspec_item));
|
2011-10-15 13:04:24 +08:00
|
|
|
query.src = (char *)name;
|
|
|
|
|
2018-05-17 06:58:12 +08:00
|
|
|
if (query_refspecs(rs, &query))
|
2011-10-15 13:04:24 +08:00
|
|
|
return NULL;
|
|
|
|
|
|
|
|
return query.dst;
|
|
|
|
}
|
|
|
|
|
2018-05-17 06:57:49 +08:00
|
|
|
int remote_find_tracking(struct remote *remote, struct refspec_item *refspec)
|
2011-10-15 13:04:24 +08:00
|
|
|
{
|
2018-05-17 06:58:12 +08:00
|
|
|
return query_refspecs(&remote->fetch, refspec);
|
2011-10-15 13:04:24 +08:00
|
|
|
}
|
|
|
|
|
2008-10-18 16:37:40 +08:00
|
|
|
static struct ref *alloc_ref_with_prefix(const char *prefix, size_t prefixlen,
|
|
|
|
const char *name)
|
|
|
|
{
|
|
|
|
size_t len = strlen(name);
|
2016-02-23 06:44:35 +08:00
|
|
|
struct ref *ref = xcalloc(1, st_add4(sizeof(*ref), prefixlen, len, 1));
|
2008-10-18 16:37:40 +08:00
|
|
|
memcpy(ref->name, prefix, prefixlen);
|
|
|
|
memcpy(ref->name + prefixlen, name, len);
|
|
|
|
return ref;
|
|
|
|
}
|
|
|
|
|
2008-10-18 16:44:18 +08:00
|
|
|
struct ref *alloc_ref(const char *name)
|
2007-07-10 12:47:23 +08:00
|
|
|
{
|
2008-10-18 16:44:18 +08:00
|
|
|
return alloc_ref_with_prefix("", 0, name);
|
2008-05-11 07:26:58 +08:00
|
|
|
}
|
|
|
|
|
2011-06-08 07:03:03 +08:00
|
|
|
struct ref *copy_ref(const struct ref *ref)
|
2007-09-11 11:03:08 +08:00
|
|
|
{
|
2009-02-28 03:10:04 +08:00
|
|
|
struct ref *cpy;
|
|
|
|
size_t len;
|
|
|
|
if (!ref)
|
|
|
|
return NULL;
|
2016-02-23 06:44:35 +08:00
|
|
|
len = st_add3(sizeof(struct ref), strlen(ref->name), 1);
|
|
|
|
cpy = xmalloc(len);
|
|
|
|
memcpy(cpy, ref, len);
|
2009-02-28 03:10:04 +08:00
|
|
|
cpy->next = NULL;
|
2015-01-13 09:59:09 +08:00
|
|
|
cpy->symref = xstrdup_or_null(ref->symref);
|
|
|
|
cpy->remote_status = xstrdup_or_null(ref->remote_status);
|
2009-02-28 03:10:04 +08:00
|
|
|
cpy->peer_ref = copy_ref(ref->peer_ref);
|
|
|
|
return cpy;
|
2007-09-11 11:03:08 +08:00
|
|
|
}
|
|
|
|
|
2007-10-30 09:05:40 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2019-04-13 13:54:31 +08:00
|
|
|
void free_one_ref(struct ref *ref)
|
2008-04-27 03:53:12 +08:00
|
|
|
{
|
|
|
|
if (!ref)
|
|
|
|
return;
|
2019-04-13 13:54:31 +08:00
|
|
|
free_one_ref(ref->peer_ref);
|
2008-04-27 03:53:12 +08:00
|
|
|
free(ref->remote_status);
|
|
|
|
free(ref->symref);
|
|
|
|
free(ref);
|
|
|
|
}
|
|
|
|
|
2007-07-10 12:47:23 +08:00
|
|
|
void free_refs(struct ref *ref)
|
|
|
|
{
|
|
|
|
struct ref *next;
|
|
|
|
while (ref) {
|
|
|
|
next = ref->next;
|
2019-04-13 13:54:31 +08:00
|
|
|
free_one_ref(ref);
|
2007-07-10 12:47:23 +08:00
|
|
|
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 07:41:15 +08:00
|
|
|
int count_refspec_match(const char *pattern,
|
|
|
|
struct ref *refs,
|
|
|
|
struct ref **matched_ref)
|
2007-05-12 23:45:59 +08:00
|
|
|
{
|
|
|
|
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);
|
|
|
|
|
2014-01-14 11:16:07 +08:00
|
|
|
if (!refname_match(pattern, name))
|
2007-05-12 23:45:59 +08:00
|
|
|
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 &&
|
2013-12-01 04:55:40 +08:00
|
|
|
!starts_with(name, "refs/heads/") &&
|
|
|
|
!starts_with(name, "refs/tags/")) {
|
2007-05-12 23:45:59 +08:00
|
|
|
/* 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) {
|
2014-03-06 03:03:43 +08:00
|
|
|
if (matched_ref)
|
|
|
|
*matched_ref = matched_weak;
|
2007-05-12 23:45:59 +08:00
|
|
|
return weak_match;
|
|
|
|
}
|
|
|
|
else {
|
2014-03-06 03:03:43 +08:00
|
|
|
if (matched_ref)
|
|
|
|
*matched_ref = matched;
|
2007-05-12 23:45:59 +08:00
|
|
|
return match;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-07-10 12:47:26 +08:00
|
|
|
static void tail_link_ref(struct ref *ref, struct ref ***tail)
|
2007-05-12 23:45:59 +08:00
|
|
|
{
|
|
|
|
**tail = ref;
|
2007-07-10 12:47:26 +08:00
|
|
|
while (ref->next)
|
|
|
|
ref = ref->next;
|
2007-05-12 23:45:59 +08:00
|
|
|
*tail = &ref->next;
|
|
|
|
}
|
|
|
|
|
2012-02-23 06:43:40 +08:00
|
|
|
static struct ref *alloc_delete_ref(void)
|
|
|
|
{
|
|
|
|
struct ref *ref = alloc_ref("(delete)");
|
2015-11-10 10:22:20 +08:00
|
|
|
oidclr(&ref->new_oid);
|
2012-02-23 06:43:40 +08:00
|
|
|
return ref;
|
|
|
|
}
|
|
|
|
|
2014-03-06 03:03:43 +08:00
|
|
|
static int try_explicit_object_name(const char *name,
|
|
|
|
struct ref **match)
|
2007-05-12 23:45:59 +08:00
|
|
|
{
|
2015-11-10 10:22:30 +08:00
|
|
|
struct object_id oid;
|
2007-05-12 23:45:59 +08:00
|
|
|
|
2014-03-06 03:03:43 +08:00
|
|
|
if (!*name) {
|
|
|
|
if (match)
|
|
|
|
*match = alloc_delete_ref();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-03-28 21:58:46 +08:00
|
|
|
if (repo_get_oid(the_repository, name, &oid))
|
2014-03-06 03:03:43 +08:00
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (match) {
|
|
|
|
*match = alloc_ref(name);
|
2015-11-10 10:22:30 +08:00
|
|
|
oidcpy(&(*match)->new_oid, &oid);
|
2014-03-06 03:03:43 +08:00
|
|
|
}
|
|
|
|
return 0;
|
2007-05-12 23:45:59 +08:00
|
|
|
}
|
|
|
|
|
2007-07-10 12:47:26 +08:00
|
|
|
static struct ref *make_linked_ref(const char *name, struct ref ***tail)
|
2007-05-12 23:45:59 +08:00
|
|
|
{
|
2008-10-18 16:44:18 +08:00
|
|
|
struct ref *ret = alloc_ref(name);
|
2007-07-10 12:47:26 +08:00
|
|
|
tail_link_ref(ret, tail);
|
|
|
|
return ret;
|
2007-06-09 15:07:34 +08:00
|
|
|
}
|
2007-05-25 13:20:56 +08:00
|
|
|
|
push: allow unqualified dest refspecs to DWIM
Previously, a push like:
git push remote src:dst
would go through the following steps:
1. check for an unambiguous 'dst' on the remote; if it
exists, then push to that ref
2. otherwise, check if 'dst' begins with 'refs/'; if it
does, create a new ref
3. otherwise, complain because we don't know where in the
refs hierarchy to put 'dst'
However, in some cases, we can guess about the ref type of
'dst' based on the ref type of 'src'. Specifically, before
complaining we now check:
2.5. if 'src' resolves to a ref starting with refs/heads
or refs/tags, then prepend that to 'dst'
So now this creates a new branch on the remote, whereas it
previously failed with an error message:
git push master:newbranch
Note that, by design, we limit this DWIM behavior only to
source refs which resolve exactly (including symrefs which
resolve to existing refs). We still complain on a partial
destination refspec if the source is a raw sha1, or a ref
expression such as 'master~10'.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2008-04-23 17:16:06 +08:00
|
|
|
static char *guess_ref(const char *name, struct ref *peer)
|
|
|
|
{
|
|
|
|
struct strbuf buf = STRBUF_INIT;
|
|
|
|
|
2014-07-16 03:59:36 +08:00
|
|
|
const char *r = resolve_ref_unsafe(peer->name, RESOLVE_REF_READING,
|
2017-09-23 17:45:04 +08:00
|
|
|
NULL, NULL);
|
push: allow unqualified dest refspecs to DWIM
Previously, a push like:
git push remote src:dst
would go through the following steps:
1. check for an unambiguous 'dst' on the remote; if it
exists, then push to that ref
2. otherwise, check if 'dst' begins with 'refs/'; if it
does, create a new ref
3. otherwise, complain because we don't know where in the
refs hierarchy to put 'dst'
However, in some cases, we can guess about the ref type of
'dst' based on the ref type of 'src'. Specifically, before
complaining we now check:
2.5. if 'src' resolves to a ref starting with refs/heads
or refs/tags, then prepend that to 'dst'
So now this creates a new branch on the remote, whereas it
previously failed with an error message:
git push master:newbranch
Note that, by design, we limit this DWIM behavior only to
source refs which resolve exactly (including symrefs which
resolve to existing refs). We still complain on a partial
destination refspec if the source is a raw sha1, or a ref
expression such as 'master~10'.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2008-04-23 17:16:06 +08:00
|
|
|
if (!r)
|
|
|
|
return NULL;
|
|
|
|
|
2018-11-14 03:52:39 +08:00
|
|
|
if (starts_with(r, "refs/heads/")) {
|
push: allow unqualified dest refspecs to DWIM
Previously, a push like:
git push remote src:dst
would go through the following steps:
1. check for an unambiguous 'dst' on the remote; if it
exists, then push to that ref
2. otherwise, check if 'dst' begins with 'refs/'; if it
does, create a new ref
3. otherwise, complain because we don't know where in the
refs hierarchy to put 'dst'
However, in some cases, we can guess about the ref type of
'dst' based on the ref type of 'src'. Specifically, before
complaining we now check:
2.5. if 'src' resolves to a ref starting with refs/heads
or refs/tags, then prepend that to 'dst'
So now this creates a new branch on the remote, whereas it
previously failed with an error message:
git push master:newbranch
Note that, by design, we limit this DWIM behavior only to
source refs which resolve exactly (including symrefs which
resolve to existing refs). We still complain on a partial
destination refspec if the source is a raw sha1, or a ref
expression such as 'master~10'.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2008-04-23 17:16:06 +08:00
|
|
|
strbuf_addstr(&buf, "refs/heads/");
|
2018-11-14 03:52:39 +08:00
|
|
|
} else if (starts_with(r, "refs/tags/")) {
|
push: allow unqualified dest refspecs to DWIM
Previously, a push like:
git push remote src:dst
would go through the following steps:
1. check for an unambiguous 'dst' on the remote; if it
exists, then push to that ref
2. otherwise, check if 'dst' begins with 'refs/'; if it
does, create a new ref
3. otherwise, complain because we don't know where in the
refs hierarchy to put 'dst'
However, in some cases, we can guess about the ref type of
'dst' based on the ref type of 'src'. Specifically, before
complaining we now check:
2.5. if 'src' resolves to a ref starting with refs/heads
or refs/tags, then prepend that to 'dst'
So now this creates a new branch on the remote, whereas it
previously failed with an error message:
git push master:newbranch
Note that, by design, we limit this DWIM behavior only to
source refs which resolve exactly (including symrefs which
resolve to existing refs). We still complain on a partial
destination refspec if the source is a raw sha1, or a ref
expression such as 'master~10'.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2008-04-23 17:16:06 +08:00
|
|
|
strbuf_addstr(&buf, "refs/tags/");
|
2018-11-14 03:52:39 +08:00
|
|
|
} else {
|
push: allow unqualified dest refspecs to DWIM
Previously, a push like:
git push remote src:dst
would go through the following steps:
1. check for an unambiguous 'dst' on the remote; if it
exists, then push to that ref
2. otherwise, check if 'dst' begins with 'refs/'; if it
does, create a new ref
3. otherwise, complain because we don't know where in the
refs hierarchy to put 'dst'
However, in some cases, we can guess about the ref type of
'dst' based on the ref type of 'src'. Specifically, before
complaining we now check:
2.5. if 'src' resolves to a ref starting with refs/heads
or refs/tags, then prepend that to 'dst'
So now this creates a new branch on the remote, whereas it
previously failed with an error message:
git push master:newbranch
Note that, by design, we limit this DWIM behavior only to
source refs which resolve exactly (including symrefs which
resolve to existing refs). We still complain on a partial
destination refspec if the source is a raw sha1, or a ref
expression such as 'master~10'.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2008-04-23 17:16:06 +08:00
|
|
|
return NULL;
|
2018-11-14 03:52:39 +08:00
|
|
|
}
|
push: allow unqualified dest refspecs to DWIM
Previously, a push like:
git push remote src:dst
would go through the following steps:
1. check for an unambiguous 'dst' on the remote; if it
exists, then push to that ref
2. otherwise, check if 'dst' begins with 'refs/'; if it
does, create a new ref
3. otherwise, complain because we don't know where in the
refs hierarchy to put 'dst'
However, in some cases, we can guess about the ref type of
'dst' based on the ref type of 'src'. Specifically, before
complaining we now check:
2.5. if 'src' resolves to a ref starting with refs/heads
or refs/tags, then prepend that to 'dst'
So now this creates a new branch on the remote, whereas it
previously failed with an error message:
git push master:newbranch
Note that, by design, we limit this DWIM behavior only to
source refs which resolve exactly (including symrefs which
resolve to existing refs). We still complain on a partial
destination refspec if the source is a raw sha1, or a ref
expression such as 'master~10'.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2008-04-23 17:16:06 +08:00
|
|
|
|
|
|
|
strbuf_addstr(&buf, name);
|
|
|
|
return strbuf_detach(&buf, NULL);
|
|
|
|
}
|
|
|
|
|
2014-03-06 03:03:21 +08:00
|
|
|
static int match_explicit_lhs(struct ref *src,
|
2018-05-17 06:57:49 +08:00
|
|
|
struct refspec_item *rs,
|
2014-03-06 03:03:21 +08:00
|
|
|
struct ref **match,
|
|
|
|
int *allocated_match)
|
|
|
|
{
|
|
|
|
switch (count_refspec_match(rs->src, src, match)) {
|
|
|
|
case 1:
|
2014-03-06 03:03:43 +08:00
|
|
|
if (allocated_match)
|
|
|
|
*allocated_match = 0;
|
2014-03-06 03:03:21 +08:00
|
|
|
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.
|
|
|
|
*/
|
2014-03-06 03:03:43 +08:00
|
|
|
if (try_explicit_object_name(rs->src, match) < 0)
|
2018-11-10 13:16:09 +08:00
|
|
|
return error(_("src refspec %s does not match any"), rs->src);
|
2014-03-06 03:03:43 +08:00
|
|
|
if (allocated_match)
|
|
|
|
*allocated_match = 1;
|
2014-03-06 03:03:21 +08:00
|
|
|
return 0;
|
|
|
|
default:
|
2018-11-10 13:16:09 +08:00
|
|
|
return error(_("src refspec %s matches more than one"), rs->src);
|
2014-03-06 03:03:21 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-14 03:52:42 +08:00
|
|
|
static void show_push_unqualified_ref_name_error(const char *dst_value,
|
|
|
|
const char *matched_src_name)
|
|
|
|
{
|
push: add an advice on unqualified <dst> push
Add an advice to the recently improved error message added in
f8aae12034 ("push: allow unqualified dest refspecs to DWIM",
2008-04-23).
Now with advice.pushUnqualifiedRefName=true (on by default) we show a
hint about how to proceed:
$ ./git-push avar v2.19.0^{commit}:newbranch -n
error: The destination you provided is not a full refname (i.e.,
starting with "refs/"). We tried to guess what you meant by:
- Looking for a ref that matches 'newbranch' on the remote side.
- Checking if the <src> being pushed ('v2.19.0^{commit}')
is a ref in "refs/{heads,tags}/". If so we add a corresponding
refs/{heads,tags}/ prefix on the remote side.
Neither worked, so we gave up. You must fully qualify the ref.
hint: The <src> part of the refspec is a commit object.
hint: Did you mean to create a new branch by pushing to
hint: 'v2.19.0^{commit}:refs/heads/newbranch'?
error: failed to push some refs to 'git@github.com:avar/git.git'
When trying to push a tag, tree or a blob we suggest that perhaps the
user meant to push them to refs/tags/ instead.
The if/else duplication for all of OBJ_{COMMIT,TAG,TREE,BLOB} is
unfortunate, but is required to correctly mark the messages for
translation. See the discussion in
<87r2gxebsi.fsf@evledraar.gmail.com> about that.
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-11-14 03:52:43 +08:00
|
|
|
struct object_id oid;
|
|
|
|
enum object_type type;
|
|
|
|
|
2018-11-14 03:52:42 +08:00
|
|
|
/*
|
|
|
|
* 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);
|
push: add an advice on unqualified <dst> push
Add an advice to the recently improved error message added in
f8aae12034 ("push: allow unqualified dest refspecs to DWIM",
2008-04-23).
Now with advice.pushUnqualifiedRefName=true (on by default) we show a
hint about how to proceed:
$ ./git-push avar v2.19.0^{commit}:newbranch -n
error: The destination you provided is not a full refname (i.e.,
starting with "refs/"). We tried to guess what you meant by:
- Looking for a ref that matches 'newbranch' on the remote side.
- Checking if the <src> being pushed ('v2.19.0^{commit}')
is a ref in "refs/{heads,tags}/". If so we add a corresponding
refs/{heads,tags}/ prefix on the remote side.
Neither worked, so we gave up. You must fully qualify the ref.
hint: The <src> part of the refspec is a commit object.
hint: Did you mean to create a new branch by pushing to
hint: 'v2.19.0^{commit}:refs/heads/newbranch'?
error: failed to push some refs to 'git@github.com:avar/git.git'
When trying to push a tag, tree or a blob we suggest that perhaps the
user meant to push them to refs/tags/ instead.
The if/else duplication for all of OBJ_{COMMIT,TAG,TREE,BLOB} is
unfortunate, but is required to correctly mark the messages for
translation. See the discussion in
<87r2gxebsi.fsf@evledraar.gmail.com> about that.
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-11-14 03:52:43 +08:00
|
|
|
|
2021-08-23 18:44:00 +08:00
|
|
|
if (!advice_enabled(ADVICE_PUSH_UNQUALIFIED_REF_NAME))
|
push: add an advice on unqualified <dst> push
Add an advice to the recently improved error message added in
f8aae12034 ("push: allow unqualified dest refspecs to DWIM",
2008-04-23).
Now with advice.pushUnqualifiedRefName=true (on by default) we show a
hint about how to proceed:
$ ./git-push avar v2.19.0^{commit}:newbranch -n
error: The destination you provided is not a full refname (i.e.,
starting with "refs/"). We tried to guess what you meant by:
- Looking for a ref that matches 'newbranch' on the remote side.
- Checking if the <src> being pushed ('v2.19.0^{commit}')
is a ref in "refs/{heads,tags}/". If so we add a corresponding
refs/{heads,tags}/ prefix on the remote side.
Neither worked, so we gave up. You must fully qualify the ref.
hint: The <src> part of the refspec is a commit object.
hint: Did you mean to create a new branch by pushing to
hint: 'v2.19.0^{commit}:refs/heads/newbranch'?
error: failed to push some refs to 'git@github.com:avar/git.git'
When trying to push a tag, tree or a blob we suggest that perhaps the
user meant to push them to refs/tags/ instead.
The if/else duplication for all of OBJ_{COMMIT,TAG,TREE,BLOB} is
unfortunate, but is required to correctly mark the messages for
translation. See the discussion in
<87r2gxebsi.fsf@evledraar.gmail.com> about that.
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-11-14 03:52:43 +08:00
|
|
|
return;
|
|
|
|
|
2023-03-28 21:58:46 +08:00
|
|
|
if (repo_get_oid(the_repository, matched_src_name, &oid))
|
push: add an advice on unqualified <dst> push
Add an advice to the recently improved error message added in
f8aae12034 ("push: allow unqualified dest refspecs to DWIM",
2008-04-23).
Now with advice.pushUnqualifiedRefName=true (on by default) we show a
hint about how to proceed:
$ ./git-push avar v2.19.0^{commit}:newbranch -n
error: The destination you provided is not a full refname (i.e.,
starting with "refs/"). We tried to guess what you meant by:
- Looking for a ref that matches 'newbranch' on the remote side.
- Checking if the <src> being pushed ('v2.19.0^{commit}')
is a ref in "refs/{heads,tags}/". If so we add a corresponding
refs/{heads,tags}/ prefix on the remote side.
Neither worked, so we gave up. You must fully qualify the ref.
hint: The <src> part of the refspec is a commit object.
hint: Did you mean to create a new branch by pushing to
hint: 'v2.19.0^{commit}:refs/heads/newbranch'?
error: failed to push some refs to 'git@github.com:avar/git.git'
When trying to push a tag, tree or a blob we suggest that perhaps the
user meant to push them to refs/tags/ instead.
The if/else duplication for all of OBJ_{COMMIT,TAG,TREE,BLOB} is
unfortunate, but is required to correctly mark the messages for
translation. See the discussion in
<87r2gxebsi.fsf@evledraar.gmail.com> about that.
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-11-14 03:52:43 +08:00
|
|
|
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);
|
|
|
|
}
|
2018-11-14 03:52:42 +08:00
|
|
|
}
|
|
|
|
|
2007-06-09 14:22:58 +08:00
|
|
|
static int match_explicit(struct ref *src, struct ref *dst,
|
|
|
|
struct ref ***dst_tail,
|
2018-05-17 06:57:49 +08:00
|
|
|
struct refspec_item *rs)
|
2007-05-12 23:45:59 +08:00
|
|
|
{
|
2007-06-09 14:22:58 +08:00
|
|
|
struct ref *matched_src, *matched_dst;
|
2014-03-06 03:03:21 +08:00
|
|
|
int allocated_src;
|
2007-05-25 13:20:56 +08:00
|
|
|
|
2007-06-09 14:22:58 +08:00
|
|
|
const char *dst_value = rs->dst;
|
push: allow unqualified dest refspecs to DWIM
Previously, a push like:
git push remote src:dst
would go through the following steps:
1. check for an unambiguous 'dst' on the remote; if it
exists, then push to that ref
2. otherwise, check if 'dst' begins with 'refs/'; if it
does, create a new ref
3. otherwise, complain because we don't know where in the
refs hierarchy to put 'dst'
However, in some cases, we can guess about the ref type of
'dst' based on the ref type of 'src'. Specifically, before
complaining we now check:
2.5. if 'src' resolves to a ref starting with refs/heads
or refs/tags, then prepend that to 'dst'
So now this creates a new branch on the remote, whereas it
previously failed with an error message:
git push master:newbranch
Note that, by design, we limit this DWIM behavior only to
source refs which resolve exactly (including symrefs which
resolve to existing refs). We still complain on a partial
destination refspec if the source is a raw sha1, or a ref
expression such as 'master~10'.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2008-04-23 17:16:06 +08:00
|
|
|
char *dst_guess;
|
2007-05-12 23:45:59 +08:00
|
|
|
|
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-10-01 05:25:29 +08: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-17 00:15:02 +08:00
|
|
|
return 0;
|
2007-05-25 13:20:56 +08:00
|
|
|
|
2007-06-09 14:22:58 +08:00
|
|
|
matched_src = matched_dst = NULL;
|
2014-03-06 03:03:21 +08:00
|
|
|
if (match_explicit_lhs(src, rs, &matched_src, &allocated_src) < 0)
|
|
|
|
return -1;
|
2007-06-09 15:14:04 +08:00
|
|
|
|
2007-09-25 12:13:25 +08:00
|
|
|
if (!dst_value) {
|
2008-02-21 01:54:05 +08:00
|
|
|
int flag;
|
|
|
|
|
2014-07-16 03:59:36 +08:00
|
|
|
dst_value = resolve_ref_unsafe(matched_src->name,
|
|
|
|
RESOLVE_REF_READING,
|
2017-09-23 17:45:04 +08:00
|
|
|
NULL, &flag);
|
2008-02-21 01:54:05 +08:00
|
|
|
if (!dst_value ||
|
|
|
|
((flag & REF_ISSYMREF) &&
|
2013-12-01 04:55:40 +08:00
|
|
|
!starts_with(dst_value, "refs/heads/")))
|
2018-11-10 13:16:09 +08:00
|
|
|
die(_("%s cannot be resolved to branch"),
|
2008-02-21 01:54:05 +08:00
|
|
|
matched_src->name);
|
2007-09-25 12:13:25 +08:00
|
|
|
}
|
2007-06-09 15:14:04 +08:00
|
|
|
|
2007-06-09 14:22:58 +08:00
|
|
|
switch (count_refspec_match(dst_value, dst, &matched_dst)) {
|
|
|
|
case 1:
|
|
|
|
break;
|
|
|
|
case 0:
|
2018-11-14 03:52:39 +08:00
|
|
|
if (starts_with(dst_value, "refs/")) {
|
2007-07-10 12:47:26 +08:00
|
|
|
matched_dst = make_linked_ref(dst_value, dst_tail);
|
2018-11-14 03:52:39 +08:00
|
|
|
} else if (is_null_oid(&matched_src->new_oid)) {
|
2018-11-10 13:16:09 +08:00
|
|
|
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-04 02:04:39 +08:00
|
|
|
dst_value);
|
2018-11-14 03:52:39 +08:00
|
|
|
} else if ((dst_guess = guess_ref(dst_value, matched_src))) {
|
push: allow unqualified dest refspecs to DWIM
Previously, a push like:
git push remote src:dst
would go through the following steps:
1. check for an unambiguous 'dst' on the remote; if it
exists, then push to that ref
2. otherwise, check if 'dst' begins with 'refs/'; if it
does, create a new ref
3. otherwise, complain because we don't know where in the
refs hierarchy to put 'dst'
However, in some cases, we can guess about the ref type of
'dst' based on the ref type of 'src'. Specifically, before
complaining we now check:
2.5. if 'src' resolves to a ref starting with refs/heads
or refs/tags, then prepend that to 'dst'
So now this creates a new branch on the remote, whereas it
previously failed with an error message:
git push master:newbranch
Note that, by design, we limit this DWIM behavior only to
source refs which resolve exactly (including symrefs which
resolve to existing refs). We still complain on a partial
destination refspec if the source is a raw sha1, or a ref
expression such as 'master~10'.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2008-04-23 17:16:06 +08:00
|
|
|
matched_dst = make_linked_ref(dst_guess, dst_tail);
|
2017-05-04 21:59:01 +08:00
|
|
|
free(dst_guess);
|
2018-11-14 03:52:39 +08:00
|
|
|
} else {
|
2018-11-14 03:52:42 +08:00
|
|
|
show_push_unqualified_ref_name_error(dst_value,
|
|
|
|
matched_src->name);
|
2018-11-14 03:52:39 +08:00
|
|
|
}
|
2007-06-09 14:22:58 +08:00
|
|
|
break;
|
|
|
|
default:
|
2007-06-09 15:14:04 +08:00
|
|
|
matched_dst = NULL;
|
2018-11-10 13:16:09 +08:00
|
|
|
error(_("dst refspec %s matches more than one"),
|
2007-06-09 14:22:58 +08:00
|
|
|
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-17 00:15:02 +08:00
|
|
|
if (!matched_dst)
|
|
|
|
return -1;
|
|
|
|
if (matched_dst->peer_ref)
|
2018-11-10 13:16:09 +08:00
|
|
|
return error(_("dst ref %s receives from more than one src"),
|
|
|
|
matched_dst->name);
|
2007-06-09 14:22:58 +08:00
|
|
|
else {
|
2014-03-06 03:03:21 +08:00
|
|
|
matched_dst->peer_ref = allocated_src ?
|
|
|
|
matched_src :
|
|
|
|
copy_ref(matched_src);
|
2007-06-09 14:22:58 +08:00
|
|
|
matched_dst->force = rs->force;
|
2007-05-12 23:45:59 +08:00
|
|
|
}
|
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-17 00:15:02 +08:00
|
|
|
return 0;
|
2007-06-09 14:22:58 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int match_explicit_refs(struct ref *src, struct ref *dst,
|
2018-05-17 06:58:14 +08:00
|
|
|
struct ref ***dst_tail, struct refspec *rs)
|
2007-06-09 14:22:58 +08:00
|
|
|
{
|
|
|
|
int i, errs;
|
2018-05-17 06:58:14 +08:00
|
|
|
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-17 00:15:02 +08:00
|
|
|
return errs;
|
2007-05-12 23:45:59 +08:00
|
|
|
}
|
|
|
|
|
2018-05-17 06:58:13 +08:00
|
|
|
static char *get_ref_match(const struct refspec *rs, const struct ref *ref,
|
|
|
|
int send_mirror, int direction,
|
|
|
|
const struct refspec_item **ret_pat)
|
2007-05-25 13:20:56 +08:00
|
|
|
{
|
2018-05-17 06:57:49 +08:00
|
|
|
const struct refspec_item *pat;
|
2012-02-23 06:43:39 +08:00
|
|
|
char *name;
|
2007-05-25 13:20:56 +08:00
|
|
|
int i;
|
2008-04-28 23:32:12 +08:00
|
|
|
int matching_refs = -1;
|
2018-05-17 06:58:13 +08:00
|
|
|
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-10-01 05:25:29 +08:00
|
|
|
|
|
|
|
if (item->negative)
|
|
|
|
continue;
|
|
|
|
|
2018-05-17 06:58:13 +08:00
|
|
|
if (item->matching &&
|
|
|
|
(matching_refs == -1 || item->force)) {
|
2008-04-28 23:32:12 +08:00
|
|
|
matching_refs = i;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2018-05-17 06:58:13 +08:00
|
|
|
if (item->pattern) {
|
|
|
|
const char *dst_side = item->dst ? item->dst : item->src;
|
2012-02-23 06:43:41 +08:00
|
|
|
int match;
|
|
|
|
if (direction == FROM_SRC)
|
2018-05-17 06:58:13 +08:00
|
|
|
match = match_name_with_pattern(item->src, ref->name, dst_side, &name);
|
2012-02-23 06:43:41 +08:00
|
|
|
else
|
2018-05-17 06:58:13 +08:00
|
|
|
match = match_name_with_pattern(dst_side, ref->name, item->src, &name);
|
2012-02-23 06:43:41 +08:00
|
|
|
if (match) {
|
2012-02-23 06:43:39 +08:00
|
|
|
matching_refs = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2007-05-25 13:20:56 +08:00
|
|
|
}
|
2012-02-23 06:43:39 +08:00
|
|
|
if (matching_refs == -1)
|
2008-04-28 23:32:12 +08:00
|
|
|
return NULL;
|
2012-02-23 06:43:39 +08:00
|
|
|
|
2018-05-17 06:58:13 +08:00
|
|
|
pat = &rs->items[matching_refs];
|
2012-02-23 06:43:39 +08:00
|
|
|
if (pat->matching) {
|
|
|
|
/*
|
|
|
|
* "matching refs"; traditionally we pushed everything
|
|
|
|
* including refs outside refs/heads/ hierarchy, but
|
|
|
|
* that does not make much sense these days.
|
|
|
|
*/
|
2013-12-01 04:55:40 +08:00
|
|
|
if (!send_mirror && !starts_with(ref->name, "refs/heads/"))
|
2012-02-23 06:43:39 +08:00
|
|
|
return NULL;
|
|
|
|
name = xstrdup(ref->name);
|
|
|
|
}
|
|
|
|
if (ret_pat)
|
|
|
|
*ret_pat = pat;
|
|
|
|
return name;
|
2007-05-25 13:20:56 +08:00
|
|
|
}
|
|
|
|
|
2009-05-31 22:26:48 +08:00
|
|
|
static struct ref **tail_ref(struct ref **head)
|
|
|
|
{
|
|
|
|
struct ref **tail = head;
|
|
|
|
while (*tail)
|
|
|
|
tail = &((*tail)->next);
|
|
|
|
return tail;
|
|
|
|
}
|
|
|
|
|
2013-03-05 04:09:50 +08:00
|
|
|
struct tips {
|
|
|
|
struct commit **tip;
|
|
|
|
int nr, alloc;
|
|
|
|
};
|
|
|
|
|
2015-11-10 10:22:30 +08:00
|
|
|
static void add_to_tips(struct tips *tips, const struct object_id *oid)
|
2013-03-05 04:09:50 +08:00
|
|
|
{
|
|
|
|
struct commit *commit;
|
|
|
|
|
2015-11-10 10:22:30 +08:00
|
|
|
if (is_null_oid(oid))
|
2013-03-05 04:09:50 +08:00
|
|
|
return;
|
2018-06-29 09:21:57 +08:00
|
|
|
commit = lookup_commit_reference_gently(the_repository, oid, 1);
|
2013-03-05 04:09:50 +08:00
|
|
|
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 &&
|
2015-11-10 10:22:20 +08:00
|
|
|
!is_null_oid(&ref->peer_ref->new_oid))
|
2015-11-10 10:22:30 +08:00
|
|
|
add_to_tips(&sent_tips, &ref->peer_ref->new_oid);
|
2013-03-05 04:09:50 +08:00
|
|
|
else
|
2015-11-10 10:22:30 +08:00
|
|
|
add_to_tips(&sent_tips, &ref->old_oid);
|
2013-12-01 04:55:40 +08:00
|
|
|
if (starts_with(ref->name, "refs/tags/"))
|
2013-03-05 04:09:50 +08:00
|
|
|
string_list_append(&dst_tag, ref->name);
|
|
|
|
}
|
|
|
|
clear_commit_marks_many(sent_tips.nr, sent_tips.tip, TMP_MARK);
|
|
|
|
|
2014-11-25 16:02:35 +08:00
|
|
|
string_list_sort(&dst_tag);
|
2013-03-05 04:09:50 +08:00
|
|
|
|
|
|
|
/* Collect tags they do not have. */
|
|
|
|
for (ref = src; ref; ref = ref->next) {
|
2013-12-01 04:55:40 +08:00
|
|
|
if (!starts_with(ref->name, "refs/tags/"))
|
2013-03-05 04:09:50 +08:00
|
|
|
continue; /* not a tag */
|
|
|
|
if (string_list_has_string(&dst_tag, ref->name))
|
|
|
|
continue; /* they already have it */
|
2018-04-26 02:20:59 +08:00
|
|
|
if (oid_object_info(the_repository, &ref->new_oid, NULL) != OBJ_TAG)
|
2013-03-05 04:09:50 +08:00
|
|
|
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) {
|
2018-11-02 21:14:49 +08:00
|
|
|
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);
|
|
|
|
|
2013-03-05 04:09:50 +08:00
|
|
|
for_each_string_list_item(item, &src_tag) {
|
|
|
|
struct ref *ref = item->util;
|
2018-11-02 21:14:49 +08:00
|
|
|
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) {
|
2013-03-05 04:09:50 +08:00
|
|
|
struct ref *dst_ref;
|
2018-11-02 21:14:49 +08:00
|
|
|
struct ref *ref = item->util;
|
2013-03-05 04:09:50 +08:00
|
|
|
struct commit *commit;
|
|
|
|
|
2015-11-10 10:22:20 +08:00
|
|
|
if (is_null_oid(&ref->new_oid))
|
2013-03-05 04:09:50 +08:00
|
|
|
continue;
|
2018-06-29 09:21:57 +08:00
|
|
|
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 06:10:10 +08:00
|
|
|
1);
|
2013-03-05 04:09:50 +08:00
|
|
|
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?
|
|
|
|
*/
|
2018-11-02 21:14:49 +08:00
|
|
|
if (!(commit->object.flags & reachable_flag))
|
2013-03-05 04:09:50 +08:00
|
|
|
continue;
|
|
|
|
|
|
|
|
/* Add it in */
|
|
|
|
dst_ref = make_linked_ref(ref->name, dst_tail);
|
2015-11-10 10:22:20 +08:00
|
|
|
oidcpy(&dst_ref->new_oid, &ref->new_oid);
|
2013-03-05 04:09:50 +08:00
|
|
|
dst_ref->peer_ref = copy_ref(ref);
|
|
|
|
}
|
2018-11-02 21:14:49 +08:00
|
|
|
|
|
|
|
clear_commit_marks_many(nr_src_commits, src_commits, reachable_flag);
|
|
|
|
free(src_commits);
|
|
|
|
free_commit_list(found_commits);
|
2013-03-05 04:09:50 +08:00
|
|
|
}
|
2018-11-02 21:14:49 +08:00
|
|
|
|
2013-03-05 04:09:50 +08:00
|
|
|
string_list_clear(&src_tag, 0);
|
|
|
|
free(sent_tips.tip);
|
|
|
|
}
|
|
|
|
|
2013-07-09 04:56:53 +08:00
|
|
|
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 16:58:39 +08: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;
|
|
|
|
|
2014-11-25 16:02:35 +08:00
|
|
|
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 16:58:39 +08:00
|
|
|
}
|
|
|
|
|
2014-03-06 03:04:54 +08: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.
|
|
|
|
*/
|
2018-05-17 06:58:22 +08:00
|
|
|
int check_push_refs(struct ref *src, struct refspec *rs)
|
2014-03-06 03:04:54 +08:00
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
int i;
|
|
|
|
|
2018-05-17 06:58:22 +08:00
|
|
|
for (i = 0; i < rs->nr; i++) {
|
|
|
|
struct refspec_item *item = &rs->items[i];
|
2014-03-06 03:04:54 +08:00
|
|
|
|
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-10-01 05:25:29 +08:00
|
|
|
if (item->pattern || item->matching || item->negative)
|
2014-03-06 03:04:54 +08:00
|
|
|
continue;
|
|
|
|
|
2018-05-17 06:58:22 +08:00
|
|
|
ret |= match_explicit_lhs(src, item, NULL, NULL);
|
2014-03-06 03:04:54 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2007-06-09 14:22:58 +08:00
|
|
|
/*
|
2011-09-10 02:54:58 +08:00
|
|
|
* 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).
|
2007-06-09 14:22:58 +08:00
|
|
|
*/
|
2011-09-10 02:54:58 +08:00
|
|
|
int match_push_refs(struct ref *src, struct ref **dst,
|
2018-05-17 06:58:21 +08:00
|
|
|
struct refspec *rs, int flags)
|
2007-05-12 23:45:59 +08:00
|
|
|
{
|
2007-11-10 07:32:10 +08:00
|
|
|
int send_all = flags & MATCH_REFS_ALL;
|
|
|
|
int send_mirror = flags & MATCH_REFS_MIRROR;
|
2012-02-23 06:43:41 +08:00
|
|
|
int send_prune = flags & MATCH_REFS_PRUNE;
|
2009-02-25 16:32:17 +08:00
|
|
|
int errs;
|
2012-02-23 06:43:38 +08:00
|
|
|
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 16:58:39 +08:00
|
|
|
struct string_list dst_ref_index = STRING_LIST_INIT_NODUP;
|
2007-05-12 23:45:59 +08:00
|
|
|
|
2018-05-17 06:58:21 +08:00
|
|
|
/* If no refspec is provided, use the default ":" */
|
|
|
|
if (!rs->nr)
|
|
|
|
refspec_append(rs, ":");
|
|
|
|
|
|
|
|
errs = match_explicit_refs(src, *dst, &dst_tail, rs);
|
2007-05-12 23:45:59 +08:00
|
|
|
|
|
|
|
/* pick the remainder */
|
2012-02-23 06:43:38 +08:00
|
|
|
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 16:58:39 +08:00
|
|
|
struct string_list_item *dst_item;
|
2007-05-12 23:45:59 +08:00
|
|
|
struct ref *dst_peer;
|
2018-05-17 06:57:49 +08:00
|
|
|
const struct refspec_item *pat = NULL;
|
2007-06-08 07:43:05 +08:00
|
|
|
char *dst_name;
|
2012-02-23 06:43:39 +08:00
|
|
|
|
2018-05-17 06:58:21 +08:00
|
|
|
dst_name = get_ref_match(rs, ref, send_mirror, FROM_SRC, &pat);
|
2012-02-23 06:43:39 +08:00
|
|
|
if (!dst_name)
|
2008-04-28 23:32:12 +08:00
|
|
|
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 16:58:39 +08: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;
|
2008-04-28 23:32:12 +08:00
|
|
|
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
|
2014-04-01 06:11:46 +08:00
|
|
|
* --all or --mirror.
|
2008-04-28 23:32:12 +08:00
|
|
|
*/
|
|
|
|
goto free_name;
|
2007-11-10 07:32:10 +08:00
|
|
|
|
2007-05-12 23:45:59 +08:00
|
|
|
/* Create a new one and link it */
|
2009-05-31 22:26:48 +08:00
|
|
|
dst_peer = make_linked_ref(dst_name, &dst_tail);
|
2015-11-10 10:22:20 +08:00
|
|
|
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 16:58:39 +08:00
|
|
|
string_list_insert(&dst_ref_index,
|
|
|
|
dst_peer->name)->util = dst_peer;
|
2007-05-12 23:45:59 +08:00
|
|
|
}
|
2012-02-23 06:43:38 +08:00
|
|
|
dst_peer->peer_ref = copy_ref(ref);
|
2008-04-28 23:32:12 +08:00
|
|
|
dst_peer->force = pat->force;
|
2007-06-08 07:43:05 +08:00
|
|
|
free_name:
|
|
|
|
free(dst_name);
|
2007-05-12 23:45:59 +08:00
|
|
|
}
|
2013-03-05 04:09:50 +08:00
|
|
|
|
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 16:58:39 +08:00
|
|
|
string_list_clear(&dst_ref_index, 0);
|
|
|
|
|
2013-03-05 04:09:50 +08:00
|
|
|
if (flags & MATCH_REFS_FOLLOW_TAGS)
|
|
|
|
add_missing_tags(src, dst, &dst_tail);
|
|
|
|
|
2012-02-23 06:43:41 +08:00
|
|
|
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 16:58:39 +08:00
|
|
|
struct string_list src_ref_index = STRING_LIST_INIT_NODUP;
|
2012-02-23 06:43:41 +08:00
|
|
|
/* 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;
|
|
|
|
|
2018-05-17 06:58:21 +08:00
|
|
|
src_name = get_ref_match(rs, ref, send_mirror, FROM_DST, NULL);
|
2012-02-23 06:43:41 +08:00
|
|
|
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 16:58:39 +08:00
|
|
|
if (!src_ref_index.nr)
|
|
|
|
prepare_ref_index(&src_ref_index, src);
|
|
|
|
if (!string_list_has_string(&src_ref_index,
|
|
|
|
src_name))
|
2012-02-23 06:43:41 +08:00
|
|
|
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 16:58:39 +08:00
|
|
|
string_list_clear(&src_ref_index, 0);
|
2012-02-23 06:43:41 +08:00
|
|
|
}
|
2018-05-17 06:57:57 +08:00
|
|
|
|
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-10-01 05:25:29 +08:00
|
|
|
*dst = apply_negative_refspecs(*dst, rs);
|
|
|
|
|
2009-02-25 16:32:17 +08:00
|
|
|
if (errs)
|
|
|
|
return -1;
|
2007-05-12 23:45:59 +08:00
|
|
|
return 0;
|
|
|
|
}
|
2007-09-11 11:02:56 +08:00
|
|
|
|
2010-01-08 10:12:42 +08:00
|
|
|
void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,
|
2013-07-09 05:42:40 +08:00
|
|
|
int force_update)
|
2010-01-08 10:12:42 +08:00
|
|
|
{
|
|
|
|
struct ref *ref;
|
|
|
|
|
|
|
|
for (ref = remote_refs; ref; ref = ref->next) {
|
2012-11-30 09:41:36 +08:00
|
|
|
int force_ref_update = ref->force || force_update;
|
2013-07-09 05:42:40 +08:00
|
|
|
int reject_reason = 0;
|
2012-11-30 09:41:36 +08:00
|
|
|
|
2010-01-08 10:12:42 +08:00
|
|
|
if (ref->peer_ref)
|
2015-11-10 10:22:20 +08:00
|
|
|
oidcpy(&ref->new_oid, &ref->peer_ref->new_oid);
|
2010-01-08 10:12:42 +08:00
|
|
|
else if (!send_mirror)
|
|
|
|
continue;
|
|
|
|
|
2015-11-10 10:22:20 +08:00
|
|
|
ref->deletion = is_null_oid(&ref->new_oid);
|
2010-01-08 10:12:42 +08:00
|
|
|
if (!ref->deletion &&
|
convert "oidcmp() == 0" to oideq()
Using the more restrictive oideq() should, in the long run,
give the compiler more opportunities to optimize these
callsites. For now, this conversion should be a complete
noop with respect to the generated code.
The result is also perhaps a little more readable, as it
avoids the "zero is equal" idiom. Since it's so prevalent in
C, I think seasoned programmers tend not to even notice it
anymore, but it can sometimes make for awkward double
negations (e.g., we can drop a few !!oidcmp() instances
here).
This patch was generated almost entirely by the included
coccinelle patch. This mechanical conversion should be
completely safe, because we check explicitly for cases where
oidcmp() is compared to 0, which is what oideq() is doing
under the hood. Note that we don't have to catch "!oidcmp()"
separately; coccinelle's standard isomorphisms make sure the
two are treated equivalently.
I say "almost" because I did hand-edit the coccinelle output
to fix up a few style violations (it mostly keeps the
original formatting, but sometimes unwraps long lines).
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-08-29 05:22:40 +08:00
|
|
|
oideq(&ref->old_oid, &ref->new_oid)) {
|
2010-01-08 10:12:42 +08:00
|
|
|
ref->status = REF_STATUS_UPTODATE;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2012-11-30 09:41:40 +08:00
|
|
|
/*
|
2016-01-30 07:18:42 +08:00
|
|
|
* If the remote ref has moved and is now different
|
|
|
|
* from what we expect, reject any push.
|
2013-07-09 05:42:40 +08:00
|
|
|
*
|
|
|
|
* 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.
|
2020-10-03 20:10:44 +08:00
|
|
|
*
|
|
|
|
* 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.
|
2013-07-09 05:42:40 +08:00
|
|
|
*/
|
|
|
|
if (ref->expect_old_sha1) {
|
2018-08-29 05:22:48 +08:00
|
|
|
if (!oideq(&ref->old_oid, &ref->old_oid_expect))
|
2013-07-09 05:42:40 +08:00
|
|
|
reject_reason = REF_STATUS_REJECT_STALE;
|
2020-10-03 20:10:44 +08:00
|
|
|
else if (ref->check_reachable && ref->unreachable)
|
|
|
|
reject_reason =
|
|
|
|
REF_STATUS_REJECT_REMOTE_UPDATED;
|
2016-01-30 07:18:42 +08:00
|
|
|
else
|
2020-10-03 20:10:44 +08:00
|
|
|
/*
|
|
|
|
* If the ref isn't stale, and is reachable
|
2021-06-11 19:18:50 +08:00
|
|
|
* from one of the reflog entries of
|
2020-10-03 20:10:44 +08:00
|
|
|
* the local branch, force the update.
|
|
|
|
*/
|
2016-01-30 07:18:42 +08:00
|
|
|
force_ref_update = 1;
|
2013-07-09 05:42:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2016-01-30 07:18:42 +08:00
|
|
|
* If the update isn't already rejected then check
|
|
|
|
* the usual "must fast-forward" rules.
|
2013-07-09 05:42:40 +08:00
|
|
|
*
|
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-17 05:02:27 +08:00
|
|
|
* Decide whether an individual refspec A:B can be
|
|
|
|
* pushed. The push will succeed if any of the
|
|
|
|
* following are true:
|
2010-01-08 10:12:42 +08:00
|
|
|
*
|
2012-11-30 09:41:40 +08:00
|
|
|
* (1) the remote reference B does not exist
|
2010-01-08 10:12:42 +08:00
|
|
|
*
|
2012-11-30 09:41:40 +08:00
|
|
|
* (2) the remote reference B is being removed (i.e.,
|
|
|
|
* pushing :B where no source is specified)
|
2010-01-08 10:12:42 +08:00
|
|
|
*
|
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-17 05:02:27 +08: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.
|
2010-01-08 10:12:42 +08:00
|
|
|
*
|
2012-11-30 09:41:40 +08:00
|
|
|
* (4) it is forced using the +A:B notation, or by
|
|
|
|
* passing the --force argument
|
2010-01-08 10:12:42 +08:00
|
|
|
*/
|
|
|
|
|
2016-01-30 07:18:42 +08:00
|
|
|
if (!reject_reason && !ref->deletion && !is_null_oid(&ref->old_oid)) {
|
2013-12-01 04:55:40 +08:00
|
|
|
if (starts_with(ref->name, "refs/tags/"))
|
2013-07-09 05:42:40 +08:00
|
|
|
reject_reason = REF_STATUS_REJECT_ALREADY_EXISTS;
|
2023-03-28 21:58:50 +08:00
|
|
|
else if (!repo_has_object_file(the_repository, &ref->old_oid))
|
2013-07-09 05:42:40 +08:00
|
|
|
reject_reason = REF_STATUS_REJECT_FETCH_FIRST;
|
2018-06-29 09:21:57 +08:00
|
|
|
else if (!lookup_commit_reference_gently(the_repository, &ref->old_oid, 1) ||
|
|
|
|
!lookup_commit_reference_gently(the_repository, &ref->new_oid, 1))
|
2013-07-09 05:42:40 +08:00
|
|
|
reject_reason = REF_STATUS_REJECT_NEEDS_FORCE;
|
2015-11-10 10:22:25 +08:00
|
|
|
else if (!ref_newer(&ref->new_oid, &ref->old_oid))
|
2013-07-09 05:42:40 +08:00
|
|
|
reject_reason = REF_STATUS_REJECT_NONFASTFORWARD;
|
2010-01-08 10:12:42 +08:00
|
|
|
}
|
2013-07-09 05:42:40 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* "--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;
|
2010-01-08 10:12:42 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-18 08:53:24 +08:00
|
|
|
static void set_merge(struct remote_state *remote_state, struct branch *ret)
|
checkout: report upstream correctly even with loosely defined branch.*.merge
When checking out a branch that is set to build on top of another
branch (often, a remote-tracking branch), "git checkout" reports how
your work relates to the other branch, e.g.
Your branch is behind 'origin/master', and can be fast-forwarded.
Back when this feature was introduced, this was only done for
branches that build on remote-tracking branches, but 5e6e2b48 (Make
local branches behave like remote branches when --tracked,
2009-04-01) added support to give the same report for branches that
build on other local branches (i.e. branches whose branch.*.remote
variables are set to '.'). Unlike the support for the branches
building on remote-tracking branches, however, this did not take
into account the fact that branch.*.merge configuration is allowed
to record a shortened branch name.
When branch.*.merge is set to 'master' (not 'refs/heads/master'),
i.e. "my branch builds on the local 'master' branch", this caused
"git checkout" to report:
Your branch is based on 'master', but the upstream is gone.
The upstream is our repository and is definitely not gone, so this
output is nonsense.
The fix is fairly obvious; just like the branch name is DWIMed when
"git pull" merges from the 'master' branch without complaint on such
a branch, the name of the branch the current branch builds upon
needs to be DWIMed the same way.
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2014-10-15 05:42:04 +08:00
|
|
|
{
|
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 12:45:13 +08:00
|
|
|
struct remote *remote;
|
checkout: report upstream correctly even with loosely defined branch.*.merge
When checking out a branch that is set to build on top of another
branch (often, a remote-tracking branch), "git checkout" reports how
your work relates to the other branch, e.g.
Your branch is behind 'origin/master', and can be fast-forwarded.
Back when this feature was introduced, this was only done for
branches that build on remote-tracking branches, but 5e6e2b48 (Make
local branches behave like remote branches when --tracked,
2009-04-01) added support to give the same report for branches that
build on other local branches (i.e. branches whose branch.*.remote
variables are set to '.'). Unlike the support for the branches
building on remote-tracking branches, however, this did not take
into account the fact that branch.*.merge configuration is allowed
to record a shortened branch name.
When branch.*.merge is set to 'master' (not 'refs/heads/master'),
i.e. "my branch builds on the local 'master' branch", this caused
"git checkout" to report:
Your branch is based on 'master', but the upstream is gone.
The upstream is our repository and is definitely not gone, so this
output is nonsense.
The fix is fairly obvious; just like the branch name is DWIMed when
"git pull" merges from the 'master' branch without complaint on such
a branch, the name of the branch the current branch builds upon
needs to be DWIMed the same way.
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2014-10-15 05:42:04 +08:00
|
|
|
char *ref;
|
2015-11-10 10:22:30 +08:00
|
|
|
struct object_id oid;
|
checkout: report upstream correctly even with loosely defined branch.*.merge
When checking out a branch that is set to build on top of another
branch (often, a remote-tracking branch), "git checkout" reports how
your work relates to the other branch, e.g.
Your branch is behind 'origin/master', and can be fast-forwarded.
Back when this feature was introduced, this was only done for
branches that build on remote-tracking branches, but 5e6e2b48 (Make
local branches behave like remote branches when --tracked,
2009-04-01) added support to give the same report for branches that
build on other local branches (i.e. branches whose branch.*.remote
variables are set to '.'). Unlike the support for the branches
building on remote-tracking branches, however, this did not take
into account the fact that branch.*.merge configuration is allowed
to record a shortened branch name.
When branch.*.merge is set to 'master' (not 'refs/heads/master'),
i.e. "my branch builds on the local 'master' branch", this caused
"git checkout" to report:
Your branch is based on 'master', but the upstream is gone.
The upstream is our repository and is definitely not gone, so this
output is nonsense.
The fix is fairly obvious; just like the branch name is DWIMed when
"git pull" merges from the 'master' branch without complaint on such
a branch, the name of the branch the current branch builds upon
needs to be DWIMed the same way.
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2014-10-15 05:42:04 +08:00
|
|
|
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 12:45:09 +08: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;
|
|
|
|
}
|
|
|
|
|
2021-11-18 08:53:24 +08:00
|
|
|
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 12:45:13 +08:00
|
|
|
|
2021-03-14 00:17:22 +08:00
|
|
|
CALLOC_ARRAY(ret->merge, ret->merge_nr);
|
checkout: report upstream correctly even with loosely defined branch.*.merge
When checking out a branch that is set to build on top of another
branch (often, a remote-tracking branch), "git checkout" reports how
your work relates to the other branch, e.g.
Your branch is behind 'origin/master', and can be fast-forwarded.
Back when this feature was introduced, this was only done for
branches that build on remote-tracking branches, but 5e6e2b48 (Make
local branches behave like remote branches when --tracked,
2009-04-01) added support to give the same report for branches that
build on other local branches (i.e. branches whose branch.*.remote
variables are set to '.'). Unlike the support for the branches
building on remote-tracking branches, however, this did not take
into account the fact that branch.*.merge configuration is allowed
to record a shortened branch name.
When branch.*.merge is set to 'master' (not 'refs/heads/master'),
i.e. "my branch builds on the local 'master' branch", this caused
"git checkout" to report:
Your branch is based on 'master', but the upstream is gone.
The upstream is our repository and is definitely not gone, so this
output is nonsense.
The fix is fairly obvious; just like the branch name is DWIMed when
"git pull" merges from the 'master' branch without complaint on such
a branch, the name of the branch the current branch builds upon
needs to be DWIMed the same way.
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2014-10-15 05:42:04 +08:00
|
|
|
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 12:45:13 +08:00
|
|
|
if (!remote_find_tracking(remote, ret->merge[i]) ||
|
checkout: report upstream correctly even with loosely defined branch.*.merge
When checking out a branch that is set to build on top of another
branch (often, a remote-tracking branch), "git checkout" reports how
your work relates to the other branch, e.g.
Your branch is behind 'origin/master', and can be fast-forwarded.
Back when this feature was introduced, this was only done for
branches that build on remote-tracking branches, but 5e6e2b48 (Make
local branches behave like remote branches when --tracked,
2009-04-01) added support to give the same report for branches that
build on other local branches (i.e. branches whose branch.*.remote
variables are set to '.'). Unlike the support for the branches
building on remote-tracking branches, however, this did not take
into account the fact that branch.*.merge configuration is allowed
to record a shortened branch name.
When branch.*.merge is set to 'master' (not 'refs/heads/master'),
i.e. "my branch builds on the local 'master' branch", this caused
"git checkout" to report:
Your branch is based on 'master', but the upstream is gone.
The upstream is our repository and is definitely not gone, so this
output is nonsense.
The fix is fairly obvious; just like the branch name is DWIMed when
"git pull" merges from the 'master' branch without complaint on such
a branch, the name of the branch the current branch builds upon
needs to be DWIMed the same way.
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2014-10-15 05:42:04 +08:00
|
|
|
strcmp(ret->remote_name, "."))
|
|
|
|
continue;
|
2023-03-28 21:58:54 +08:00
|
|
|
if (repo_dwim_ref(the_repository, ret->merge_name[i],
|
|
|
|
strlen(ret->merge_name[i]), &oid, &ref,
|
|
|
|
0) == 1)
|
checkout: report upstream correctly even with loosely defined branch.*.merge
When checking out a branch that is set to build on top of another
branch (often, a remote-tracking branch), "git checkout" reports how
your work relates to the other branch, e.g.
Your branch is behind 'origin/master', and can be fast-forwarded.
Back when this feature was introduced, this was only done for
branches that build on remote-tracking branches, but 5e6e2b48 (Make
local branches behave like remote branches when --tracked,
2009-04-01) added support to give the same report for branches that
build on other local branches (i.e. branches whose branch.*.remote
variables are set to '.'). Unlike the support for the branches
building on remote-tracking branches, however, this did not take
into account the fact that branch.*.merge configuration is allowed
to record a shortened branch name.
When branch.*.merge is set to 'master' (not 'refs/heads/master'),
i.e. "my branch builds on the local 'master' branch", this caused
"git checkout" to report:
Your branch is based on 'master', but the upstream is gone.
The upstream is our repository and is definitely not gone, so this
output is nonsense.
The fix is fairly obvious; just like the branch name is DWIMed when
"git pull" merges from the 'master' branch without complaint on such
a branch, the name of the branch the current branch builds upon
needs to be DWIMed the same way.
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2014-10-15 05:42:04 +08:00
|
|
|
ret->merge[i]->dst = ref;
|
|
|
|
else
|
|
|
|
ret->merge[i]->dst = xstrdup(ret->merge_name[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-09-11 11:02:56 +08:00
|
|
|
struct branch *branch_get(const char *name)
|
|
|
|
{
|
|
|
|
struct branch *ret;
|
|
|
|
|
2021-11-18 08:53:23 +08:00
|
|
|
read_config(the_repository);
|
2007-09-11 11:02:56 +08:00
|
|
|
if (!name || !*name || !strcmp(name, "HEAD"))
|
2021-11-18 08:53:22 +08:00
|
|
|
ret = the_repository->remote_state->current_branch;
|
2007-09-11 11:02:56 +08:00
|
|
|
else
|
2021-11-18 08:53:23 +08:00
|
|
|
ret = make_branch(the_repository->remote_state, name,
|
|
|
|
strlen(name));
|
2021-11-18 08:53:24 +08:00
|
|
|
set_merge(the_repository->remote_state, ret);
|
2007-09-11 11:02:56 +08:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
int branch_has_merge_config(struct branch *branch)
|
|
|
|
{
|
|
|
|
return branch && !!branch->merge;
|
|
|
|
}
|
|
|
|
|
2007-09-18 16:54:53 +08:00
|
|
|
int branch_merge_matches(struct branch *branch,
|
|
|
|
int i,
|
|
|
|
const char *refname)
|
2007-09-11 11:02:56 +08:00
|
|
|
{
|
2007-09-18 16:54:53 +08:00
|
|
|
if (!branch || i < 0 || i >= branch->merge_nr)
|
2007-09-11 11:02:56 +08:00
|
|
|
return 0;
|
2014-01-14 11:16:07 +08:00
|
|
|
return refname_match(branch->merge[i]->src, refname);
|
2007-09-11 11:02:56 +08:00
|
|
|
}
|
2007-09-11 11:03:08 +08:00
|
|
|
|
2016-04-26 05:15:23 +08:00
|
|
|
__attribute__((format (printf,2,3)))
|
2015-05-21 12:45:32 +08:00
|
|
|
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"));
|
remote.c: untangle error logic in branch_get_upstream
The error-diagnosis logic in branch_get_upstream was copied
straight from sha1_name.c in the previous commit. However,
because we check all error cases and upfront and then later
diagnose them, the logic is a bit tangled. In particular:
- if branch->merge[0] is NULL, we may end up dereferencing
it for an error message (in practice, it should never be
NULL, so this is probably not a triggerable bug).
- We may enter the code path because branch->merge[0]->dst
is NULL, but we then start our error diagnosis by
checking whether our local branch exists. But that is
only relevant to diagnosing missing merge config, not a
missing tracking ref; our diagnosis may hide the real
problem.
Instead, let's just use a sequence of "if" blocks to check
for each error type, diagnose it, and return NULL.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2015-05-22 08:46:43 +08:00
|
|
|
|
|
|
|
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?
|
|
|
|
*/
|
2015-05-21 12:45:32 +08:00
|
|
|
if (!ref_exists(branch->refname))
|
|
|
|
return error_buf(err, _("no such branch: '%s'"),
|
|
|
|
branch->name);
|
remote.c: untangle error logic in branch_get_upstream
The error-diagnosis logic in branch_get_upstream was copied
straight from sha1_name.c in the previous commit. However,
because we check all error cases and upfront and then later
diagnose them, the logic is a bit tangled. In particular:
- if branch->merge[0] is NULL, we may end up dereferencing
it for an error message (in practice, it should never be
NULL, so this is probably not a triggerable bug).
- We may enter the code path because branch->merge[0]->dst
is NULL, but we then start our error diagnosis by
checking whether our local branch exists. But that is
only relevant to diagnosing missing merge config, not a
missing tracking ref; our diagnosis may hide the real
problem.
Instead, let's just use a sequence of "if" blocks to check
for each error type, diagnose it, and return NULL.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2015-05-22 08:46:43 +08:00
|
|
|
return error_buf(err,
|
|
|
|
_("no upstream configured for branch '%s'"),
|
|
|
|
branch->name);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!branch->merge[0]->dst)
|
2015-05-21 12:45:32 +08:00
|
|
|
return error_buf(err,
|
|
|
|
_("upstream branch '%s' not stored as a remote-tracking branch"),
|
|
|
|
branch->merge[0]->src);
|
|
|
|
|
2015-05-21 12:45:28 +08:00
|
|
|
return branch->merge[0]->dst;
|
|
|
|
}
|
|
|
|
|
2015-05-21 12:45:36 +08:00
|
|
|
static const char *tracking_for_push_dest(struct remote *remote,
|
|
|
|
const char *refname,
|
|
|
|
struct strbuf *err)
|
|
|
|
{
|
|
|
|
char *ret;
|
|
|
|
|
2018-05-17 06:58:11 +08:00
|
|
|
ret = apply_refspecs(&remote->fetch, refname);
|
2015-05-21 12:45:36 +08:00
|
|
|
if (!ret)
|
|
|
|
return error_buf(err,
|
|
|
|
_("push destination '%s' on remote '%s' has no local tracking branch"),
|
|
|
|
refname, remote->name);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2021-11-18 08:53:24 +08:00
|
|
|
static const char *branch_get_push_1(struct remote_state *remote_state,
|
|
|
|
struct branch *branch, struct strbuf *err)
|
2015-05-21 12:45:36 +08:00
|
|
|
{
|
|
|
|
struct remote *remote;
|
|
|
|
|
2021-11-18 08:53:24 +08:00
|
|
|
remote = remotes_remote_get(
|
|
|
|
remote_state,
|
|
|
|
remotes_pushremote_for_branch(remote_state, branch, NULL));
|
2015-05-21 12:45:36 +08:00
|
|
|
if (!remote)
|
|
|
|
return error_buf(err,
|
|
|
|
_("branch '%s' has no remote for pushing"),
|
|
|
|
branch->name);
|
|
|
|
|
2018-05-17 06:58:00 +08:00
|
|
|
if (remote->push.nr) {
|
2015-05-21 12:45:36 +08:00
|
|
|
char *dst;
|
|
|
|
const char *ret;
|
|
|
|
|
2018-05-17 06:58:11 +08:00
|
|
|
dst = apply_refspecs(&remote->push, branch->refname);
|
2015-05-21 12:45:36 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-02 17:38:39 +08:00
|
|
|
BUG("unhandled push situation");
|
2015-05-21 12:45:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
const char *branch_get_push(struct branch *branch, struct strbuf *err)
|
|
|
|
{
|
2021-11-18 08:53:24 +08:00
|
|
|
read_config(the_repository);
|
2021-11-18 08:53:25 +08:00
|
|
|
die_on_missing_branch(the_repository, branch);
|
|
|
|
|
2017-01-07 09:12:15 +08:00
|
|
|
if (!branch)
|
|
|
|
return error_buf(err, _("HEAD does not point to a branch"));
|
|
|
|
|
2015-05-21 12:45:36 +08:00
|
|
|
if (!branch->push_tracking_ref)
|
2021-11-18 08:53:24 +08:00
|
|
|
branch->push_tracking_ref = branch_get_push_1(
|
|
|
|
the_repository->remote_state, branch, err);
|
2015-05-21 12:45:36 +08:00
|
|
|
return branch->push_tracking_ref;
|
|
|
|
}
|
|
|
|
|
2022-03-01 17:33:50 +08:00
|
|
|
static int ignore_symref_update(const char *refname, struct strbuf *scratch)
|
fetch: ignore wildcarded refspecs that update local symbolic refs
In a repository cloned from somewhere else, you typically have a
symbolic ref refs/remotes/origin/HEAD pointing at the 'master'
remote-tracking ref that is next to it. When fetching into such a
repository with "git fetch --mirror" from another repository that
was similarly cloned, the implied wildcard refspec refs/*:refs/*
will end up asking to update refs/remotes/origin/HEAD with the
object at refs/remotes/origin/HEAD at the remote side, while asking
to update refs/remotes/origin/master the same way. Depending on the
order the two updates happen, the latter one would find that the
value of the ref before it is updated has changed from what the code
expects.
When the user asks to update the underlying ref via the symbolic ref
explicitly without using a wildcard refspec, e.g. "git fetch $there
refs/heads/master:refs/remotes/origin/HEAD", we should still let him
do so, but when expanding wildcard refs, it will result in a more
intuitive outcome if we simply ignore local symbolic refs.
As the purpose of the symbolic ref refs/remotes/origin/HEAD is to
follow the ref it points at (e.g. refs/remotes/origin/master), its
value would change when the underlying ref is updated.
Earlier commit da3efdb (receive-pack: detect aliased updates which
can occur with symrefs, 2010-04-19) fixed a similar issue for "git
push".
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2012-12-12 05:00:52 +08:00
|
|
|
{
|
2022-03-01 17:33:50 +08:00
|
|
|
return !refs_read_symbolic_ref(get_main_ref_store(the_repository), refname, scratch);
|
fetch: ignore wildcarded refspecs that update local symbolic refs
In a repository cloned from somewhere else, you typically have a
symbolic ref refs/remotes/origin/HEAD pointing at the 'master'
remote-tracking ref that is next to it. When fetching into such a
repository with "git fetch --mirror" from another repository that
was similarly cloned, the implied wildcard refspec refs/*:refs/*
will end up asking to update refs/remotes/origin/HEAD with the
object at refs/remotes/origin/HEAD at the remote side, while asking
to update refs/remotes/origin/master the same way. Depending on the
order the two updates happen, the latter one would find that the
value of the ref before it is updated has changed from what the code
expects.
When the user asks to update the underlying ref via the symbolic ref
explicitly without using a wildcard refspec, e.g. "git fetch $there
refs/heads/master:refs/remotes/origin/HEAD", we should still let him
do so, but when expanding wildcard refs, it will result in a more
intuitive outcome if we simply ignore local symbolic refs.
As the purpose of the symbolic ref refs/remotes/origin/HEAD is to
follow the ref it points at (e.g. refs/remotes/origin/master), its
value would change when the underlying ref is updated.
Earlier commit da3efdb (receive-pack: detect aliased updates which
can occur with symrefs, 2010-04-19) fixed a similar issue for "git
push".
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2012-12-12 05:00:52 +08:00
|
|
|
}
|
|
|
|
|
2013-10-30 13:32:56 +08:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2007-10-30 09:05:40 +08:00
|
|
|
static struct ref *get_expanded_map(const struct ref *remote_refs,
|
2018-05-17 06:57:49 +08:00
|
|
|
const struct refspec_item *refspec)
|
2007-09-11 11:03:08 +08:00
|
|
|
{
|
2022-03-01 17:33:50 +08:00
|
|
|
struct strbuf scratch = STRBUF_INIT;
|
2007-10-30 09:05:40 +08:00
|
|
|
const struct ref *ref;
|
2007-09-11 11:03:08 +08:00
|
|
|
struct ref *ret = NULL;
|
|
|
|
struct ref **tail = &ret;
|
|
|
|
|
|
|
|
for (ref = remote_refs; ref; ref = ref->next) {
|
2013-10-30 13:32:57 +08:00
|
|
|
char *expn_name = NULL;
|
|
|
|
|
2022-03-01 17:33:50 +08:00
|
|
|
strbuf_reset(&scratch);
|
|
|
|
|
2007-09-11 11:03:08 +08:00
|
|
|
if (strchr(ref->name, '^'))
|
|
|
|
continue; /* a dereference item */
|
2009-03-07 14:11:34 +08:00
|
|
|
if (match_name_with_pattern(refspec->src, ref->name,
|
fetch: ignore wildcarded refspecs that update local symbolic refs
In a repository cloned from somewhere else, you typically have a
symbolic ref refs/remotes/origin/HEAD pointing at the 'master'
remote-tracking ref that is next to it. When fetching into such a
repository with "git fetch --mirror" from another repository that
was similarly cloned, the implied wildcard refspec refs/*:refs/*
will end up asking to update refs/remotes/origin/HEAD with the
object at refs/remotes/origin/HEAD at the remote side, while asking
to update refs/remotes/origin/master the same way. Depending on the
order the two updates happen, the latter one would find that the
value of the ref before it is updated has changed from what the code
expects.
When the user asks to update the underlying ref via the symbolic ref
explicitly without using a wildcard refspec, e.g. "git fetch $there
refs/heads/master:refs/remotes/origin/HEAD", we should still let him
do so, but when expanding wildcard refs, it will result in a more
intuitive outcome if we simply ignore local symbolic refs.
As the purpose of the symbolic ref refs/remotes/origin/HEAD is to
follow the ref it points at (e.g. refs/remotes/origin/master), its
value would change when the underlying ref is updated.
Earlier commit da3efdb (receive-pack: detect aliased updates which
can occur with symrefs, 2010-04-19) fixed a similar issue for "git
push".
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2012-12-12 05:00:52 +08:00
|
|
|
refspec->dst, &expn_name) &&
|
2022-03-01 17:33:50 +08:00
|
|
|
!ignore_symref_update(expn_name, &scratch)) {
|
2007-09-11 11:03:08 +08:00
|
|
|
struct ref *cpy = copy_ref(ref);
|
|
|
|
|
2009-03-07 14:11:34 +08:00
|
|
|
cpy->peer_ref = alloc_ref(expn_name);
|
2007-09-11 11:03:08 +08:00
|
|
|
if (refspec->force)
|
|
|
|
cpy->peer_ref->force = 1;
|
|
|
|
*tail = cpy;
|
|
|
|
tail = &cpy->next;
|
|
|
|
}
|
2013-10-30 13:32:57 +08:00
|
|
|
free(expn_name);
|
2007-09-11 11:03:08 +08:00
|
|
|
}
|
|
|
|
|
2022-03-01 17:33:50 +08:00
|
|
|
strbuf_release(&scratch);
|
2007-09-11 11:03:08 +08:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2007-10-30 09:05:40 +08:00
|
|
|
static const struct ref *find_ref_by_name_abbrev(const struct ref *refs, const char *name)
|
2007-09-11 11:03:08 +08:00
|
|
|
{
|
2007-10-30 09:05:40 +08:00
|
|
|
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-02 00:22:37 +08:00
|
|
|
const struct ref *best_match = NULL;
|
|
|
|
int best_score = 0;
|
|
|
|
|
2007-09-11 11:03:08 +08:00
|
|
|
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-02 00:22:37 +08:00
|
|
|
int score = refname_match(name, ref->name);
|
|
|
|
|
|
|
|
if (best_score < score) {
|
|
|
|
best_match = ref;
|
|
|
|
best_score = score;
|
|
|
|
}
|
2007-09-11 11:03:08 +08:00
|
|
|
}
|
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-02 00:22:37 +08:00
|
|
|
return best_match;
|
2007-09-11 11:03:08 +08:00
|
|
|
}
|
|
|
|
|
2007-10-30 09:05:40 +08:00
|
|
|
struct ref *get_remote_ref(const struct ref *remote_refs, const char *name)
|
2007-09-11 11:03:08 +08:00
|
|
|
{
|
2007-10-30 09:05:40 +08:00
|
|
|
const struct ref *ref = find_ref_by_name_abbrev(remote_refs, name);
|
2007-09-11 11:03:08 +08:00
|
|
|
|
|
|
|
if (!ref)
|
2007-10-27 14:09:48 +08:00
|
|
|
return NULL;
|
2007-09-11 11:03:08 +08:00
|
|
|
|
|
|
|
return copy_ref(ref);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct ref *get_local_ref(const char *name)
|
|
|
|
{
|
2009-06-17 21:38:36 +08:00
|
|
|
if (!name || name[0] == '\0')
|
2007-09-11 11:03:08 +08:00
|
|
|
return NULL;
|
|
|
|
|
2013-12-01 04:55:40 +08:00
|
|
|
if (starts_with(name, "refs/"))
|
2008-10-18 16:44:18 +08:00
|
|
|
return alloc_ref(name);
|
2007-09-11 11:03:08 +08:00
|
|
|
|
2013-12-01 04:55:40 +08:00
|
|
|
if (starts_with(name, "heads/") ||
|
|
|
|
starts_with(name, "tags/") ||
|
|
|
|
starts_with(name, "remotes/"))
|
2008-10-18 16:37:40 +08:00
|
|
|
return alloc_ref_with_prefix("refs/", 5, name);
|
2007-09-11 11:03:08 +08:00
|
|
|
|
2008-10-18 16:37:40 +08:00
|
|
|
return alloc_ref_with_prefix("refs/heads/", 11, name);
|
2007-09-11 11:03:08 +08:00
|
|
|
}
|
|
|
|
|
2007-10-30 09:05:40 +08:00
|
|
|
int get_fetch_map(const struct ref *remote_refs,
|
2018-05-17 06:57:49 +08:00
|
|
|
const struct refspec_item *refspec,
|
2007-10-27 14:09:48 +08:00
|
|
|
struct ref ***tail,
|
|
|
|
int missing_ok)
|
2007-09-11 11:03:08 +08:00
|
|
|
{
|
2008-03-18 10:05:23 +08:00
|
|
|
struct ref *ref_map, **rmp;
|
2007-09-11 11:03:08 +08:00
|
|
|
|
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-10-01 05:25:29 +08:00
|
|
|
if (refspec->negative)
|
|
|
|
return 0;
|
|
|
|
|
2007-09-11 11:03:08 +08:00
|
|
|
if (refspec->pattern) {
|
|
|
|
ref_map = get_expanded_map(remote_refs, refspec);
|
|
|
|
} else {
|
2007-10-27 14:09:48 +08:00
|
|
|
const char *name = refspec->src[0] ? refspec->src : "HEAD";
|
|
|
|
|
2013-01-30 06:02:15 +08:00
|
|
|
if (refspec->exact_sha1) {
|
|
|
|
ref_map = alloc_ref(name);
|
2015-11-10 10:22:20 +08:00
|
|
|
get_oid_hex(name, &ref_map->old_oid);
|
2018-06-28 06:30:23 +08:00
|
|
|
ref_map->exact_oid = 1;
|
2013-01-30 06:02:15 +08:00
|
|
|
} else {
|
|
|
|
ref_map = get_remote_ref(remote_refs, name);
|
|
|
|
}
|
2007-10-27 14:09:48 +08:00
|
|
|
if (!missing_ok && !ref_map)
|
2018-11-10 13:16:09 +08:00
|
|
|
die(_("couldn't find remote ref %s"), name);
|
2007-10-27 14:09:48 +08:00
|
|
|
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;
|
|
|
|
}
|
2007-09-11 11:03:08 +08:00
|
|
|
}
|
|
|
|
|
2008-03-18 10:05:23 +08:00
|
|
|
for (rmp = &ref_map; *rmp; ) {
|
|
|
|
if ((*rmp)->peer_ref) {
|
2013-12-01 04:55:40 +08:00
|
|
|
if (!starts_with((*rmp)->peer_ref->name, "refs/") ||
|
2012-05-05 06:35:18 +08:00
|
|
|
check_refname_format((*rmp)->peer_ref->name, 0)) {
|
2008-03-18 10:05:23 +08:00
|
|
|
struct ref *ignore = *rmp;
|
2018-11-10 13:16:09 +08:00
|
|
|
error(_("* Ignoring funny ref '%s' locally"),
|
2008-03-18 10:05:23 +08:00
|
|
|
(*rmp)->peer_ref->name);
|
|
|
|
*rmp = (*rmp)->next;
|
|
|
|
free(ignore->peer_ref);
|
|
|
|
free(ignore);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
rmp = &((*rmp)->next);
|
2007-09-11 11:03:08 +08:00
|
|
|
}
|
|
|
|
|
2007-10-13 04:40:04 +08:00
|
|
|
if (ref_map)
|
|
|
|
tail_link_ref(ref_map, tail);
|
2007-09-11 11:03:08 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2008-04-27 03:53:12 +08:00
|
|
|
|
|
|
|
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)) {
|
2015-11-10 10:22:20 +08:00
|
|
|
oidcpy(&ref->old_oid, &list->old_oid);
|
2008-04-27 03:53:12 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
2008-07-02 15:51:18 +08:00
|
|
|
|
|
|
|
/*
|
2019-04-16 20:16:46 +08:00
|
|
|
* Compute the commit ahead/behind values for the pair branch_name, base.
|
2018-01-10 02:50:15 +08:00
|
|
|
*
|
|
|
|
* 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).
|
|
|
|
*
|
2019-04-16 20:16:46 +08:00
|
|
|
* 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.
|
2008-07-02 15:51:18 +08:00
|
|
|
*/
|
2019-04-16 20:16:46 +08:00
|
|
|
|
|
|
|
static int stat_branch_pair(const char *branch_name, const char *base,
|
|
|
|
int *num_ours, int *num_theirs,
|
|
|
|
enum ahead_behind_flags abf)
|
2008-07-02 15:51:18 +08:00
|
|
|
{
|
2015-11-10 10:22:30 +08:00
|
|
|
struct object_id oid;
|
2008-07-02 15:51:18 +08:00
|
|
|
struct commit *ours, *theirs;
|
|
|
|
struct rev_info revs;
|
2022-08-02 23:33:16 +08:00
|
|
|
struct setup_revision_opt opt = {
|
|
|
|
.free_removed_argv_elements = 1,
|
|
|
|
};
|
2020-07-29 04:25:12 +08:00
|
|
|
struct strvec argv = STRVEC_INIT;
|
2008-07-02 15:51:18 +08:00
|
|
|
|
branch: report invalid tracking branch as gone
Command "git branch -vv" will report tracking branches, but invalid
tracking branches are also reported. This is because the function
stat_tracking_info() can not distinguish invalid tracking branch
from other cases which it would not like to report, such as
there is no upstream settings at all, or nothing is changed between
one branch and its upstream.
Junio suggested missing upstream should be reported [1] like:
$ git branch -v -v
master e67ac84 initial
* topic 3fc0f2a [topicbase: gone] topic
$ git status
# On branch topic
# Your branch is based on 'topicbase', but the upstream is gone.
# (use "git branch --unset-upstream" to fixup)
...
$ git status -b -s
## topic...topicbase [gone]
...
In order to do like that, we need to distinguish these three cases
(i.e. no tracking, with configured but no longer valid tracking, and
with tracking) in function stat_tracking_info(). So the refactored
function stat_tracking_info() has three return values: -1 (with "gone"
base), 0 (no base), and 1 (with base).
If the caller does not like to report tracking info when nothing
changed between the branch and its upstream, simply checks if
num_theirs and num_ours are both 0.
[1]: http://thread.gmane.org/gmane.comp.version-control.git/231830/focus=232288
Suggested-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Jiang Xin <worldhello.net@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-08-26 15:02:48 +08:00
|
|
|
/* Cannot stat if what we used to build on no longer exists */
|
2017-10-16 06:06:56 +08:00
|
|
|
if (read_ref(base, &oid))
|
branch: report invalid tracking branch as gone
Command "git branch -vv" will report tracking branches, but invalid
tracking branches are also reported. This is because the function
stat_tracking_info() can not distinguish invalid tracking branch
from other cases which it would not like to report, such as
there is no upstream settings at all, or nothing is changed between
one branch and its upstream.
Junio suggested missing upstream should be reported [1] like:
$ git branch -v -v
master e67ac84 initial
* topic 3fc0f2a [topicbase: gone] topic
$ git status
# On branch topic
# Your branch is based on 'topicbase', but the upstream is gone.
# (use "git branch --unset-upstream" to fixup)
...
$ git status -b -s
## topic...topicbase [gone]
...
In order to do like that, we need to distinguish these three cases
(i.e. no tracking, with configured but no longer valid tracking, and
with tracking) in function stat_tracking_info(). So the refactored
function stat_tracking_info() has three return values: -1 (with "gone"
base), 0 (no base), and 1 (with base).
If the caller does not like to report tracking info when nothing
changed between the branch and its upstream, simply checks if
num_theirs and num_ours are both 0.
[1]: http://thread.gmane.org/gmane.comp.version-control.git/231830/focus=232288
Suggested-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Jiang Xin <worldhello.net@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-08-26 15:02:48 +08:00
|
|
|
return -1;
|
2018-06-29 09:21:58 +08:00
|
|
|
theirs = lookup_commit_reference(the_repository, &oid);
|
2008-07-02 15:51:18 +08:00
|
|
|
if (!theirs)
|
branch: report invalid tracking branch as gone
Command "git branch -vv" will report tracking branches, but invalid
tracking branches are also reported. This is because the function
stat_tracking_info() can not distinguish invalid tracking branch
from other cases which it would not like to report, such as
there is no upstream settings at all, or nothing is changed between
one branch and its upstream.
Junio suggested missing upstream should be reported [1] like:
$ git branch -v -v
master e67ac84 initial
* topic 3fc0f2a [topicbase: gone] topic
$ git status
# On branch topic
# Your branch is based on 'topicbase', but the upstream is gone.
# (use "git branch --unset-upstream" to fixup)
...
$ git status -b -s
## topic...topicbase [gone]
...
In order to do like that, we need to distinguish these three cases
(i.e. no tracking, with configured but no longer valid tracking, and
with tracking) in function stat_tracking_info(). So the refactored
function stat_tracking_info() has three return values: -1 (with "gone"
base), 0 (no base), and 1 (with base).
If the caller does not like to report tracking info when nothing
changed between the branch and its upstream, simply checks if
num_theirs and num_ours are both 0.
[1]: http://thread.gmane.org/gmane.comp.version-control.git/231830/focus=232288
Suggested-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Jiang Xin <worldhello.net@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-08-26 15:02:48 +08:00
|
|
|
return -1;
|
2008-07-02 15:51:18 +08:00
|
|
|
|
2019-04-16 20:16:46 +08:00
|
|
|
if (read_ref(branch_name, &oid))
|
branch: report invalid tracking branch as gone
Command "git branch -vv" will report tracking branches, but invalid
tracking branches are also reported. This is because the function
stat_tracking_info() can not distinguish invalid tracking branch
from other cases which it would not like to report, such as
there is no upstream settings at all, or nothing is changed between
one branch and its upstream.
Junio suggested missing upstream should be reported [1] like:
$ git branch -v -v
master e67ac84 initial
* topic 3fc0f2a [topicbase: gone] topic
$ git status
# On branch topic
# Your branch is based on 'topicbase', but the upstream is gone.
# (use "git branch --unset-upstream" to fixup)
...
$ git status -b -s
## topic...topicbase [gone]
...
In order to do like that, we need to distinguish these three cases
(i.e. no tracking, with configured but no longer valid tracking, and
with tracking) in function stat_tracking_info(). So the refactored
function stat_tracking_info() has three return values: -1 (with "gone"
base), 0 (no base), and 1 (with base).
If the caller does not like to report tracking info when nothing
changed between the branch and its upstream, simply checks if
num_theirs and num_ours are both 0.
[1]: http://thread.gmane.org/gmane.comp.version-control.git/231830/focus=232288
Suggested-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Jiang Xin <worldhello.net@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-08-26 15:02:48 +08:00
|
|
|
return -1;
|
2018-06-29 09:21:58 +08:00
|
|
|
ours = lookup_commit_reference(the_repository, &oid);
|
2008-07-02 15:51:18 +08:00
|
|
|
if (!ours)
|
branch: report invalid tracking branch as gone
Command "git branch -vv" will report tracking branches, but invalid
tracking branches are also reported. This is because the function
stat_tracking_info() can not distinguish invalid tracking branch
from other cases which it would not like to report, such as
there is no upstream settings at all, or nothing is changed between
one branch and its upstream.
Junio suggested missing upstream should be reported [1] like:
$ git branch -v -v
master e67ac84 initial
* topic 3fc0f2a [topicbase: gone] topic
$ git status
# On branch topic
# Your branch is based on 'topicbase', but the upstream is gone.
# (use "git branch --unset-upstream" to fixup)
...
$ git status -b -s
## topic...topicbase [gone]
...
In order to do like that, we need to distinguish these three cases
(i.e. no tracking, with configured but no longer valid tracking, and
with tracking) in function stat_tracking_info(). So the refactored
function stat_tracking_info() has three return values: -1 (with "gone"
base), 0 (no base), and 1 (with base).
If the caller does not like to report tracking info when nothing
changed between the branch and its upstream, simply checks if
num_theirs and num_ours are both 0.
[1]: http://thread.gmane.org/gmane.comp.version-control.git/231830/focus=232288
Suggested-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Jiang Xin <worldhello.net@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-08-26 15:02:48 +08:00
|
|
|
return -1;
|
2008-07-02 15:51:18 +08:00
|
|
|
|
2018-01-10 02:50:15 +08:00
|
|
|
*num_theirs = *num_ours = 0;
|
|
|
|
|
2008-07-02 15:51:18 +08:00
|
|
|
/* are we the same? */
|
2018-01-10 02:50:15 +08:00
|
|
|
if (theirs == ours)
|
2015-05-22 08:49:11 +08:00
|
|
|
return 0;
|
2018-01-10 02:50:15 +08:00
|
|
|
if (abf == AHEAD_BEHIND_QUICK)
|
|
|
|
return 1;
|
2018-01-10 02:50:16 +08:00
|
|
|
if (abf != AHEAD_BEHIND_FULL)
|
2019-04-16 20:16:46 +08:00
|
|
|
BUG("stat_branch_pair: invalid abf '%d'", abf);
|
2008-07-02 15:51:18 +08:00
|
|
|
|
2009-04-22 07:32:18 +08:00
|
|
|
/* Run "rev-list --left-right ours...theirs" internally... */
|
2020-07-29 04:25:12 +08:00
|
|
|
strvec_push(&argv, ""); /* ignored */
|
|
|
|
strvec_push(&argv, "--left-right");
|
|
|
|
strvec_pushf(&argv, "%s...%s",
|
strvec: fix indentation in renamed calls
Code which split an argv_array call across multiple lines, like:
argv_array_pushl(&args, "one argument",
"another argument", "and more",
NULL);
was recently mechanically renamed to use strvec, which results in
mis-matched indentation like:
strvec_pushl(&args, "one argument",
"another argument", "and more",
NULL);
Let's fix these up to align the arguments with the opening paren. I did
this manually by sifting through the results of:
git jump grep 'strvec_.*,$'
and liberally applying my editor's auto-format. Most of the changes are
of the form shown above, though I also normalized a few that had
originally used a single-tab indentation (rather than our usual style of
aligning with the open paren). I also rewrapped a couple of obvious
cases (e.g., where previously too-long lines became short enough to fit
on one), but I wasn't aggressive about it. In cases broken to three or
more lines, the grouping of arguments is sometimes meaningful, and it
wasn't worth my time or reviewer time to ponder each case individually.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-07-29 04:26:31 +08:00
|
|
|
oid_to_hex(&ours->object.oid),
|
|
|
|
oid_to_hex(&theirs->object.oid));
|
2020-07-29 04:25:12 +08:00
|
|
|
strvec_push(&argv, "--");
|
2008-07-02 15:51:18 +08:00
|
|
|
|
2018-09-21 23:57:38 +08:00
|
|
|
repo_init_revisions(the_repository, &revs, NULL);
|
2022-08-02 23:33:16 +08:00
|
|
|
setup_revisions(argv.nr, argv.v, &revs, &opt);
|
2014-08-11 05:33:26 +08:00
|
|
|
if (prepare_revision_walk(&revs))
|
2018-11-10 13:16:09 +08:00
|
|
|
die(_("revision walk setup failed"));
|
2008-07-02 15:51:18 +08:00
|
|
|
|
|
|
|
/* ... 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)++;
|
|
|
|
}
|
2008-07-04 03:09:48 +08:00
|
|
|
|
|
|
|
/* clear object flags smudged by the above traversal */
|
|
|
|
clear_commit_marks(ours, ALL_REV_FLAGS);
|
|
|
|
clear_commit_marks(theirs, ALL_REV_FLAGS);
|
2015-09-25 05:07:58 +08:00
|
|
|
|
2020-07-29 04:25:12 +08:00
|
|
|
strvec_clear(&argv);
|
2022-04-14 04:01:36 +08:00
|
|
|
release_revisions(&revs);
|
2018-01-10 02:50:15 +08:00
|
|
|
return 1;
|
2008-07-02 15:51:18 +08:00
|
|
|
}
|
|
|
|
|
2019-04-16 20:16:46 +08:00
|
|
|
/*
|
|
|
|
* 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);
|
|
|
|
}
|
|
|
|
|
2008-07-02 15:51:18 +08:00
|
|
|
/*
|
|
|
|
* Return true when there is anything to report, otherwise false.
|
|
|
|
*/
|
2018-01-10 02:50:18 +08:00
|
|
|
int format_tracking_info(struct branch *branch, struct strbuf *sb,
|
|
|
|
enum ahead_behind_flags abf)
|
2008-07-02 15:51:18 +08:00
|
|
|
{
|
2018-01-10 02:50:18 +08:00
|
|
|
int ours, theirs, sti;
|
2015-05-22 08:49:11 +08:00
|
|
|
const char *full_base;
|
2014-08-11 03:43:33 +08:00
|
|
|
char *base;
|
branch: report invalid tracking branch as gone
Command "git branch -vv" will report tracking branches, but invalid
tracking branches are also reported. This is because the function
stat_tracking_info() can not distinguish invalid tracking branch
from other cases which it would not like to report, such as
there is no upstream settings at all, or nothing is changed between
one branch and its upstream.
Junio suggested missing upstream should be reported [1] like:
$ git branch -v -v
master e67ac84 initial
* topic 3fc0f2a [topicbase: gone] topic
$ git status
# On branch topic
# Your branch is based on 'topicbase', but the upstream is gone.
# (use "git branch --unset-upstream" to fixup)
...
$ git status -b -s
## topic...topicbase [gone]
...
In order to do like that, we need to distinguish these three cases
(i.e. no tracking, with configured but no longer valid tracking, and
with tracking) in function stat_tracking_info(). So the refactored
function stat_tracking_info() has three return values: -1 (with "gone"
base), 0 (no base), and 1 (with base).
If the caller does not like to report tracking info when nothing
changed between the branch and its upstream, simply checks if
num_theirs and num_ours are both 0.
[1]: http://thread.gmane.org/gmane.comp.version-control.git/231830/focus=232288
Suggested-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Jiang Xin <worldhello.net@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-08-26 15:02:48 +08:00
|
|
|
int upstream_is_gone = 0;
|
2008-07-02 15:51:18 +08:00
|
|
|
|
2019-04-16 20:16:46 +08:00
|
|
|
sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
|
2018-01-10 02:50:18 +08:00
|
|
|
if (sti < 0) {
|
2015-05-22 08:49:11 +08:00
|
|
|
if (!full_base)
|
|
|
|
return 0;
|
branch: report invalid tracking branch as gone
Command "git branch -vv" will report tracking branches, but invalid
tracking branches are also reported. This is because the function
stat_tracking_info() can not distinguish invalid tracking branch
from other cases which it would not like to report, such as
there is no upstream settings at all, or nothing is changed between
one branch and its upstream.
Junio suggested missing upstream should be reported [1] like:
$ git branch -v -v
master e67ac84 initial
* topic 3fc0f2a [topicbase: gone] topic
$ git status
# On branch topic
# Your branch is based on 'topicbase', but the upstream is gone.
# (use "git branch --unset-upstream" to fixup)
...
$ git status -b -s
## topic...topicbase [gone]
...
In order to do like that, we need to distinguish these three cases
(i.e. no tracking, with configured but no longer valid tracking, and
with tracking) in function stat_tracking_info(). So the refactored
function stat_tracking_info() has three return values: -1 (with "gone"
base), 0 (no base), and 1 (with base).
If the caller does not like to report tracking info when nothing
changed between the branch and its upstream, simply checks if
num_theirs and num_ours are both 0.
[1]: http://thread.gmane.org/gmane.comp.version-control.git/231830/focus=232288
Suggested-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Jiang Xin <worldhello.net@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-08-26 15:02:48 +08:00
|
|
|
upstream_is_gone = 1;
|
|
|
|
}
|
2008-07-02 15:51:18 +08:00
|
|
|
|
2015-05-22 08:49:11 +08:00
|
|
|
base = shorten_unambiguous_ref(full_base, 0);
|
branch: report invalid tracking branch as gone
Command "git branch -vv" will report tracking branches, but invalid
tracking branches are also reported. This is because the function
stat_tracking_info() can not distinguish invalid tracking branch
from other cases which it would not like to report, such as
there is no upstream settings at all, or nothing is changed between
one branch and its upstream.
Junio suggested missing upstream should be reported [1] like:
$ git branch -v -v
master e67ac84 initial
* topic 3fc0f2a [topicbase: gone] topic
$ git status
# On branch topic
# Your branch is based on 'topicbase', but the upstream is gone.
# (use "git branch --unset-upstream" to fixup)
...
$ git status -b -s
## topic...topicbase [gone]
...
In order to do like that, we need to distinguish these three cases
(i.e. no tracking, with configured but no longer valid tracking, and
with tracking) in function stat_tracking_info(). So the refactored
function stat_tracking_info() has three return values: -1 (with "gone"
base), 0 (no base), and 1 (with base).
If the caller does not like to report tracking info when nothing
changed between the branch and its upstream, simply checks if
num_theirs and num_ours are both 0.
[1]: http://thread.gmane.org/gmane.comp.version-control.git/231830/focus=232288
Suggested-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Jiang Xin <worldhello.net@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-08-26 15:02:48 +08:00
|
|
|
if (upstream_is_gone) {
|
|
|
|
strbuf_addf(sb,
|
|
|
|
_("Your branch is based on '%s', but the upstream is gone.\n"),
|
|
|
|
base);
|
2021-08-23 18:44:00 +08:00
|
|
|
if (advice_enabled(ADVICE_STATUS_HINTS))
|
2016-09-16 02:31:00 +08:00
|
|
|
strbuf_addstr(sb,
|
branch: report invalid tracking branch as gone
Command "git branch -vv" will report tracking branches, but invalid
tracking branches are also reported. This is because the function
stat_tracking_info() can not distinguish invalid tracking branch
from other cases which it would not like to report, such as
there is no upstream settings at all, or nothing is changed between
one branch and its upstream.
Junio suggested missing upstream should be reported [1] like:
$ git branch -v -v
master e67ac84 initial
* topic 3fc0f2a [topicbase: gone] topic
$ git status
# On branch topic
# Your branch is based on 'topicbase', but the upstream is gone.
# (use "git branch --unset-upstream" to fixup)
...
$ git status -b -s
## topic...topicbase [gone]
...
In order to do like that, we need to distinguish these three cases
(i.e. no tracking, with configured but no longer valid tracking, and
with tracking) in function stat_tracking_info(). So the refactored
function stat_tracking_info() has three return values: -1 (with "gone"
base), 0 (no base), and 1 (with base).
If the caller does not like to report tracking info when nothing
changed between the branch and its upstream, simply checks if
num_theirs and num_ours are both 0.
[1]: http://thread.gmane.org/gmane.comp.version-control.git/231830/focus=232288
Suggested-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Jiang Xin <worldhello.net@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-08-26 15:02:48 +08:00
|
|
|
_(" (use \"git branch --unset-upstream\" to fixup)\n"));
|
2018-01-10 02:50:18 +08:00
|
|
|
} else if (!sti) {
|
2013-08-26 15:02:49 +08:00
|
|
|
strbuf_addf(sb,
|
2017-08-24 01:49:35 +08:00
|
|
|
_("Your branch is up to date with '%s'.\n"),
|
2013-08-26 15:02:49 +08:00
|
|
|
base);
|
2018-01-10 02:50:18 +08:00
|
|
|
} else if (abf == AHEAD_BEHIND_QUICK) {
|
|
|
|
strbuf_addf(sb,
|
|
|
|
_("Your branch and '%s' refer to different commits.\n"),
|
|
|
|
base);
|
2021-08-23 18:44:00 +08:00
|
|
|
if (advice_enabled(ADVICE_STATUS_HINTS))
|
2018-01-10 02:50:18 +08:00
|
|
|
strbuf_addf(sb, _(" (use \"%s\" for details)\n"),
|
|
|
|
"git status --ahead-behind");
|
branch: report invalid tracking branch as gone
Command "git branch -vv" will report tracking branches, but invalid
tracking branches are also reported. This is because the function
stat_tracking_info() can not distinguish invalid tracking branch
from other cases which it would not like to report, such as
there is no upstream settings at all, or nothing is changed between
one branch and its upstream.
Junio suggested missing upstream should be reported [1] like:
$ git branch -v -v
master e67ac84 initial
* topic 3fc0f2a [topicbase: gone] topic
$ git status
# On branch topic
# Your branch is based on 'topicbase', but the upstream is gone.
# (use "git branch --unset-upstream" to fixup)
...
$ git status -b -s
## topic...topicbase [gone]
...
In order to do like that, we need to distinguish these three cases
(i.e. no tracking, with configured but no longer valid tracking, and
with tracking) in function stat_tracking_info(). So the refactored
function stat_tracking_info() has three return values: -1 (with "gone"
base), 0 (no base), and 1 (with base).
If the caller does not like to report tracking info when nothing
changed between the branch and its upstream, simply checks if
num_theirs and num_ours are both 0.
[1]: http://thread.gmane.org/gmane.comp.version-control.git/231830/focus=232288
Suggested-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Jiang Xin <worldhello.net@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-08-26 15:02:48 +08:00
|
|
|
} else if (!theirs) {
|
2012-02-02 10:02:23 +08:00
|
|
|
strbuf_addf(sb,
|
|
|
|
Q_("Your branch is ahead of '%s' by %d commit.\n",
|
|
|
|
"Your branch is ahead of '%s' by %d commits.\n",
|
branch: report invalid tracking branch as gone
Command "git branch -vv" will report tracking branches, but invalid
tracking branches are also reported. This is because the function
stat_tracking_info() can not distinguish invalid tracking branch
from other cases which it would not like to report, such as
there is no upstream settings at all, or nothing is changed between
one branch and its upstream.
Junio suggested missing upstream should be reported [1] like:
$ git branch -v -v
master e67ac84 initial
* topic 3fc0f2a [topicbase: gone] topic
$ git status
# On branch topic
# Your branch is based on 'topicbase', but the upstream is gone.
# (use "git branch --unset-upstream" to fixup)
...
$ git status -b -s
## topic...topicbase [gone]
...
In order to do like that, we need to distinguish these three cases
(i.e. no tracking, with configured but no longer valid tracking, and
with tracking) in function stat_tracking_info(). So the refactored
function stat_tracking_info() has three return values: -1 (with "gone"
base), 0 (no base), and 1 (with base).
If the caller does not like to report tracking info when nothing
changed between the branch and its upstream, simply checks if
num_theirs and num_ours are both 0.
[1]: http://thread.gmane.org/gmane.comp.version-control.git/231830/focus=232288
Suggested-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Jiang Xin <worldhello.net@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-08-26 15:02:48 +08:00
|
|
|
ours),
|
|
|
|
base, ours);
|
2021-08-23 18:44:00 +08:00
|
|
|
if (advice_enabled(ADVICE_STATUS_HINTS))
|
2016-09-16 02:31:00 +08:00
|
|
|
strbuf_addstr(sb,
|
2012-12-03 14:16:57 +08:00
|
|
|
_(" (use \"git push\" to publish your local commits)\n"));
|
branch: report invalid tracking branch as gone
Command "git branch -vv" will report tracking branches, but invalid
tracking branches are also reported. This is because the function
stat_tracking_info() can not distinguish invalid tracking branch
from other cases which it would not like to report, such as
there is no upstream settings at all, or nothing is changed between
one branch and its upstream.
Junio suggested missing upstream should be reported [1] like:
$ git branch -v -v
master e67ac84 initial
* topic 3fc0f2a [topicbase: gone] topic
$ git status
# On branch topic
# Your branch is based on 'topicbase', but the upstream is gone.
# (use "git branch --unset-upstream" to fixup)
...
$ git status -b -s
## topic...topicbase [gone]
...
In order to do like that, we need to distinguish these three cases
(i.e. no tracking, with configured but no longer valid tracking, and
with tracking) in function stat_tracking_info(). So the refactored
function stat_tracking_info() has three return values: -1 (with "gone"
base), 0 (no base), and 1 (with base).
If the caller does not like to report tracking info when nothing
changed between the branch and its upstream, simply checks if
num_theirs and num_ours are both 0.
[1]: http://thread.gmane.org/gmane.comp.version-control.git/231830/focus=232288
Suggested-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Jiang Xin <worldhello.net@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-08-26 15:02:48 +08:00
|
|
|
} else if (!ours) {
|
2012-02-02 10:02:23 +08:00
|
|
|
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",
|
branch: report invalid tracking branch as gone
Command "git branch -vv" will report tracking branches, but invalid
tracking branches are also reported. This is because the function
stat_tracking_info() can not distinguish invalid tracking branch
from other cases which it would not like to report, such as
there is no upstream settings at all, or nothing is changed between
one branch and its upstream.
Junio suggested missing upstream should be reported [1] like:
$ git branch -v -v
master e67ac84 initial
* topic 3fc0f2a [topicbase: gone] topic
$ git status
# On branch topic
# Your branch is based on 'topicbase', but the upstream is gone.
# (use "git branch --unset-upstream" to fixup)
...
$ git status -b -s
## topic...topicbase [gone]
...
In order to do like that, we need to distinguish these three cases
(i.e. no tracking, with configured but no longer valid tracking, and
with tracking) in function stat_tracking_info(). So the refactored
function stat_tracking_info() has three return values: -1 (with "gone"
base), 0 (no base), and 1 (with base).
If the caller does not like to report tracking info when nothing
changed between the branch and its upstream, simply checks if
num_theirs and num_ours are both 0.
[1]: http://thread.gmane.org/gmane.comp.version-control.git/231830/focus=232288
Suggested-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Jiang Xin <worldhello.net@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-08-26 15:02:48 +08:00
|
|
|
theirs),
|
|
|
|
base, theirs);
|
2021-08-23 18:44:00 +08:00
|
|
|
if (advice_enabled(ADVICE_STATUS_HINTS))
|
2016-09-16 02:31:00 +08:00
|
|
|
strbuf_addstr(sb,
|
2012-12-03 14:16:57 +08:00
|
|
|
_(" (use \"git pull\" to update your local branch)\n"));
|
2012-11-15 18:45:00 +08:00
|
|
|
} else {
|
2012-02-02 10:02:23 +08:00
|
|
|
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",
|
2016-05-03 08:12:30 +08:00
|
|
|
ours + theirs),
|
branch: report invalid tracking branch as gone
Command "git branch -vv" will report tracking branches, but invalid
tracking branches are also reported. This is because the function
stat_tracking_info() can not distinguish invalid tracking branch
from other cases which it would not like to report, such as
there is no upstream settings at all, or nothing is changed between
one branch and its upstream.
Junio suggested missing upstream should be reported [1] like:
$ git branch -v -v
master e67ac84 initial
* topic 3fc0f2a [topicbase: gone] topic
$ git status
# On branch topic
# Your branch is based on 'topicbase', but the upstream is gone.
# (use "git branch --unset-upstream" to fixup)
...
$ git status -b -s
## topic...topicbase [gone]
...
In order to do like that, we need to distinguish these three cases
(i.e. no tracking, with configured but no longer valid tracking, and
with tracking) in function stat_tracking_info(). So the refactored
function stat_tracking_info() has three return values: -1 (with "gone"
base), 0 (no base), and 1 (with base).
If the caller does not like to report tracking info when nothing
changed between the branch and its upstream, simply checks if
num_theirs and num_ours are both 0.
[1]: http://thread.gmane.org/gmane.comp.version-control.git/231830/focus=232288
Suggested-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Jiang Xin <worldhello.net@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-08-26 15:02:48 +08:00
|
|
|
base, ours, theirs);
|
2021-08-23 18:44:00 +08:00
|
|
|
if (advice_enabled(ADVICE_STATUS_HINTS))
|
2016-09-16 02:31:00 +08:00
|
|
|
strbuf_addstr(sb,
|
2012-12-03 14:16:57 +08:00
|
|
|
_(" (use \"git pull\" to merge the remote branch into yours)\n"));
|
2012-11-15 18:45:00 +08:00
|
|
|
}
|
2014-08-11 03:43:33 +08:00
|
|
|
free(base);
|
2008-07-02 15:51:18 +08:00
|
|
|
return 1;
|
|
|
|
}
|
2009-02-25 16:32:11 +08:00
|
|
|
|
2015-05-26 02:39:01 +08:00
|
|
|
static int one_local_ref(const char *refname, const struct object_id *oid,
|
2022-08-26 01:09:48 +08:00
|
|
|
int flag UNUSED,
|
2022-08-19 18:08:32 +08:00
|
|
|
void *cb_data)
|
2009-02-25 16:32:11 +08:00
|
|
|
{
|
|
|
|
struct ref ***local_tail = cb_data;
|
|
|
|
struct ref *ref;
|
|
|
|
|
|
|
|
/* we already know it starts with refs/ to get here */
|
2011-09-16 05:10:25 +08:00
|
|
|
if (check_refname_format(refname + 5, 0))
|
2009-02-25 16:32:11 +08:00
|
|
|
return 0;
|
|
|
|
|
2016-02-23 06:44:32 +08:00
|
|
|
ref = alloc_ref(refname);
|
2015-11-10 10:22:20 +08:00
|
|
|
oidcpy(&ref->new_oid, oid);
|
2009-02-25 16:32:11 +08:00
|
|
|
**local_tail = ref;
|
|
|
|
*local_tail = &ref->next;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct ref *get_local_heads(void)
|
|
|
|
{
|
2009-04-17 06:16:23 +08:00
|
|
|
struct ref *local_refs = NULL, **local_tail = &local_refs;
|
2015-05-26 02:38:28 +08:00
|
|
|
|
2009-02-25 16:32:11 +08:00
|
|
|
for_each_ref(one_local_ref, &local_tail);
|
|
|
|
return local_refs;
|
|
|
|
}
|
2009-02-25 16:32:13 +08:00
|
|
|
|
2009-02-28 03:10:05 +08:00
|
|
|
struct ref *guess_remote_head(const struct ref *head,
|
|
|
|
const struct ref *refs,
|
|
|
|
int all)
|
2009-02-25 16:32:13 +08:00
|
|
|
{
|
|
|
|
const struct ref *r;
|
2009-02-28 03:10:05 +08:00
|
|
|
struct ref *list = NULL;
|
|
|
|
struct ref **tail = &list;
|
2009-02-25 16:32:13 +08:00
|
|
|
|
2009-02-25 16:32:14 +08:00
|
|
|
if (!head)
|
2009-02-25 16:32:13 +08:00
|
|
|
return NULL;
|
|
|
|
|
2009-02-28 03:10:06 +08:00
|
|
|
/*
|
|
|
|
* 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));
|
|
|
|
|
2020-06-24 22:46:35 +08:00
|
|
|
/* If a remote branch exists with the default branch name, let's use it. */
|
2009-02-28 03:10:05 +08:00
|
|
|
if (!all) {
|
2020-12-11 19:36:56 +08:00
|
|
|
char *ref = xstrfmt("refs/heads/%s",
|
|
|
|
git_default_branch_name(0));
|
2020-06-24 22:46:35 +08:00
|
|
|
|
|
|
|
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 */
|
2009-02-28 03:10:05 +08:00
|
|
|
r = find_ref_by_name(refs, "refs/heads/master");
|
convert "oidcmp() == 0" to oideq()
Using the more restrictive oideq() should, in the long run,
give the compiler more opportunities to optimize these
callsites. For now, this conversion should be a complete
noop with respect to the generated code.
The result is also perhaps a little more readable, as it
avoids the "zero is equal" idiom. Since it's so prevalent in
C, I think seasoned programmers tend not to even notice it
anymore, but it can sometimes make for awkward double
negations (e.g., we can drop a few !!oidcmp() instances
here).
This patch was generated almost entirely by the included
coccinelle patch. This mechanical conversion should be
completely safe, because we check explicitly for cases where
oidcmp() is compared to 0, which is what oideq() is doing
under the hood. Note that we don't have to catch "!oidcmp()"
separately; coccinelle's standard isomorphisms make sure the
two are treated equivalently.
I say "almost" because I did hand-edit the coccinelle output
to fix up a few style violations (it mostly keeps the
original formatting, but sometimes unwraps long lines).
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-08-29 05:22:40 +08:00
|
|
|
if (r && oideq(&r->old_oid, &head->old_oid))
|
2009-02-28 03:10:05 +08:00
|
|
|
return copy_ref(r);
|
|
|
|
}
|
2009-02-25 16:32:13 +08:00
|
|
|
|
|
|
|
/* Look for another ref that points there */
|
2009-02-28 03:10:05 +08:00
|
|
|
for (r = refs; r; r = r->next) {
|
2011-06-03 13:11:13 +08:00
|
|
|
if (r != head &&
|
2013-12-01 04:55:40 +08:00
|
|
|
starts_with(r->name, "refs/heads/") &&
|
convert "oidcmp() == 0" to oideq()
Using the more restrictive oideq() should, in the long run,
give the compiler more opportunities to optimize these
callsites. For now, this conversion should be a complete
noop with respect to the generated code.
The result is also perhaps a little more readable, as it
avoids the "zero is equal" idiom. Since it's so prevalent in
C, I think seasoned programmers tend not to even notice it
anymore, but it can sometimes make for awkward double
negations (e.g., we can drop a few !!oidcmp() instances
here).
This patch was generated almost entirely by the included
coccinelle patch. This mechanical conversion should be
completely safe, because we check explicitly for cases where
oidcmp() is compared to 0, which is what oideq() is doing
under the hood. Note that we don't have to catch "!oidcmp()"
separately; coccinelle's standard isomorphisms make sure the
two are treated equivalently.
I say "almost" because I did hand-edit the coccinelle output
to fix up a few style violations (it mostly keeps the
original formatting, but sometimes unwraps long lines).
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-08-29 05:22:40 +08:00
|
|
|
oideq(&r->old_oid, &head->old_oid)) {
|
2009-02-28 03:10:05 +08:00
|
|
|
*tail = copy_ref(r);
|
|
|
|
tail = &((*tail)->next);
|
|
|
|
if (!all)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2009-02-25 16:32:13 +08:00
|
|
|
|
2009-02-28 03:10:05 +08:00
|
|
|
return list;
|
2009-02-25 16:32:13 +08:00
|
|
|
}
|
2009-11-10 13:03:31 +08:00
|
|
|
|
|
|
|
struct stale_heads_info {
|
|
|
|
struct string_list *ref_names;
|
|
|
|
struct ref **stale_refs_tail;
|
2018-05-17 06:58:10 +08:00
|
|
|
struct refspec *rs;
|
2009-11-10 13:03:31 +08:00
|
|
|
};
|
|
|
|
|
2015-05-26 02:39:01 +08:00
|
|
|
static int get_stale_heads_cb(const char *refname, const struct object_id *oid,
|
|
|
|
int flags, void *cb_data)
|
2009-11-10 13:03:31 +08:00
|
|
|
{
|
|
|
|
struct stale_heads_info *info = cb_data;
|
2014-02-27 17:00:10 +08:00
|
|
|
struct string_list matches = STRING_LIST_INIT_DUP;
|
2018-05-17 06:57:49 +08:00
|
|
|
struct refspec_item query;
|
2014-02-27 17:00:10 +08:00
|
|
|
int i, stale = 1;
|
2018-05-17 06:57:49 +08:00
|
|
|
memset(&query, 0, sizeof(struct refspec_item));
|
2011-10-15 13:04:25 +08:00
|
|
|
query.dst = (char *)refname;
|
|
|
|
|
2018-05-17 06:58:10 +08:00
|
|
|
query_refspecs_multiple(info->rs, &query, &matches);
|
2014-02-27 17:00:10 +08:00
|
|
|
if (matches.nr == 0)
|
|
|
|
goto clean_exit; /* No matches */
|
2011-10-15 13:04:25 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* 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
|
2014-02-27 17:00:10 +08:00
|
|
|
* remote, we consider it to be stale. In order to deal with
|
|
|
|
* overlapping refspecs, we need to go over all of the
|
|
|
|
* matching refs.
|
2011-10-15 13:04:25 +08:00
|
|
|
*/
|
2014-02-27 17:00:10 +08:00
|
|
|
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) {
|
2011-10-15 13:04:25 +08:00
|
|
|
struct ref *ref = make_linked_ref(refname, &info->stale_refs_tail);
|
2015-11-10 10:22:20 +08:00
|
|
|
oidcpy(&ref->new_oid, oid);
|
2009-11-10 13:03:31 +08:00
|
|
|
}
|
2011-10-15 13:04:25 +08:00
|
|
|
|
2014-02-27 17:00:10 +08:00
|
|
|
clean_exit:
|
|
|
|
string_list_clear(&matches, 0);
|
2009-11-10 13:03:31 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-05-17 06:58:10 +08:00
|
|
|
struct ref *get_stale_heads(struct refspec *rs, struct ref *fetch_map)
|
2009-11-10 13:03:31 +08:00
|
|
|
{
|
|
|
|
struct ref *ref, *stale_refs = NULL;
|
2010-07-05 03:46:19 +08:00
|
|
|
struct string_list ref_names = STRING_LIST_INIT_NODUP;
|
2009-11-10 13:03:31 +08:00
|
|
|
struct stale_heads_info info;
|
2015-05-26 02:38:28 +08:00
|
|
|
|
2009-11-10 13:03:31 +08:00
|
|
|
info.ref_names = &ref_names;
|
|
|
|
info.stale_refs_tail = &stale_refs;
|
2018-05-17 06:58:10 +08:00
|
|
|
info.rs = rs;
|
2009-11-10 13:03:31 +08:00
|
|
|
for (ref = fetch_map; ref; ref = ref->next)
|
2010-06-26 07:41:38 +08:00
|
|
|
string_list_append(&ref_names, ref->name);
|
2014-11-25 16:02:35 +08:00
|
|
|
string_list_sort(&ref_names);
|
2009-11-10 13:03:31 +08:00
|
|
|
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 06:34:36 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Compare-and-swap
|
|
|
|
*/
|
2015-01-15 06:58:44 +08:00
|
|
|
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 06:34:36 +08: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;
|
|
|
|
}
|
|
|
|
|
2017-04-01 04:20:48 +08:00
|
|
|
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 06:34:36 +08: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;
|
2016-07-27 04:44:44 +08:00
|
|
|
else if (!colon[1])
|
2017-07-14 07:49:21 +08:00
|
|
|
oidclr(&entry->expect);
|
2023-03-28 21:58:46 +08:00
|
|
|
else if (repo_get_oid(the_repository, colon + 1, &entry->expect))
|
2018-11-10 13:16:09 +08:00
|
|
|
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 06:34:36 +08: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);
|
|
|
|
}
|
2013-07-10 02:01:06 +08:00
|
|
|
|
|
|
|
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
|
2020-10-03 20:10:44 +08:00
|
|
|
* 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[].
|
2013-07-10 02:01:06 +08:00
|
|
|
* If we cannot do so, return negative to signal an error.
|
|
|
|
*/
|
|
|
|
static int remote_tracking(struct remote *remote, const char *refname,
|
2020-10-03 20:10:44 +08:00
|
|
|
struct object_id *oid, char **dst_refname)
|
2013-07-10 02:01:06 +08:00
|
|
|
{
|
|
|
|
char *dst;
|
|
|
|
|
2018-05-17 06:58:11 +08:00
|
|
|
dst = apply_refspecs(&remote->fetch, refname);
|
2013-07-10 02:01:06 +08:00
|
|
|
if (!dst)
|
|
|
|
return -1; /* no tracking ref for refname at remote */
|
2017-10-16 06:06:56 +08:00
|
|
|
if (read_ref(dst, oid))
|
2013-07-10 02:01:06 +08:00
|
|
|
return -1; /* we know what the tracking ref is but we cannot read it */
|
2020-10-03 20:10:44 +08:00
|
|
|
|
|
|
|
*dst_refname = dst;
|
2013-07-10 02:01:06 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-10-03 20:10:44 +08:00
|
|
|
/*
|
|
|
|
* 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;
|
|
|
|
};
|
|
|
|
|
2021-09-27 20:54:25 +08:00
|
|
|
#define REFLOG_COMMIT_ARRAY_INIT { 0 }
|
2020-10-03 20:10:44 +08:00
|
|
|
|
|
|
|
/* 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. */
|
2022-08-26 01:09:48 +08:00
|
|
|
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)
|
2020-10-03 20:10:44 +08:00
|
|
|
{
|
|
|
|
timestamp_t *ts = cb_data;
|
|
|
|
*ts = timestamp;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2022-08-26 01:09:48 +08:00
|
|
|
static int check_and_collect_until(struct object_id *o_oid UNUSED,
|
2020-10-03 20:10:44 +08:00
|
|
|
struct object_id *n_oid,
|
2022-08-26 01:09:48 +08:00
|
|
|
const char *ident UNUSED,
|
|
|
|
timestamp_t timestamp, int tz UNUSED,
|
|
|
|
const char *message UNUSED, void *cb_data)
|
2020-10-03 20:10:44 +08:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
|
2023-03-28 21:58:47 +08:00
|
|
|
if ((ret = repo_in_merge_bases_many(the_repository, commit, size, chunk)))
|
2020-10-03 20:10:44 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2013-07-10 02:01:06 +08:00
|
|
|
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];
|
2014-01-14 11:16:07 +08:00
|
|
|
if (!refname_match(entry->refname, ref->name))
|
2013-07-10 02:01:06 +08:00
|
|
|
continue;
|
|
|
|
ref->expect_old_sha1 = 1;
|
|
|
|
if (!entry->use_tracking)
|
2017-07-14 07:49:21 +08:00
|
|
|
oidcpy(&ref->old_oid_expect, &entry->expect);
|
2020-10-03 20:10:44 +08:00
|
|
|
else if (remote_tracking(remote, ref->name,
|
|
|
|
&ref->old_oid_expect,
|
|
|
|
&ref->tracking_ref))
|
2016-07-27 04:44:45 +08:00
|
|
|
oidclr(&ref->old_oid_expect);
|
2020-10-03 20:10:44 +08:00
|
|
|
else
|
|
|
|
ref->check_reachable = cas->use_force_if_includes;
|
2013-07-10 02:01:06 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Are we using "--<option>" to cover all? */
|
|
|
|
if (!cas->use_tracking_for_rest)
|
|
|
|
return;
|
|
|
|
|
|
|
|
ref->expect_old_sha1 = 1;
|
2020-10-03 20:10:44 +08:00
|
|
|
if (remote_tracking(remote, ref->name,
|
|
|
|
&ref->old_oid_expect,
|
|
|
|
&ref->tracking_ref))
|
2016-07-27 04:44:45 +08:00
|
|
|
oidclr(&ref->old_oid_expect);
|
2020-10-03 20:10:44 +08:00
|
|
|
else
|
|
|
|
ref->check_reachable = cas->use_force_if_includes;
|
2013-07-10 02:01:06 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void apply_push_cas(struct push_cas_option *cas,
|
|
|
|
struct remote *remote,
|
|
|
|
struct ref *remote_refs)
|
|
|
|
{
|
|
|
|
struct ref *ref;
|
2020-10-03 20:10:44 +08:00
|
|
|
for (ref = remote_refs; ref; ref = ref->next) {
|
2013-07-10 02:01:06 +08:00
|
|
|
apply_cas(cas, remote, ref);
|
2020-10-03 20:10:44 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* 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);
|
|
|
|
}
|
2013-07-10 02:01:06 +08:00
|
|
|
}
|
2021-11-18 08:53:22 +08:00
|
|
|
|
|
|
|
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);
|
2021-11-18 08:53:25 +08:00
|
|
|
hashmap_init(&r->branches_hash, branches_hash_cmp, NULL, 0);
|
2021-11-18 08:53:22 +08:00
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
void remote_state_clear(struct remote_state *remote_state)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
2022-06-07 23:50:03 +08:00
|
|
|
for (i = 0; i < remote_state->remotes_nr; i++)
|
2021-11-18 08:53:22 +08:00
|
|
|
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);
|
2021-11-18 08:53:25 +08:00
|
|
|
hashmap_clear_and_free(&remote_state->branches_hash, struct remote, ent);
|
2021-11-18 08:53:22 +08:00
|
|
|
}
|
2022-05-17 04:11:03 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* 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;
|
2022-05-17 04:11:04 +08:00
|
|
|
char *remoteurl;
|
2022-05-17 04:11:03 +08:00
|
|
|
struct strbuf sb = STRBUF_INIT;
|
2022-05-17 04:11:04 +08:00
|
|
|
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");
|
2022-05-17 04:11:03 +08:00
|
|
|
|
2022-05-17 04:11:04 +08:00
|
|
|
remoteurl = xstrdup(remote_url);
|
2022-05-17 04:11:03 +08:00
|
|
|
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.
|
|
|
|
*/
|
2022-06-16 07:35:44 +08:00
|
|
|
while (*url) {
|
2022-05-17 04:11:03 +08:00
|
|
|
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);
|
|
|
|
}
|