threads / patch / 35644

patch, 3 partsMinor preparation for @{publish}

Subject: [PATCH 0/3] Minor preparation for @{publish}

## tl;dr

9 messages between Jan 12, 2014 and Jan 13, 2014. Diffs are folded; open one to read it.

replies: 8people: 3as markdown or json

Ramkumar Ramachandra· Jan 12, 2014, 17:11 UTC · lore
Hi,

I'm getting ready to switch timezones in a few days, so the @{publish} series is on hold for some time. In the meantime, I thought I'd send in a few patches early.

[1/3] is a minor typo fix I happened to notice while writing tests.

[2/3] is an is an excellent patch by Peff, that greatly helped prettify the code. It's a pure refactor and should have no effect on functionality.

[3/3] introduces branch->pushremote corresponding to branch->remote, which is crucial for getting @{publish} from the branch_get() interface. Peff had sent a similar patch, but mine has some subtle differences.

Thanks.
Jeff King (1):
  interpret_branch_name: factor out upstream handling
Ramkumar Ramachandra (2):
  t1507 (rev-parse-upstream): fix typo in test title
  remote: introduce and fill branch->pushremote
 remote.c                      | 15 ++++++--
 remote.h                      |  3 ++
 sha1_name.c                   | 83 +++++++++++++++++++++++++++----------------
 t/t1507-rev-parse-upstream.sh |  2 +-
 4 files changed, 68 insertions(+), 35 deletions(-)
-- 
1.8.5.2.313.g5abf4c0.dirty
Ramkumar Ramachandra· Jan 12, 2014, 17:11 UTC · re: Ramkumar Ramachandra · lore

[PATCH 1/3] t1507 (rev-parse-upstream): fix typo in test title

Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com>
---
 t/t1507-rev-parse-upstream.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to t/t1507-rev-parse-upstream.sh +1 −1
diff --git a/t/t1507-rev-parse-upstream.sh b/t/t1507-rev-parse-upstream.sh
index 2a19e79..15f2e7e 100755
--- a/t/t1507-rev-parse-upstream.sh
+++ b/t/t1507-rev-parse-upstream.sh
@@ -54,7 +54,7 @@ test_expect_success 'my-side@{upstream} resolves to correct full name' '
 	test refs/remotes/origin/side = "$(full_name my-side@{u})"
 '
 
-test_expect_success 'refs/heads/my-side@{upstream} does not resolve to my-side{upstream}' '
+test_expect_success 'refs/heads/my-side@{upstream} does not resolve to my-side@{upstream}' '
 	test_must_fail full_name refs/heads/my-side@{upstream}
 '
 
-- 
1.8.5.2.313.g5abf4c0.dirty
Ramkumar Ramachandra· Jan 12, 2014, 17:11 UTC · re: Ramkumar Ramachandra · lore

[PATCH 2/3] interpret_branch_name: factor out upstream handling

From: Jeff King <peff@peff.net>

This function checks a few different @{}-constructs. The early part checks for and dispatches us to helpers for each construct, but the code for handling @{upstream} is inline.

Let's factor this out into its own function. This makes interpret_branch_name more readable, and will make it much simpler to add more constructs in future patches.

While we're at it, let's also break apart the refactored code into a few helper functions. These will be useful when we implement similar @{upstream}-like constructs.

Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com>
---
 sha1_name.c | 83 ++++++++++++++++++++++++++++++++++++++-----------------------
 1 file changed, 52 insertions(+), 31 deletions(-)
Show changes to sha1_name.c +52 −31
diff --git a/sha1_name.c b/sha1_name.c
index b1873d8..7ebb8ee 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -1046,6 +1046,54 @@ static int reinterpret(const char *name, int namelen, int len, struct strbuf *bu
 	return ret - used + len;
 }
 
+static void set_shortened_ref(struct strbuf *buf, const char *ref)
+{
+	char *s = shorten_unambiguous_ref(ref, 0);
+	strbuf_reset(buf);
+	strbuf_addstr(buf, s);
+	free(s);
+}
+
+static const char *get_upstream_branch(const char *branch_buf, int len)
+{
+	char *branch = xstrndup(branch_buf, len);
+	struct branch *upstream = branch_get(*branch ? branch : NULL);
+
+	/*
+	 * Upstream can be NULL only if branch refers to HEAD and HEAD
+	 * points to something different than a branch.
+	 */
+	if (!upstream)
+		die(_("HEAD does not point to a branch"));
+	if (!upstream->merge || !upstream->merge[0]->dst) {
+		if (!ref_exists(upstream->refname))
+			die(_("No such branch: '%s'"), branch);
+		if (!upstream->merge) {
+			die(_("No upstream configured for branch '%s'"),
+				upstream->name);
+		}
+		die(
+			_("Upstream branch '%s' not stored as a remote-tracking branch"),
+			upstream->merge[0]->src);
+	}
+	free(branch);
+
+	return upstream->merge[0]->dst;
+}
+
+static int interpret_upstream_mark(const char *name, int namelen,
+				   int at, struct strbuf *buf)
+{
+	int len;
+
+	len = upstream_mark(name + at, namelen - at);
+	if (!len)
+		return -1;
+
+	set_shortened_ref(buf, get_upstream_branch(name, at));
+	return len + at;
+}
+
 /*
  * This reads short-hand syntax that not only evaluates to a commit
  * object name, but also can act as if the end user spelled the name
@@ -1070,9 +1118,7 @@ static int reinterpret(const char *name, int namelen, int len, struct strbuf *bu
 int interpret_branch_name(const char *name, int namelen, struct strbuf *buf)
 {
 	char *cp;
-	struct branch *upstream;
 	int len = interpret_nth_prior_checkout(name, buf);
-	int tmp_len;
 
 	if (!namelen)
 		namelen = strlen(name);
@@ -1094,36 +1140,11 @@ int interpret_branch_name(const char *name, int namelen, struct strbuf *buf)
 	if (len > 0)
 		return reinterpret(name, namelen, len, buf);
 
-	tmp_len = upstream_mark(cp, namelen - (cp - name));
-	if (!tmp_len)
-		return -1;
+	len = interpret_upstream_mark(name, namelen, cp - name, buf);
+	if (len > 0)
+		return len;
 
-	len = cp + tmp_len - name;
-	cp = xstrndup(name, cp - name);
-	upstream = branch_get(*cp ? cp : NULL);
-	/*
-	 * Upstream can be NULL only if cp refers to HEAD and HEAD
-	 * points to something different than a branch.
-	 */
-	if (!upstream)
-		die(_("HEAD does not point to a branch"));
-	if (!upstream->merge || !upstream->merge[0]->dst) {
-		if (!ref_exists(upstream->refname))
-			die(_("No such branch: '%s'"), cp);
-		if (!upstream->merge) {
-			die(_("No upstream configured for branch '%s'"),
-				upstream->name);
-		}
-		die(
-			_("Upstream branch '%s' not stored as a remote-tracking branch"),
-			upstream->merge[0]->src);
-	}
-	free(cp);
-	cp = shorten_unambiguous_ref(upstream->merge[0]->dst, 0);
-	strbuf_reset(buf);
-	strbuf_addstr(buf, cp);
-	free(cp);
-	return len;
+	return -1;
 }
 
 int strbuf_branchname(struct strbuf *sb, const char *name)
-- 
1.8.5.2.313.g5abf4c0.dirty
Ramkumar Ramachandra· Jan 12, 2014, 17:11 UTC · re: Ramkumar Ramachandra · lore

[PATCH 3/3] remote: introduce and fill branch->pushremote

When a caller uses branch_get() to retrieve a "struct branch", they get the per-branch remote name and a pointer to the remote struct. However, they have no way of knowing about the per-branch pushremote from this interface. So, let's expose that information via fields similar to "remote" and "remote_name"; "pushremote" and "pushremote_name".

Helped-by: Jeff King <peff@peff.net>
Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com>
---
 remote.c | 15 ++++++++++++---
 remote.h |  3 +++
 2 files changed, 15 insertions(+), 3 deletions(-)
Show changes to 2 files +15 −3

remote.c, remote.h

diff --git a/remote.c b/remote.c
index a89efab..286cdce 100644
--- a/remote.c
+++ b/remote.c
@@ -351,9 +351,10 @@ static int handle_config(const char *key, const char *value, void *cb)
 				explicit_default_remote_name = 1;
 			}
 		} else if (!strcmp(subkey, ".pushremote")) {
+			if (git_config_string(&branch->pushremote_name, key, value))
+				return -1;
 			if (branch == current_branch)
-				if (git_config_string(&pushremote_name, key, value))
-					return -1;
+				pushremote_name = branch->pushremote_name;
 		} else if (!strcmp(subkey, ".merge")) {
 			if (!value)
 				return config_error_nonbool(key);
@@ -1543,7 +1544,9 @@ struct branch *branch_get(const char *name)
 		ret = current_branch;
 	else
 		ret = make_branch(name, 0);
-	if (ret && ret->remote_name) {
+	if (!ret)
+		return ret;
+	if (ret->remote_name) {
 		ret->remote = remote_get(ret->remote_name);
 		if (ret->merge_nr) {
 			int i;
@@ -1557,6 +1560,12 @@ struct branch *branch_get(const char *name)
 			}
 		}
 	}
+	if (ret->pushremote_name)
+		ret->pushremote = remote_get(ret->pushremote_name);
+	else if (pushremote_name)
+		ret->pushremote = remote_get(pushremote_name);
+	else
+		ret->pushremote = ret->remote;
 	return ret;
 }
 
diff --git a/remote.h b/remote.h
index 00c6a76..ac5aadc 100644
--- a/remote.h
+++ b/remote.h
@@ -201,6 +201,9 @@ struct branch {
 	const char *remote_name;
 	struct remote *remote;
 
+	const char *pushremote_name;
+	struct remote *pushremote;
+
 	const char **merge_name;
 	struct refspec **merge;
 	int merge_nr;
-- 
1.8.5.2.313.g5abf4c0.dirty
Jeff King· Jan 13, 2014, 08:34 UTC · re: Ramkumar Ramachandra · lore

Re: [PATCH 3/3] remote: introduce and fill branch->pushremote

On Sun, Jan 12, 2014 at 10:41:06PM +0530, Ramkumar Ramachandra wrote:
Show 5 quoted lines
> When a caller uses branch_get() to retrieve a "struct branch", they get
> the per-branch remote name and a pointer to the remote struct. However,
> they have no way of knowing about the per-branch pushremote from this
> interface. So, let's expose that information via fields similar to
> "remote" and "remote_name"; "pushremote" and "pushremote_name".

Makes sense. This is similar to what I posted before, but stops short of setting branch->pushremote based on "default.pushremote". I think that's a good thing. Your patch matches branch->remote better, and the logic for doing that fallback should probably stay outside of the "struct branch" construct.

All 3 patches look like sane building blocks to me.
One comment on this hunk, though:
Show 7 quoted lines
>  		} else if (!strcmp(subkey, ".pushremote")) {
> +			if (git_config_string(&branch->pushremote_name, key, value))
> +				return -1;
>  			if (branch == current_branch)
> -				if (git_config_string(&pushremote_name, key, value))
> -					return -1;
> +				pushremote_name = branch->pushremote_name;

In this code (both before and after your patch), pushremote_name does double-duty for storing both "remote.pushdefault", and the current branch's "branch.*.pushremote". I introduced an extra variable in my version of the patch to store "remote.pushdefault" directly, and turned pushremote_name into an alias (either to the current branch config, or to the global config).

I did that for two reasons, one minor and one that I think will come up further in the topic:

  1. After your patch "pushremote_name" sometimes owns its memory (if
     allocated for remote.pushdefault), and sometimes not (if an alias to
     branch.*.pushremote). This isn't a problem in the current code,
     because we never actually free() the string, meaning that if you
     set push.default twice, we leak. But that probably does not matter
     too much, and we have many such minor leaks of global config.
  2. If the current branch has a branch.*.pushremote set, but we want to
     know where a _different_ branch would be pushed, we have no way to
     access remote.pushdefault (it gets overwritten in the hunk above).
     @{upstream} does not have this problem, because it is _only_
     defined if branch.*.remote is set. There is no such thing as
     defaulting to a "remote.default" (or "origin") there, and we never
     need to look at default_remote_name.
     For @{publish}, though, I think we will want that default. The
     common config will be to simply set "remote.pushdefault", rather
     than setting up "branch.*.pushremote" for each branch, and we would
     want @{publish} to handle that properly.

So I think your patch is OK as-is, as the problem in (2) does not show up until later in the series. But I suspect you will need to do something to address it (and I think it is fine as a patch that comes later to do that refactoring).

-Peff
Ramkumar Ramachandra· Jan 13, 2014, 11:22 UTC · re: Jeff King · lore

Re: [PATCH 3/3] remote: introduce and fill branch->pushremote

Jeff King wrote:
Show 13 quoted lines
>   2. If the current branch has a branch.*.pushremote set, but we want to
>      know where a _different_ branch would be pushed, we have no way to
>      access remote.pushdefault (it gets overwritten in the hunk above).
>
>      @{upstream} does not have this problem, because it is _only_
>      defined if branch.*.remote is set. There is no such thing as
>      defaulting to a "remote.default" (or "origin") there, and we never
>      need to look at default_remote_name.
>
>      For @{publish}, though, I think we will want that default. The
>      common config will be to simply set "remote.pushdefault", rather
>      than setting up "branch.*.pushremote" for each branch, and we would
>      want @{publish} to handle that properly.

Not sure I understand what the problem is. Let's say we have two branches: "master", and "side" with remote.pushdefault = ram, branch.*.remote = origin, and branch.side.pushremote = peff. Now, when I query master's pushremote, I get "ram" and when I query side's pushremote, I get "peff"; all the logic for falling-back from branch.*.pushremote to remote.pushdefault to branch.*.remote is in branch_get(), so I need to do nothing extra on the caller-side. From the caller's perspective, why does it matter if the pushremote of a particular branch is due to branch.*.pushremote or remote.pushdefault?

Jeff King· Jan 13, 2014, 18:59 UTC · re: Ramkumar Ramachandra · lore

Re: [PATCH 3/3] remote: introduce and fill branch->pushremote

On Mon, Jan 13, 2014 at 04:52:52PM +0530, Ramkumar Ramachandra wrote:
Show 9 quoted lines
> Not sure I understand what the problem is. Let's say we have two
> branches: "master", and "side" with remote.pushdefault = ram,
> branch.*.remote = origin, and branch.side.pushremote = peff. Now, when
> I query master's pushremote, I get "ram" and when I query side's
> pushremote, I get "peff"; all the logic for falling-back from
> branch.*.pushremote to remote.pushdefault to branch.*.remote is in
> branch_get(), so I need to do nothing extra on the caller-side. From
> the caller's perspective, why does it matter if the pushremote of a
> particular branch is due to branch.*.pushremote or remote.pushdefault?

Imagine your HEAD is at "side". What should "master@{publish}" produce? I would argue "ram/master". Where does "ram" come from in your code?

It does not matter for actually pushing, because to do a non-default push, you must always specify a remote. But "@{publish}" will ask the question "even if I am on 'side' now, what would happen if I were to default-push on 'master'?".

-Peff
Junio C Hamano· Jan 13, 2014, 20:15 UTC · re: Jeff King · lore

Re: [PATCH 3/3] remote: introduce and fill branch->pushremote

Jeff King <peff@peff.net> writes:
> It does not matter for actually pushing, because to do a non-default
> push, you must always specify a remote. But "@{publish}" will ask the
> question "even if I am on 'side' now, what would happen if I were to
> default-push on 'master'?".

In a similar wording to yours, it can be said that B@{upstream} is "what would happen if I were to default-pull on 'B'?".

A related tangent is what should B@{publish} should yield when there is no triangular configuration variables like remote.pushdefault, branch.B.pushremote and a possible future extension branch.B.push are defined. The definition you gave, i.e. "if I were to default-push", gives a good guideline, I think.

I.e. "git push origin master" does tell us to push out 'master', but it does not explicitly say what ref to update. It may be set to update their remotes/satellite/master when we are emulating a fetch in reverse by pushing, via e.g.

	[remote "origin"]
        	push = refs/heads/master:refs/remotes/satellite/master

and it would be intuitive if we make "master@{publish}" resolve to "refs/remotes/satellite/master" in such a case.

One thing that makes things a bit fuzzy is what should happen if you have more than one push destinations. For example:

	[remote "home"]
        	pushurl = ...
                push = refs/heads/master:refs/remotes/satellite/master
	[remote "github"]
        	pushurl = ...
                mirror
	[remote]
        	pushdefault = ???

"git push home" updates their 'refs/remotes/satellite/master' with my 'master' with the above, while "git push github" will update their 'refs/heads/master' with 'master'.

We can say master@{publish} is 'remotes/satellite/master' if remote.pushdefault (or 'branch.master.pushremote") is set to 'home', it is 'master' if it is 'github', and if "git push" while sitting on 'master' does not push it anywhere then master@{publish} is an error. There may be a better definition of what "if I were to default-push" really means, but I don't think of any offhand.

Jeff King· Jan 13, 2014, 20:27 UTC · re: Junio C Hamano · lore

Re: [PATCH 3/3] remote: introduce and fill branch->pushremote

On Mon, Jan 13, 2014 at 12:15:08PM -0800, Junio C Hamano wrote:
Show 9 quoted lines
> Jeff King <peff@peff.net> writes:
> 
> > It does not matter for actually pushing, because to do a non-default
> > push, you must always specify a remote. But "@{publish}" will ask the
> > question "even if I am on 'side' now, what would happen if I were to
> > default-push on 'master'?".
> 
> In a similar wording to yours, it can be said that B@{upstream} is
> "what would happen if I were to default-pull on 'B'?".

Right. I wondered at first if there was a similar bug in @{upstream}, but as I noted earlier, it is not defined if a per-branch remote is not set. The answer to your question above is "nothing", so we do not have to worry about it. :)

Show 5 quoted lines
> A related tangent is what should B@{publish} should yield when there
> is no triangular configuration variables like remote.pushdefault,
> branch.B.pushremote and a possible future extension branch.B.push
> are defined.  The definition you gave, i.e. "if I were to
> default-push", gives a good guideline, I think.

Yes, that is what I tried for with my original patches. (e.g., "push.default=upstream" should just make @{publish} a synonym for @{upstream}, which is what my patch did). I punted on "simple", but it would ideally do the same thing as "push". Which is why I do not think my patches are appropriate as-is; they need to somehow share the logic with "git push" rather than try to reimplement it.

Show 10 quoted lines
> I.e. "git push origin master" does tell us to push out 'master', but
> it does not explicitly say what ref to update.  It may be set to
> update their remotes/satellite/master when we are emulating a fetch
> in reverse by pushing, via e.g.
> 
> 	[remote "origin"]
>         	push = refs/heads/master:refs/remotes/satellite/master
> 
> and it would be intuitive if we make "master@{publish}" resolve to
> "refs/remotes/satellite/master" in such a case.

Right. And my patches did that (or at least I intended them to :) ) by applying the push refspec (if any), and then applying the fetch refspec on top of that. But again, that seems like policy that should be shared with "git push".

That being said, I do not think your example is the best one for @{publish}. You have not specified any remote at all. I think the closest "push" behavior for @{publish} would be something like:

  git checkout master && git push
I.e., where would _that_ push go?
Show 24 quoted lines
> One thing that makes things a bit fuzzy is what should happen if
> you have more than one push destinations.  For example:
> 
> 	[remote "home"]
>         	pushurl = ...
>                 push = refs/heads/master:refs/remotes/satellite/master
> 
> 	[remote "github"]
>         	pushurl = ...
>                 mirror
> 
> 	[remote]
>         	pushdefault = ???
> 
> "git push home" updates their 'refs/remotes/satellite/master' with
> my 'master' with the above, while "git push github" will update
> their 'refs/heads/master' with 'master'.
> 
> We can say master@{publish} is 'remotes/satellite/master' if
> remote.pushdefault (or 'branch.master.pushremote") is set to 'home',
> it is 'master' if it is 'github', and if "git push" while sitting on
> 'master' does not push it anywhere then master@{publish} is an
> error.  There may be a better definition of what "if I were to
> default-push" really means, but I don't think of any offhand.

Exactly. I do not think the multiple push destinations matter here, because it is always "what would I do if I were on the branch". At most one of them can be the default in that case (based on the config as you noted).

-Peff

← back to recent threads