git.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/5] work around "transport-take-over" hack
@ 2013-08-07 23:30 Junio C Hamano
  2013-08-07 23:30 ` [PATCH 1/5] t5802: add test for connect helper Junio C Hamano
                   ` (4 more replies)
  0 siblings, 5 replies; 6+ messages in thread
From: Junio C Hamano @ 2013-08-07 23:30 UTC (permalink / raw)
  To: git; +Cc: Shawn Pearce

"git fetch" sometimes needs to make a real request to the transport
after a single "fetch_refs" request, in order to follow tags that
the other end should have sent as part of the primary transfer with
"follow-tags" request.

However, a transport that defines "connect" has a gross hack that
destroys its internal state to render it unusable after processing a
single request, breaking this.

This is my attempt to work it around.  I am not too proud of it, but
after trying two other approaches to fix it closer to the real cause
(i.e. the implementation of Git-aware transport helper) and seeing
that both of them ended up being even less appealing and not worth
posting, I think this is probably the best fix to the issue.

Junio C Hamano (5):
  t5802: add test for connect helper
  fetch: rename file-scope global "transport" to "gtransport"
  fetch: refactor code that prepares a transport
  fetch: refactor code that fetches leftover tags
  fetch: work around "transport-take-over" hack

 builtin/fetch.c           | 85 ++++++++++++++++++++++++++++++-----------------
 t/t5802-connect-helper.sh | 72 +++++++++++++++++++++++++++++++++++++++
 transport.c               |  2 ++
 transport.h               |  6 ++++
 4 files changed, 134 insertions(+), 31 deletions(-)
 create mode 100755 t/t5802-connect-helper.sh

-- 
1.8.4-rc1-210-gf6d87e2

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

* [PATCH 1/5] t5802: add test for connect helper
  2013-08-07 23:30 [PATCH 0/5] work around "transport-take-over" hack Junio C Hamano
@ 2013-08-07 23:30 ` Junio C Hamano
  2013-08-07 23:30 ` [PATCH 2/5] fetch: rename file-scope global "transport" to "gtransport" Junio C Hamano
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 6+ messages in thread
From: Junio C Hamano @ 2013-08-07 23:30 UTC (permalink / raw)
  To: git; +Cc: Shawn Pearce

This is an attempt to reproduce a problem reported for a third-party
custom "connect" remote helper.  The conjecture is that sometimes
"git fetch" wants to make two connections (one for the primary
transfer with 'follow-tags' option set, and then after noticing that
some tags are not packed because the primary transfer did not have
to send any commit that is pointed by them, another to explicitly
ask for the missing tags), and their "connect" helper is not called
in the second request, breaking the "fetch" as a whole.

Unfortunately this test script does not trigger the alleged failure
and happily passes when talking to upload-pack from git-core (see
patch 5/5 for details).

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 t/t5802-connect-helper.sh | 72 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 72 insertions(+)
 create mode 100755 t/t5802-connect-helper.sh

diff --git a/t/t5802-connect-helper.sh b/t/t5802-connect-helper.sh
new file mode 100755
index 0000000..878faf2
--- /dev/null
+++ b/t/t5802-connect-helper.sh
@@ -0,0 +1,72 @@
+#!/bin/sh
+
+test_description='ext::cmd remote "connect" helper'
+. ./test-lib.sh
+
+test_expect_success setup '
+	test_tick &&
+	git commit --allow-empty -m initial &&
+	test_tick &&
+	git commit --allow-empty -m second &&
+	test_tick &&
+	git commit --allow-empty -m third &&
+	test_tick &&
+	git tag -a -m "tip three" three &&
+
+	test_tick &&
+	git commit --allow-empty -m fourth
+'
+
+test_expect_success clone '
+	cmd=$(echo "echo >&2 ext::sh invoked && %S .." | sed -e "s/ /% /g") &&
+	git clone "ext::sh -c %S% ." dst &&
+	git for-each-ref refs/heads/ refs/tags/ >expect &&
+	(
+		cd dst &&
+		git config remote.origin.url "ext::sh -c $cmd" &&
+		git for-each-ref refs/heads/ refs/tags/
+	) >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'update following tag' '
+	test_tick &&
+	git commit --allow-empty -m fifth &&
+	test_tick &&
+	git tag -a -m "tip five" five &&
+	git for-each-ref refs/heads/ refs/tags/ >expect &&
+	(
+		cd dst &&
+		git pull &&
+		git for-each-ref refs/heads/ refs/tags/ >../actual
+	) &&
+	test_cmp expect actual
+'
+
+test_expect_success 'update backfilled tag' '
+	test_tick &&
+	git commit --allow-empty -m sixth &&
+	test_tick &&
+	git tag -a -m "tip two" two three^1 &&
+	git for-each-ref refs/heads/ refs/tags/ >expect &&
+	(
+		cd dst &&
+		git pull &&
+		git for-each-ref refs/heads/ refs/tags/ >../actual
+	) &&
+	test_cmp expect actual
+'
+
+test_expect_success 'update backfilled tag without primary transfer' '
+	test_tick &&
+	git tag -a -m "tip one " one two^1 &&
+	git for-each-ref refs/heads/ refs/tags/ >expect &&
+	(
+		cd dst &&
+		git pull &&
+		git for-each-ref refs/heads/ refs/tags/ >../actual
+	) &&
+	test_cmp expect actual
+'
+
+test_done
-- 
1.8.4-rc1-210-gf6d87e2

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

* [PATCH 2/5] fetch: rename file-scope global "transport" to "gtransport"
  2013-08-07 23:30 [PATCH 0/5] work around "transport-take-over" hack Junio C Hamano
  2013-08-07 23:30 ` [PATCH 1/5] t5802: add test for connect helper Junio C Hamano
@ 2013-08-07 23:30 ` Junio C Hamano
  2013-08-07 23:30 ` [PATCH 3/5] fetch: refactor code that prepares a transport Junio C Hamano
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 6+ messages in thread
From: Junio C Hamano @ 2013-08-07 23:30 UTC (permalink / raw)
  To: git; +Cc: Shawn Pearce

Although many functions in this file take a "struct transport" as a
parameter, "fetch_one()" assigns to the global singleton instance
which is a file-scope static, in order to allow a parameterless
signal handler unlock_pack() to access it.

Rename the variable to gtransport to make sure these uses stand out.

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

diff --git a/builtin/fetch.c b/builtin/fetch.c
index d784b2e..636b47f 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -36,7 +36,7 @@ static int tags = TAGS_DEFAULT, unshallow;
 static const char *depth;
 static const char *upload_pack;
 static struct strbuf default_rla = STRBUF_INIT;
-static struct transport *transport;
+static struct transport *gtransport;
 static const char *submodule_prefix = "";
 static const char *recurse_submodules_default;
 
@@ -95,8 +95,8 @@ static struct option builtin_fetch_options[] = {
 
 static void unlock_pack(void)
 {
-	if (transport)
-		transport_unlock_pack(transport);
+	if (gtransport)
+		transport_unlock_pack(gtransport);
 }
 
 static void unlock_pack_on_signal(int signo)
@@ -818,13 +818,13 @@ static int do_fetch(struct transport *transport,
 
 static void set_option(const char *name, const char *value)
 {
-	int r = transport_set_option(transport, name, value);
+	int r = transport_set_option(gtransport, name, value);
 	if (r < 0)
 		die(_("Option \"%s\" value \"%s\" is not valid for %s"),
-			name, value, transport->url);
+		    name, value, gtransport->url);
 	if (r > 0)
 		warning(_("Option \"%s\" is ignored for %s\n"),
-			name, transport->url);
+			name, gtransport->url);
 }
 
 static int get_one_remote_for_fetch(struct remote *remote, void *priv)
@@ -949,8 +949,8 @@ static int fetch_one(struct remote *remote, int argc, const char **argv)
 		die(_("No remote repository specified.  Please, specify either a URL or a\n"
 		    "remote name from which new revisions should be fetched."));
 
-	transport = transport_get(remote, NULL);
-	transport_set_verbosity(transport, verbosity, progress);
+	gtransport = transport_get(remote, NULL);
+	transport_set_verbosity(gtransport, verbosity, progress);
 	if (upload_pack)
 		set_option(TRANS_OPT_UPLOADPACK, upload_pack);
 	if (keep)
@@ -983,10 +983,10 @@ static int fetch_one(struct remote *remote, int argc, const char **argv)
 	sigchain_push_common(unlock_pack_on_signal);
 	atexit(unlock_pack);
 	refspec = parse_fetch_refspec(ref_nr, refs);
-	exit_code = do_fetch(transport, refspec, ref_nr);
+	exit_code = do_fetch(gtransport, refspec, ref_nr);
 	free_refspec(ref_nr, refspec);
-	transport_disconnect(transport);
-	transport = NULL;
+	transport_disconnect(gtransport);
+	gtransport = NULL;
 	return exit_code;
 }
 
-- 
1.8.4-rc1-210-gf6d87e2

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

* [PATCH 3/5] fetch: refactor code that prepares a transport
  2013-08-07 23:30 [PATCH 0/5] work around "transport-take-over" hack Junio C Hamano
  2013-08-07 23:30 ` [PATCH 1/5] t5802: add test for connect helper Junio C Hamano
  2013-08-07 23:30 ` [PATCH 2/5] fetch: rename file-scope global "transport" to "gtransport" Junio C Hamano
@ 2013-08-07 23:30 ` Junio C Hamano
  2013-08-07 23:30 ` [PATCH 4/5] fetch: refactor code that fetches leftover tags Junio C Hamano
  2013-08-07 23:30 ` [PATCH 5/5] fetch: work around "transport-take-over" hack Junio C Hamano
  4 siblings, 0 replies; 6+ messages in thread
From: Junio C Hamano @ 2013-08-07 23:30 UTC (permalink / raw)
  To: git; +Cc: Shawn Pearce

Make a helper function prepare_transport() that returns a transport
to talk to a given remote.

The set_option() helper that used to always affect the file-scope
global "gtransport" now takes a transport as its parameter.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin/fetch.c | 46 ++++++++++++++++++++++++++--------------------
 1 file changed, 26 insertions(+), 20 deletions(-)

diff --git a/builtin/fetch.c b/builtin/fetch.c
index 636b47f..39a3fc8 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -720,6 +720,31 @@ static int truncate_fetch_head(void)
 	return 0;
 }
 
+static void set_option(struct transport *transport, const char *name, const char *value)
+{
+	int r = transport_set_option(transport, name, value);
+	if (r < 0)
+		die(_("Option \"%s\" value \"%s\" is not valid for %s"),
+		    name, value, transport->url);
+	if (r > 0)
+		warning(_("Option \"%s\" is ignored for %s\n"),
+			name, transport->url);
+}
+
+struct transport *prepare_transport(struct remote *remote)
+{
+	struct transport *transport;
+	transport = transport_get(remote, NULL);
+	transport_set_verbosity(transport, verbosity, progress);
+	if (upload_pack)
+		set_option(transport, TRANS_OPT_UPLOADPACK, upload_pack);
+	if (keep)
+		set_option(transport, TRANS_OPT_KEEP, "yes");
+	if (depth)
+		set_option(transport, TRANS_OPT_DEPTH, depth);
+	return transport;
+}
+
 static int do_fetch(struct transport *transport,
 		    struct refspec *refs, int ref_count)
 {
@@ -816,17 +841,6 @@ static int do_fetch(struct transport *transport,
 	return retcode;
 }
 
-static void set_option(const char *name, const char *value)
-{
-	int r = transport_set_option(gtransport, name, value);
-	if (r < 0)
-		die(_("Option \"%s\" value \"%s\" is not valid for %s"),
-		    name, value, gtransport->url);
-	if (r > 0)
-		warning(_("Option \"%s\" is ignored for %s\n"),
-			name, gtransport->url);
-}
-
 static int get_one_remote_for_fetch(struct remote *remote, void *priv)
 {
 	struct string_list *list = priv;
@@ -949,15 +963,7 @@ static int fetch_one(struct remote *remote, int argc, const char **argv)
 		die(_("No remote repository specified.  Please, specify either a URL or a\n"
 		    "remote name from which new revisions should be fetched."));
 
-	gtransport = transport_get(remote, NULL);
-	transport_set_verbosity(gtransport, verbosity, progress);
-	if (upload_pack)
-		set_option(TRANS_OPT_UPLOADPACK, upload_pack);
-	if (keep)
-		set_option(TRANS_OPT_KEEP, "yes");
-	if (depth)
-		set_option(TRANS_OPT_DEPTH, depth);
-
+	gtransport = prepare_transport(remote);
 	if (argc > 0) {
 		int j = 0;
 		refs = xcalloc(argc + 1, sizeof(const char *));
-- 
1.8.4-rc1-210-gf6d87e2

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

* [PATCH 4/5] fetch: refactor code that fetches leftover tags
  2013-08-07 23:30 [PATCH 0/5] work around "transport-take-over" hack Junio C Hamano
                   ` (2 preceding siblings ...)
  2013-08-07 23:30 ` [PATCH 3/5] fetch: refactor code that prepares a transport Junio C Hamano
@ 2013-08-07 23:30 ` Junio C Hamano
  2013-08-07 23:30 ` [PATCH 5/5] fetch: work around "transport-take-over" hack Junio C Hamano
  4 siblings, 0 replies; 6+ messages in thread
From: Junio C Hamano @ 2013-08-07 23:30 UTC (permalink / raw)
  To: git; +Cc: Shawn Pearce

Usually the upload-pack process running on the other side will give
us all the reachable tags we need during the primary object transfer
in do_fetch().  If that does not happen (e.g. the other side may be
running a third-party implementation of upload-pack), we will run
another fetch to pick up leftover tags that we know point at the
commits reachable from our updated tips.

Separate out the code to run this second fetch into a helper
function.

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

diff --git a/builtin/fetch.c b/builtin/fetch.c
index 39a3fc8..0b21f07 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -745,6 +745,13 @@ struct transport *prepare_transport(struct remote *remote)
 	return transport;
 }
 
+static void backfill_tags(struct transport *transport, struct ref *ref_map)
+{
+	transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL);
+	transport_set_option(transport, TRANS_OPT_DEPTH, "0");
+	fetch_refs(transport, ref_map);
+}
+
 static int do_fetch(struct transport *transport,
 		    struct refspec *refs, int ref_count)
 {
@@ -828,11 +835,8 @@ static int do_fetch(struct transport *transport,
 		struct ref **tail = &ref_map;
 		ref_map = NULL;
 		find_non_local_tags(transport, &ref_map, &tail);
-		if (ref_map) {
-			transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL);
-			transport_set_option(transport, TRANS_OPT_DEPTH, "0");
-			fetch_refs(transport, ref_map);
-		}
+		if (ref_map)
+			backfill_tags(transport, ref_map);
 		free_refs(ref_map);
 	}
 
-- 
1.8.4-rc1-210-gf6d87e2

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

* [PATCH 5/5] fetch: work around "transport-take-over" hack
  2013-08-07 23:30 [PATCH 0/5] work around "transport-take-over" hack Junio C Hamano
                   ` (3 preceding siblings ...)
  2013-08-07 23:30 ` [PATCH 4/5] fetch: refactor code that fetches leftover tags Junio C Hamano
@ 2013-08-07 23:30 ` Junio C Hamano
  4 siblings, 0 replies; 6+ messages in thread
From: Junio C Hamano @ 2013-08-07 23:30 UTC (permalink / raw)
  To: git; +Cc: Shawn Pearce

A Git-aware "connect" transport allows the "transport_take_over" to
redirect generic transport requests like fetch(), push_refs() and
get_refs_list() to the native Git transport handling methods.  The
take-over process replaces transport->data with a fake data that
these method implementations understand.

While this hack works OK for a single request, it breaks when the
transport needs to make more than one requests.  transport->data
that used to hold necessary information for the specific helper to
work correctly is destroyed during the take-over process.

One codepath that this matters is "git fetch" in auto-follow mode;
when it does not get all the tags that ought to point at the history
it got (which can be determined by looking at the peeled tags in the
initial advertisement) from the primary transfer, it internally
makes a second request to complete the fetch.  Because "take-over"
hack has already destroyed the data necessary to talk to the
transport helper by the time this happens, the second request cannot
make a request to the helper to make another connection to fetch
these additional tags.

Mark such a transport as "cannot_reuse", and use a separate
transport to perform the backfill fetch in order to work around
this breakage.

Note that this problem does not manifest itself when running t5802,
because our upload-pack gives you all the necessary auto-followed
tags during the primary transfer.  You would need to step through
"git fetch" in a debugger, stop immediately after the primary
transfer finishes and writes these auto-followed tags, remove the
tag references and repack/prune the repository to convince the
"find-non-local-tags" procedure that the primary transfer failed to
give us all the necessary tags, and then let it continue, in order
to trigger the bug in the secondary transfer this patch fixes.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin/fetch.c | 13 +++++++++++++
 transport.c     |  2 ++
 transport.h     |  6 ++++++
 3 files changed, 21 insertions(+)

diff --git a/builtin/fetch.c b/builtin/fetch.c
index 0b21f07..57ab7e4 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -37,6 +37,7 @@ static const char *depth;
 static const char *upload_pack;
 static struct strbuf default_rla = STRBUF_INIT;
 static struct transport *gtransport;
+static struct transport *gsecondary;
 static const char *submodule_prefix = "";
 static const char *recurse_submodules_default;
 
@@ -97,6 +98,8 @@ static void unlock_pack(void)
 {
 	if (gtransport)
 		transport_unlock_pack(gtransport);
+	if (gsecondary)
+		transport_unlock_pack(gsecondary);
 }
 
 static void unlock_pack_on_signal(int signo)
@@ -747,9 +750,19 @@ struct transport *prepare_transport(struct remote *remote)
 
 static void backfill_tags(struct transport *transport, struct ref *ref_map)
 {
+	if (transport->cannot_reuse) {
+		gsecondary = prepare_transport(transport->remote);
+		transport = gsecondary;
+	}
+
 	transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL);
 	transport_set_option(transport, TRANS_OPT_DEPTH, "0");
 	fetch_refs(transport, ref_map);
+
+	if (gsecondary) {
+		transport_disconnect(gsecondary);
+		gsecondary = NULL;
+	}
 }
 
 static int do_fetch(struct transport *transport,
diff --git a/transport.c b/transport.c
index e15db98..de25588 100644
--- a/transport.c
+++ b/transport.c
@@ -875,6 +875,8 @@ void transport_take_over(struct transport *transport,
 	transport->push_refs = git_transport_push;
 	transport->disconnect = disconnect_git;
 	transport->smart_options = &(data->options);
+
+	transport->cannot_reuse = 1;
 }
 
 static int is_local(const char *url)
diff --git a/transport.h b/transport.h
index ea70ea7..96e0ede 100644
--- a/transport.h
+++ b/transport.h
@@ -27,6 +27,12 @@ struct transport {
 	 */
 	unsigned got_remote_refs : 1;
 
+	/*
+	 * Transports that call take-over destroys the data specific to
+	 * the transport type while doing so, and cannot be reused.
+	 */
+	unsigned cannot_reuse : 1;
+
 	/**
 	 * Returns 0 if successful, positive if the option is not
 	 * recognized or is inapplicable, and negative if the option
-- 
1.8.4-rc1-210-gf6d87e2

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

end of thread, other threads:[~2013-08-07 23:31 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2013-08-07 23:30 [PATCH 0/5] work around "transport-take-over" hack Junio C Hamano
2013-08-07 23:30 ` [PATCH 1/5] t5802: add test for connect helper Junio C Hamano
2013-08-07 23:30 ` [PATCH 2/5] fetch: rename file-scope global "transport" to "gtransport" Junio C Hamano
2013-08-07 23:30 ` [PATCH 3/5] fetch: refactor code that prepares a transport Junio C Hamano
2013-08-07 23:30 ` [PATCH 4/5] fetch: refactor code that fetches leftover tags Junio C Hamano
2013-08-07 23:30 ` [PATCH 5/5] fetch: work around "transport-take-over" hack 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).