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>
maint
Junio C Hamano 2013-12-03 15:41:15 -08:00
parent 50d829c11a
commit ca02465b41
5 changed files with 96 additions and 8 deletions

View File

@ -56,8 +56,13 @@ it can be any arbitrary "SHA-1 expression", such as `master~4` or
+ +
The <dst> tells which ref on the remote side is updated with this The <dst> tells which ref on the remote side is updated with this
push. Arbitrary expressions cannot be used here, an actual ref must push. Arbitrary expressions cannot be used here, an actual ref must
be named. If `:`<dst> is omitted, the same ref as <src> will be be named.
updated. If `git push [<repository>]` without any `<refspec>` argument is set to
update some ref at the destination with `<src>` with
`remote.<repository>.push` configuration variable, `:<dst>` part can
be omitted---such a push will update a ref that `<src>` normally updates
without any `<refspec>` on the command line. Otherwise, missing
`:<dst>` means to update the same ref as the `<src>`.
+ +
The object referenced by <src> is used to update the <dst> reference The object referenced by <src> is used to update the <dst> reference
on the remote side. By default this is only allowed if <dst> is not on the remote side. By default this is only allowed if <dst> is not

View File

@ -35,9 +35,38 @@ static void add_refspec(const char *ref)
refspec[refspec_nr-1] = ref; refspec[refspec_nr-1] = ref;
} }


static void set_refspecs(const char **refs, int nr) static const char *map_refspec(const char *ref,
struct remote *remote, struct ref *local_refs)
{ {
struct ref *matched = NULL;

/* Does "ref" uniquely name our ref? */
if (count_refspec_match(ref, local_refs, &matched) != 1)
return ref;

if (remote->push) {
struct refspec query;
memset(&query, 0, sizeof(struct refspec));
query.src = matched->name;
if (!query_refspecs(remote->push, remote->push_refspec_nr, &query) &&
query.dst) {
struct strbuf buf = STRBUF_INIT;
strbuf_addf(&buf, "%s%s:%s",
query.force ? "+" : "",
query.src, query.dst);
return strbuf_detach(&buf, NULL);
}
}

return ref;
}

static void set_refspecs(const char **refs, int nr, const char *repo)
{
struct remote *remote = NULL;
struct ref *local_refs = NULL;
int i; int i;

for (i = 0; i < nr; i++) { for (i = 0; i < nr; i++) {
const char *ref = refs[i]; const char *ref = refs[i];
if (!strcmp("tag", ref)) { if (!strcmp("tag", ref)) {
@ -56,6 +85,13 @@ static void set_refspecs(const char **refs, int nr)
die(_("--delete only accepts plain target ref names")); die(_("--delete only accepts plain target ref names"));
strbuf_addf(&delref, ":%s", ref); strbuf_addf(&delref, ":%s", ref);
ref = strbuf_detach(&delref, NULL); ref = strbuf_detach(&delref, NULL);
} else if (!strchr(ref, ':')) {
if (!remote) {
/* lazily grab remote and local_refs */
remote = remote_get(repo);
local_refs = get_local_heads();
}
ref = map_refspec(ref, remote, local_refs);
} }
add_refspec(ref); add_refspec(ref);
} }
@ -487,7 +523,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)


if (argc > 0) { if (argc > 0) {
repo = argv[0]; repo = argv[0];
set_refspecs(argv + 1, argc - 1); set_refspecs(argv + 1, argc - 1, repo);
} }


rc = do_push(repo, flags); rc = do_push(repo, flags);

View File

@ -821,7 +821,7 @@ static int match_name_with_pattern(const char *key, const char *name,
return ret; return ret;
} }


static int query_refspecs(struct refspec *refs, int ref_count, struct refspec *query) int query_refspecs(struct refspec *refs, int ref_count, struct refspec *query)
{ {
int i; int i;
int find_src = !query->src; int find_src = !query->src;
@ -955,9 +955,9 @@ void sort_ref_list(struct ref **l, int (*cmp)(const void *, const void *))
*l = llist_mergesort(*l, ref_list_get_next, ref_list_set_next, cmp); *l = llist_mergesort(*l, ref_list_get_next, ref_list_set_next, cmp);
} }


static int count_refspec_match(const char *pattern, int count_refspec_match(const char *pattern,
struct ref *refs, struct ref *refs,
struct ref **matched_ref) struct ref **matched_ref)
{ {
int patlen = strlen(pattern); int patlen = strlen(pattern);
struct ref *matched_weak = NULL; struct ref *matched_weak = NULL;

View File

@ -128,6 +128,7 @@ struct ref *alloc_ref(const char *name);
struct ref *copy_ref(const struct ref *ref); struct ref *copy_ref(const struct ref *ref);
struct ref *copy_ref_list(const struct ref *ref); struct ref *copy_ref_list(const struct ref *ref);
void sort_ref_list(struct ref **, int (*cmp)(const void *, const void *)); void sort_ref_list(struct ref **, int (*cmp)(const void *, const void *));
extern int count_refspec_match(const char *, struct ref *refs, struct ref **matched_ref);
int ref_compare_name(const void *, const void *); int ref_compare_name(const void *, const void *);


int check_ref_type(const struct ref *ref, int flags); int check_ref_type(const struct ref *ref, int flags);
@ -158,6 +159,7 @@ struct refspec *parse_fetch_refspec(int nr_refspec, const char **refspec);


void free_refspec(int nr_refspec, struct refspec *refspec); void free_refspec(int nr_refspec, struct refspec *refspec);


extern int query_refspecs(struct refspec *specs, int nr, struct refspec *query);
char *apply_refspecs(struct refspec *refspecs, int nr_refspec, char *apply_refspecs(struct refspec *refspecs, int nr_refspec,
const char *name); const char *name);



View File

@ -1126,6 +1126,51 @@ test_expect_success 'fetch follows tags by default' '
test_cmp expect actual test_cmp expect actual
' '


test_expect_success 'pushing a specific ref applies remote.$name.push as refmap' '
mk_test testrepo heads/master &&
rm -fr src dst &&
git init src &&
git init --bare dst &&
(
cd src &&
git pull ../testrepo master &&
git branch next &&
git config remote.dst.url ../dst &&
git config remote.dst.push "+refs/heads/*:refs/remotes/src/*" &&
git push dst master &&
git show-ref refs/heads/master |
sed -e "s|refs/heads/|refs/remotes/src/|" >../dst/expect
) &&
(
cd dst &&
test_must_fail git show-ref refs/heads/next &&
test_must_fail git show-ref refs/heads/master &&
git show-ref refs/remotes/src/master >actual
) &&
test_cmp dst/expect dst/actual
'

test_expect_success 'with no remote.$name.push, it is not used as refmap' '
mk_test testrepo heads/master &&
rm -fr src dst &&
git init src &&
git init --bare dst &&
(
cd src &&
git pull ../testrepo master &&
git branch next &&
git config remote.dst.url ../dst &&
git push dst master &&
git show-ref refs/heads/master >../dst/expect
) &&
(
cd dst &&
test_must_fail git show-ref refs/heads/next &&
git show-ref refs/heads/master >actual
) &&
test_cmp dst/expect dst/actual
'

test_expect_success 'push does not follow tags by default' ' test_expect_success 'push does not follow tags by default' '
mk_test testrepo heads/master && mk_test testrepo heads/master &&
rm -fr src dst && rm -fr src dst &&