Skip to content

Commit

Permalink
Merge branch 'jc/push-refmap'
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Junio C Hamano committed Dec 27, 2013
2 parents 7794a68 + fc9261c commit 7cdebd8
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 28 deletions.
9 changes: 7 additions & 2 deletions Documentation/git-push.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
84 changes: 62 additions & 22 deletions builtin/push.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand Down Expand Up @@ -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);
Expand Down
8 changes: 4 additions & 4 deletions remote.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions remote.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);

Expand Down
75 changes: 75 additions & 0 deletions t/t5516-fetch-push.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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 &&
Expand Down

0 comments on commit 7cdebd8

Please sign in to comment.