git.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/3] Teaching "git push" to map pushed refs
@ 2013-12-04  0:39 Junio C Hamano
  2013-12-04  0:39 ` [PATCH 1/3] builtin/push.c: use strbuf instead of manual allocation Junio C Hamano
                   ` (3 more replies)
  0 siblings, 4 replies; 8+ messages in thread
From: Junio C Hamano @ 2013-12-04  0:39 UTC (permalink / raw)
  To: git

Earlier, Peff taught "git fetch origin master" to update a
remote-tracking branch by internally mapping the colon-less refspec
'master' to '+refs/heads/master:refs/remotes/origin/master'.  Both

	git fetch origin
	git fetch origin master

would update the same refs/remotes/origin/master, which avoids
surprises.

However, we did not have a similar refspec mapping on the push
side.  This three-patch series does just that, and would help
triangular workflow by making these two:

	git checkout master && git push origin
	git push origin master

update the same ref at the 'origin'.

Junio C Hamano (3):
  builtin/push.c: use strbuf instead of manual allocation
  push: use remote.$name.push as a refmap
  push: also use "upstream" mapping when pushing a single ref

 Documentation/git-push.txt |  9 ++++--
 builtin/push.c             | 79 ++++++++++++++++++++++++++++++++--------------
 remote.c                   |  8 ++---
 remote.h                   |  2 ++
 t/t5516-fetch-push.sh      | 75 +++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 144 insertions(+), 29 deletions(-)

-- 
1.8.5.1-400-gbc1da41

^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH 1/3] builtin/push.c: use strbuf instead of manual allocation
  2013-12-04  0:39 [PATCH 0/3] Teaching "git push" to map pushed refs Junio C Hamano
@ 2013-12-04  0:39 ` Junio C Hamano
  2013-12-04  0:39 ` [PATCH 2/3] push: use remote.$name.push as a refmap Junio C Hamano
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 8+ messages in thread
From: Junio C Hamano @ 2013-12-04  0:39 UTC (permalink / raw)
  To: git

The command line arguments given to "git push" are massaged into
a list of refspecs in set_refspecs() function. This was implemented
using xmalloc, strcpy and friends, but it is much easier to read if
done using strbuf.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin/push.c | 35 ++++++++++++++---------------------
 1 file changed, 14 insertions(+), 21 deletions(-)

diff --git a/builtin/push.c b/builtin/push.c
index 7b1b66c..76e4400 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -41,29 +41,22 @@ static void set_refspecs(const char **refs, int nr)
 	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/");
-			}
-			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 = 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);
+		}
 		add_refspec(ref);
 	}
 }
-- 
1.8.5.1-400-gbc1da41

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH 2/3] push: use remote.$name.push as a refmap
  2013-12-04  0:39 [PATCH 0/3] Teaching "git push" to map pushed refs Junio C Hamano
  2013-12-04  0:39 ` [PATCH 1/3] builtin/push.c: use strbuf instead of manual allocation Junio C Hamano
@ 2013-12-04  0:39 ` Junio C Hamano
  2013-12-04  0:39 ` [PATCH 3/3] push: also use "upstream" mapping when pushing a single ref Junio C Hamano
  2013-12-05  1:27 ` [PATCH v2 0/3] Teaching "git push" to map pushed refs Junio C Hamano
  3 siblings, 0 replies; 8+ messages in thread
From: Junio C Hamano @ 2013-12-04  0:39 UTC (permalink / raw)
  To: git

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.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 Documentation/git-push.txt |  9 +++++++--
 builtin/push.c             | 35 ++++++++++++++++++++++++++++++++---
 remote.c                   |  8 ++++----
 remote.h                   |  2 ++
 t/t5516-fetch-push.sh      | 45 +++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 90 insertions(+), 9 deletions(-)

diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt
index 9eec740..2b7f4f9 100644
--- a/Documentation/git-push.txt
+++ b/Documentation/git-push.txt
@@ -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
diff --git a/builtin/push.c b/builtin/push.c
index 76e4400..bdc1fc1 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -35,7 +35,34 @@ 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);
+		}
+	}
+
+	return ref;
+}
+
+static void set_refspecs(const char **refs, int nr,
+			 struct remote *remote, struct ref *local_refs)
 {
 	int i;
 	for (i = 0; i < nr; i++) {
@@ -56,7 +83,8 @@ static void set_refspecs(const char **refs, int nr)
 				die(_("--delete only accepts plain target ref names"));
 			strbuf_addf(&delref, ":%s", ref);
 			ref = strbuf_detach(&delref, NULL);
-		}
+		} else if (!strchr(ref, ':'))
+			ref = map_refspec(ref, remote, local_refs);
 		add_refspec(ref);
 	}
 }
@@ -487,7 +515,8 @@ 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,
+			     remote_get(repo), get_local_heads());
 	}
 
 	rc = do_push(repo, flags);
diff --git a/remote.c b/remote.c
index 9f1a8aa..6ffa149 100644
--- a/remote.c
+++ b/remote.c
@@ -821,7 +821,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;
@@ -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);
 }
 
-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;
diff --git a/remote.h b/remote.h
index 131130a..f7d43b4 100644
--- a/remote.h
+++ b/remote.h
@@ -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);
@@ -158,6 +159,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);
 
diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh
index 99c32d7..6d7f102 100755
--- a/t/t5516-fetch-push.sh
+++ b/t/t5516-fetch-push.sh
@@ -1126,6 +1126,51 @@ 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 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' '
 	mk_test testrepo heads/master &&
 	rm -fr src dst &&
-- 
1.8.5.1-400-gbc1da41

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH 3/3] push: also use "upstream" mapping when pushing a single ref
  2013-12-04  0:39 [PATCH 0/3] Teaching "git push" to map pushed refs Junio C Hamano
  2013-12-04  0:39 ` [PATCH 1/3] builtin/push.c: use strbuf instead of manual allocation Junio C Hamano
  2013-12-04  0:39 ` [PATCH 2/3] push: use remote.$name.push as a refmap Junio C Hamano
@ 2013-12-04  0:39 ` Junio C Hamano
  2013-12-05  1:27 ` [PATCH v2 0/3] Teaching "git push" to map pushed refs Junio C Hamano
  3 siblings, 0 replies; 8+ messages in thread
From: Junio C Hamano @ 2013-12-04  0:39 UTC (permalink / raw)
  To: git

When the user is using the 'upstream' mode, these commands:

    $ git push
    $ git push origin

would find the 'upstream' branch for the current branch, and then
push the current branch to update it.  However, pushing a single
branch explicitly, i.e.

    $ git push origin $(git symbolic-ref --short HEAD)

would not go through the same ref mapping process, and ends up
updating the branch at 'origin' of the same name, which may not
necessarily be the upstream of the branch being pushed.

In the spirit similar to the previous one, map a colon-less refspec
using the upstream mapping logic.

Note that this is deliberately done after the logic to map refspec
via the remote.$name.push configuration did not find any.  This is
because most of those that employ a simple 'upstream' mode do not
have the push refspec, and checking with remote.$name.push refspec
first will allow it to be used as a mechanism to override the usual
'upstream' push mapping (which is primarily useful for centralized
workflow) by those who want to use a triangular workflow.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin/push.c        | 11 +++++++++++
 t/t5516-fetch-push.sh | 30 ++++++++++++++++++++++++++++++
 2 files changed, 41 insertions(+)

diff --git a/builtin/push.c b/builtin/push.c
index bdc1fc1..71c081e 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -58,6 +58,17 @@ static const char *map_refspec(const char *ref,
 		}
 	}
 
+	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;
 }
 
diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh
index 6d7f102..926e7f6 100755
--- a/t/t5516-fetch-push.sh
+++ b/t/t5516-fetch-push.sh
@@ -1160,6 +1160,7 @@ test_expect_success 'with no remote.$name.push, it is not used as refmap' '
 		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
 	) &&
@@ -1171,6 +1172,35 @@ test_expect_success 'with no remote.$name.push, it is not used as refmap' '
 	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 &&
-- 
1.8.5.1-400-gbc1da41

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH v2 0/3] Teaching "git push" to map pushed refs
  2013-12-04  0:39 [PATCH 0/3] Teaching "git push" to map pushed refs Junio C Hamano
                   ` (2 preceding siblings ...)
  2013-12-04  0:39 ` [PATCH 3/3] push: also use "upstream" mapping when pushing a single ref Junio C Hamano
@ 2013-12-05  1:27 ` Junio C Hamano
  2013-12-05  1:27   ` [PATCH v2 1/3] builtin/push.c: use strbuf instead of manual allocation Junio C Hamano
                     ` (2 more replies)
  3 siblings, 3 replies; 8+ messages in thread
From: Junio C Hamano @ 2013-12-05  1:27 UTC (permalink / raw)
  To: git

The 'master' refspec in "git fetch origin master" used to mean "We
grab their 'master' branch, but we do not store it in any of our
remote-tracking branches." but in modern Git, the remote-tracking
branch that would receive updates from 'master' with a corresponding
"git fetch origin" (without any specific refs on the command line)
is updated.  Updating the same refs/remotes/origin/master with

	git fetch origin
	git fetch origin master

avoids surprises.

However, we did not have a similar refspec mapping on the push side.
This three-patch series does just that.  It would help triangular
workflow by making these two:

	git checkout master && git push origin
	git push origin master

update the same ref at the 'origin'.  It also would help those who
need to emulate a fetch run on mothership from satellite with a push
run on satellite into mothership (due to e.g. network connectivity
issues).

The second round avoids instantiating the remote and the list of
local heads until the very last minute when they are actually
needed (the change is in the second patch).

Junio C Hamano (3):
  builtin/push.c: use strbuf instead of manual allocation
  push: use remote.$name.push as a refmap
  push: also use "upstream" mapping when pushing a single ref

 Documentation/git-push.txt |  9 +++--
 builtin/push.c             | 84 ++++++++++++++++++++++++++++++++++------------
 remote.c                   |  8 ++---
 remote.h                   |  2 ++
 t/t5516-fetch-push.sh      | 75 +++++++++++++++++++++++++++++++++++++++++
 5 files changed, 150 insertions(+), 28 deletions(-)

-- 
1.8.5.1-402-gdd8f092

^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH v2 1/3] builtin/push.c: use strbuf instead of manual allocation
  2013-12-05  1:27 ` [PATCH v2 0/3] Teaching "git push" to map pushed refs Junio C Hamano
@ 2013-12-05  1:27   ` Junio C Hamano
  2013-12-05  1:27   ` [PATCH v2 2/3] push: use remote.$name.push as a refmap Junio C Hamano
  2013-12-05  1:27   ` [PATCH v2 3/3] push: also use "upstream" mapping when pushing a single ref Junio C Hamano
  2 siblings, 0 replies; 8+ messages in thread
From: Junio C Hamano @ 2013-12-05  1:27 UTC (permalink / raw)
  To: git

The command line arguments given to "git push" are massaged into
a list of refspecs in set_refspecs() function. This was implemented
using xmalloc, strcpy and friends, but it is much easier to read if
done using strbuf.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin/push.c | 35 ++++++++++++++---------------------
 1 file changed, 14 insertions(+), 21 deletions(-)

diff --git a/builtin/push.c b/builtin/push.c
index 7b1b66c..76e4400 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -41,29 +41,22 @@ static void set_refspecs(const char **refs, int nr)
 	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/");
-			}
-			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 = 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);
+		}
 		add_refspec(ref);
 	}
 }
-- 
1.8.5.1-402-gdd8f092

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH v2 2/3] push: use remote.$name.push as a refmap
  2013-12-05  1:27 ` [PATCH v2 0/3] Teaching "git push" to map pushed refs Junio C Hamano
  2013-12-05  1:27   ` [PATCH v2 1/3] builtin/push.c: use strbuf instead of manual allocation Junio C Hamano
@ 2013-12-05  1:27   ` Junio C Hamano
  2013-12-05  1:27   ` [PATCH v2 3/3] push: also use "upstream" mapping when pushing a single ref Junio C Hamano
  2 siblings, 0 replies; 8+ messages in thread
From: Junio C Hamano @ 2013-12-05  1:27 UTC (permalink / raw)
  To: git

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>
---
 Documentation/git-push.txt |  9 +++++++--
 builtin/push.c             | 40 ++++++++++++++++++++++++++++++++++++++--
 remote.c                   |  8 ++++----
 remote.h                   |  2 ++
 t/t5516-fetch-push.sh      | 45 +++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 96 insertions(+), 8 deletions(-)

diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt
index 9eec740..2b7f4f9 100644
--- a/Documentation/git-push.txt
+++ b/Documentation/git-push.txt
@@ -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
diff --git a/builtin/push.c b/builtin/push.c
index 76e4400..857f76d 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -35,9 +35,38 @@ 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);
+		}
+	}
+
+	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)) {
@@ -56,6 +85,13 @@ static void set_refspecs(const char **refs, int nr)
 				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();
+			}
+			ref = map_refspec(ref, remote, local_refs);
 		}
 		add_refspec(ref);
 	}
@@ -487,7 +523,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);
diff --git a/remote.c b/remote.c
index 9f1a8aa..6ffa149 100644
--- a/remote.c
+++ b/remote.c
@@ -821,7 +821,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;
@@ -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);
 }
 
-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;
diff --git a/remote.h b/remote.h
index 131130a..f7d43b4 100644
--- a/remote.h
+++ b/remote.h
@@ -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);
@@ -158,6 +159,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);
 
diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh
index 99c32d7..6d7f102 100755
--- a/t/t5516-fetch-push.sh
+++ b/t/t5516-fetch-push.sh
@@ -1126,6 +1126,51 @@ 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 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' '
 	mk_test testrepo heads/master &&
 	rm -fr src dst &&
-- 
1.8.5.1-402-gdd8f092

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH v2 3/3] push: also use "upstream" mapping when pushing a single ref
  2013-12-05  1:27 ` [PATCH v2 0/3] Teaching "git push" to map pushed refs Junio C Hamano
  2013-12-05  1:27   ` [PATCH v2 1/3] builtin/push.c: use strbuf instead of manual allocation Junio C Hamano
  2013-12-05  1:27   ` [PATCH v2 2/3] push: use remote.$name.push as a refmap Junio C Hamano
@ 2013-12-05  1:27   ` Junio C Hamano
  2 siblings, 0 replies; 8+ messages in thread
From: Junio C Hamano @ 2013-12-05  1:27 UTC (permalink / raw)
  To: git

When the user is using the 'upstream' mode, these commands:

    $ git push
    $ git push origin

would find the 'upstream' branch for the current branch, and then
push the current branch to update it.  However, pushing a single
branch explicitly, i.e.

    $ git push origin $(git symbolic-ref --short HEAD)

would not go through the same ref mapping process, and ends up
updating the branch at 'origin' of the same name, which may not
necessarily be the upstream of the branch being pushed.

In the spirit similar to the previous one, map a colon-less refspec
using the upstream mapping logic.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin/push.c        | 11 +++++++++++
 t/t5516-fetch-push.sh | 30 ++++++++++++++++++++++++++++++
 2 files changed, 41 insertions(+)

diff --git a/builtin/push.c b/builtin/push.c
index 857f76d..9e47c29 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -58,6 +58,17 @@ static const char *map_refspec(const char *ref,
 		}
 	}
 
+	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;
 }
 
diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh
index 6d7f102..926e7f6 100755
--- a/t/t5516-fetch-push.sh
+++ b/t/t5516-fetch-push.sh
@@ -1160,6 +1160,7 @@ test_expect_success 'with no remote.$name.push, it is not used as refmap' '
 		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
 	) &&
@@ -1171,6 +1172,35 @@ test_expect_success 'with no remote.$name.push, it is not used as refmap' '
 	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 &&
-- 
1.8.5.1-402-gdd8f092

^ permalink raw reply related	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2013-12-05  1:27 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2013-12-04  0:39 [PATCH 0/3] Teaching "git push" to map pushed refs Junio C Hamano
2013-12-04  0:39 ` [PATCH 1/3] builtin/push.c: use strbuf instead of manual allocation Junio C Hamano
2013-12-04  0:39 ` [PATCH 2/3] push: use remote.$name.push as a refmap Junio C Hamano
2013-12-04  0:39 ` [PATCH 3/3] push: also use "upstream" mapping when pushing a single ref Junio C Hamano
2013-12-05  1:27 ` [PATCH v2 0/3] Teaching "git push" to map pushed refs Junio C Hamano
2013-12-05  1:27   ` [PATCH v2 1/3] builtin/push.c: use strbuf instead of manual allocation Junio C Hamano
2013-12-05  1:27   ` [PATCH v2 2/3] push: use remote.$name.push as a refmap Junio C Hamano
2013-12-05  1:27   ` [PATCH v2 3/3] push: also use "upstream" mapping when pushing a single ref Junio C Hamano

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).