1
0
Fork 0
mirror of https://github.com/git/git.git synced 2024-06-10 01:46:15 +02:00

Merge branch 'jc/push-refmap'

Make "git push origin master" update the same ref that would be
updated by our 'master' when "git push origin" (no refspecs) is run
while the 'master' branch is checked out, which makes "git push"
more symmetric to "git fetch" and more usable for the triangular
workflow.

* jc/push-refmap:
  push: also use "upstream" mapping when pushing a single ref
  push: use remote.$name.push as a refmap
  builtin/push.c: use strbuf instead of manual allocation
This commit is contained in:
Junio C Hamano 2013-12-27 14:57:50 -08:00
commit 7cdebd8a20
5 changed files with 150 additions and 28 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
push. Arbitrary expressions cannot be used here, an actual ref must
be named. If `:`<dst> is omitted, the same ref as <src> will be
updated.
be named.
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
on the remote side. By default this is only allowed if <dst> is not

View File

@ -35,35 +35,75 @@ static void add_refspec(const char *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);
}
}
if (push_default == PUSH_DEFAULT_UPSTREAM &&
!prefixcmp(matched->name, "refs/heads/")) {
struct branch *branch = branch_get(matched->name + 11);
if (branch->merge_nr == 1 && branch->merge[0]->src) {
struct strbuf buf = STRBUF_INIT;
strbuf_addf(&buf, "%s:%s",
ref, branch->merge[0]->src);
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;
for (i = 0; i < nr; i++) {
const char *ref = refs[i];
if (!strcmp("tag", ref)) {
char *tag;
int len;
struct strbuf tagref = STRBUF_INIT;
if (nr <= ++i)
die(_("tag shorthand without <tag>"));
len = strlen(refs[i]) + 11;
if (deleterefs) {
tag = xmalloc(len+1);
strcpy(tag, ":refs/tags/");
} else {
tag = xmalloc(len);
strcpy(tag, "refs/tags/");
ref = refs[i];
if (deleterefs)
strbuf_addf(&tagref, ":refs/tags/%s", ref);
else
strbuf_addf(&tagref, "refs/tags/%s", ref);
ref = strbuf_detach(&tagref, NULL);
} else if (deleterefs) {
struct strbuf delref = STRBUF_INIT;
if (strchr(ref, ':'))
die(_("--delete only accepts plain target ref names"));
strbuf_addf(&delref, ":%s", ref);
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();
}
strcat(tag, refs[i]);
ref = tag;
} else if (deleterefs && !strchr(ref, ':')) {
char *delref;
int len = strlen(ref)+1;
delref = xmalloc(len+1);
strcpy(delref, ":");
strcat(delref, ref);
ref = delref;
} else if (deleterefs)
die(_("--delete only accepts plain target ref names"));
ref = map_refspec(ref, remote, local_refs);
}
add_refspec(ref);
}
}
@ -501,7 +541,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
if (argc > 0) {
repo = argv[0];
set_refspecs(argv + 1, argc - 1);
set_refspecs(argv + 1, argc - 1, repo);
}
rc = do_push(repo, flags);

View File

@ -852,7 +852,7 @@ static int match_name_with_pattern(const char *key, const char *name,
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 find_src = !query->src;
@ -986,9 +986,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);
}
static int count_refspec_match(const char *pattern,
struct ref *refs,
struct ref **matched_ref)
int count_refspec_match(const char *pattern,
struct ref *refs,
struct ref **matched_ref)
{
int patlen = strlen(pattern);
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_list(const struct ref *ref);
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 check_ref_type(const struct ref *ref, int flags);
@ -162,6 +163,7 @@ struct refspec *parse_fetch_refspec(int nr_refspec, const char **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,
const char *name);

View File

@ -1126,6 +1126,81 @@ test_expect_success 'fetch follows tags by default' '
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 config push.default matching &&
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 'with no remote.$name.push, upstream mapping is used' '
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.fetch "+refs/heads/*:refs/remotes/dst/*" &&
git config push.default upstream &&
git config branch.master.merge refs/heads/trunk &&
git config branch.master.remote dst &&
git push dst master &&
git show-ref refs/heads/master |
sed -e "s|refs/heads/master|refs/heads/trunk|" >../dst/expect
) &&
(
cd dst &&
test_must_fail git show-ref refs/heads/master &&
test_must_fail git show-ref refs/heads/next &&
git show-ref refs/heads/trunk >actual
) &&
test_cmp dst/expect dst/actual
'
test_expect_success 'push does not follow tags by default' '
mk_test testrepo heads/master &&
rm -fr src dst &&