{"thread":{"id":"23238","subject":"[PATCH 0/7] remote-helpers: exporting","startedAt":"2010-03-29T16:48:22Z","lastAt":"2010-04-12T14:26:53Z","messageCount":14,"participants":["Sverre Rabbelier","Daniel Barkalow","Junio C Hamano","Jonathan Nieder"],"isPatch":true,"patchVersion":1,"patchTotal":7},"messages":[{"id":"138105","messageId":"1269881309-19690-1-git-send-email-srabbelier@gmail.com","threadId":"23238","inReplyTo":null,"subject":"[PATCH 0/7] remote-helpers: exporting","fromName":"Sverre Rabbelier","fromEmail":"srabbelier@gmail.com","sentAt":"2010-03-29T16:48:22Z","receivedAt":"2010-03-29T16:48:22Z","isPatch":true,"sender":{"key":"srabbelier@gmail.com","avatar":"https://avatars.githubusercontent.com/u/3098?v=4"},"body":"Hopefully this is in time for 1.7.1, it'd be good to have the 'export'\ncommand in asap. I'm fairly sure that the first two patches are good\n(all tests pass, and I've spent a lot of time debugging the relevant\ncode), but I'd still appreciate if the clone experts could give\nit a look.\n\nI'm really glad to finally get this working, hopefully I'll also have\ntime to finish up the remote-hg implementation, but probably not\nbefore the 1.7.1 window closes (assuming that will be somewhere in\nthe next few days).\n\nSverre Rabbelier (7):\n      clone: pass the remote name to remote_get\n      clone: also configure url for bare clones\n      fast-import: always create marks_file directories\n      remote-helpers: allow requesing the path to the .git directory\n      remote-helpers: add support for an export command\n      remote-helpers: add testgit helper\n      remote-helpers: add tests for testgit helper\n\n .gitignore                          |    1 +\n Makefile                            |    2 +\n builtin/clone.c                     |   16 ++--\n fast-import.c                       |    2 +\n git-remote-testgit.py               |  233 +++++++++++++++++++++++++++++++++++\n git_remote_helpers/git/exporter.py  |   51 ++++++++\n git_remote_helpers/git/importer.py  |   38 ++++++\n git_remote_helpers/git/non_local.py |   61 +++++++++\n git_remote_helpers/git/repo.py      |   70 +++++++++++\n t/t5800-remote-helpers.sh           |   70 +++++++++++\n transport-helper.c                  |  141 +++++++++++++++++++--\n 11 files changed, 665 insertions(+), 20 deletions(-)\n"},{"id":"138106","messageId":"1269881309-19690-2-git-send-email-srabbelier@gmail.com","threadId":"23238","inReplyTo":"1269881309-19690-1-git-send-email-srabbelier@gmail.com","subject":"[PATCH 1/7] clone: pass the remote name to remote_get","fromName":"Sverre Rabbelier","fromEmail":"srabbelier@gmail.com","sentAt":"2010-03-29T16:48:23Z","receivedAt":"2010-03-29T16:48:23Z","isPatch":true,"sender":{"key":"srabbelier@gmail.com","avatar":"https://avatars.githubusercontent.com/u/3098?v=4"},"body":"Currently when using a remote helper to clone a repository, the\nremote helper will be passed the url of the target repository as\nfirst argument (which represents the name of the remote). This name\nis extracted from transport->remote->name, which is set by\nbuiltin/clone.c when it calls remote_get with argv[0] as argument.\n\nFix this by passing the name remote will be set up as instead.\n\nHowever, setup_reference calls remote_get before the remote is\nadded to the config file. This will result in an improperly\nconfigured remote (in memory) if later on remote_get is called\nwith an argument that is not equal to the initial remote_get call\nin setup_reference. Fix this by delaying the remote_get call until\nafter the remote has been added to the config file.\n---\n\n\tThis is a combination of the earlier two-patch fix I sent.\n\n builtin/clone.c |    8 ++++----\n 1 files changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 05f8fb4..068d61f 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -470,9 +470,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \t */\n \tunsetenv(CONFIG_ENVIRONMENT);\n \n-\tif (option_reference)\n-\t\tsetup_reference(git_dir);\n-\n \tgit_config(git_default_config, NULL);\n \n \tif (option_bare) {\n@@ -504,6 +501,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \t\tstrbuf_reset(&key);\n \t}\n \n+\tif (option_reference)\n+\t\tsetup_reference(git_dir);\n+\n \tfetch_pattern = value.buf;\n \trefspec = parse_fetch_refspec(1, &fetch_pattern);\n \n@@ -513,7 +513,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \t\trefs = clone_local(path, git_dir);\n \t\tmapped_refs = wanted_peer_refs(refs, refspec);\n \t} else {\n-\t\tstruct remote *remote = remote_get(argv[0]);\n+\t\tstruct remote *remote = remote_get(option_origin);\n \t\ttransport = transport_get(remote, remote->url[0]);\n \n \t\tif (!transport->get_refs_list || !transport->fetch)\n-- \n1.7.0.3.317.gbb04ec\n"},{"id":"138107","messageId":"1269881309-19690-3-git-send-email-srabbelier@gmail.com","threadId":"23238","inReplyTo":"1269881309-19690-1-git-send-email-srabbelier@gmail.com","subject":"[PATCH 2/7] clone: also configure url for bare clones","fromName":"Sverre Rabbelier","fromEmail":"srabbelier@gmail.com","sentAt":"2010-03-29T16:48:24Z","receivedAt":"2010-03-29T16:48:24Z","isPatch":true,"sender":{"key":"srabbelier@gmail.com","avatar":"https://avatars.githubusercontent.com/u/3098?v=4"},"body":"Without this the 'origin' remote would not be configured, so when\ncalling remote_get with 'origin' as argument we would get an\nunconfigured remote.\n---\n\n\tTurns out there was one use-case that is not exercised by the\n\ttest suite. The remote-helper tests later in the series do\n\texercised that path, but we might want a seperate test.\n\n builtin/clone.c |    8 ++++----\n 1 files changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 068d61f..05be999 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -495,12 +495,12 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \t\t\tgit_config_set(key.buf, \"true\");\n \t\t\tstrbuf_reset(&key);\n \t\t}\n-\n-\t\tstrbuf_addf(&key, \"remote.%s.url\", option_origin);\n-\t\tgit_config_set(key.buf, repo);\n-\t\tstrbuf_reset(&key);\n \t}\n \n+\tstrbuf_addf(&key, \"remote.%s.url\", option_origin);\n+\tgit_config_set(key.buf, repo);\n+\tstrbuf_reset(&key);\n+\n \tif (option_reference)\n \t\tsetup_reference(git_dir);\n \n-- \n1.7.0.3.317.gbb04ec\n"},{"id":"138108","messageId":"1269881309-19690-4-git-send-email-srabbelier@gmail.com","threadId":"23238","inReplyTo":"1269881309-19690-1-git-send-email-srabbelier@gmail.com","subject":"[PATCH 3/7] fast-import: always create marks_file directories","fromName":"Sverre Rabbelier","fromEmail":"srabbelier@gmail.com","sentAt":"2010-03-29T16:48:25Z","receivedAt":"2010-03-29T16:48:25Z","isPatch":true,"sender":{"key":"srabbelier@gmail.com","avatar":"https://avatars.githubusercontent.com/u/3098?v=4"},"body":"CC: \"Shawn O. Pearce\" <spearce@spearce.org>\n---\n\n\tNot stricly neccesary (one could argue that it is the\n\tresponsibility of the caller to make sure the directory exists),\n\tbut it might make sense to do this one (in fast-import) rather\n\tthan having to do it in all helpers.\n\n fast-import.c |    2 ++\n 1 files changed, 2 insertions(+), 0 deletions(-)\n\ndiff --git a/fast-import.c b/fast-import.c\nindex 309f2c5..129a786 100644\n--- a/fast-import.c\n+++ b/fast-import.c\n@@ -2707,6 +2707,7 @@ static void option_import_marks(const char *marks, int from_stream)\n \t}\n \n \timport_marks_file = make_fast_import_path(marks);\n+\tsafe_create_leading_directories_const(import_marks_file);\n \timport_marks_file_from_stream = from_stream;\n }\n \n@@ -2737,6 +2738,7 @@ static void option_active_branches(const char *branches)\n static void option_export_marks(const char *marks)\n {\n \texport_marks_file = make_fast_import_path(marks);\n+\tsafe_create_leading_directories_const(export_marks_file);\n }\n \n static void option_export_pack_edges(const char *edges)\n-- \n1.7.0.3.317.gbb04ec\n"},{"id":"138109","messageId":"1269881309-19690-5-git-send-email-srabbelier@gmail.com","threadId":"23238","inReplyTo":"1269881309-19690-1-git-send-email-srabbelier@gmail.com","subject":"[PATCH 4/7] remote-helpers: allow requesing the path to the .git directory","fromName":"Sverre Rabbelier","fromEmail":"srabbelier@gmail.com","sentAt":"2010-03-29T16:48:26Z","receivedAt":"2010-03-29T16:48:26Z","isPatch":true,"sender":{"key":"srabbelier@gmail.com","avatar":"https://avatars.githubusercontent.com/u/3098?v=4"},"body":"The 'gitdir' capability is reported by the remote helper if it\nrequires the location of the .git directory. The location of the .git\ndirectory can then be used by the helper to store status files even\nwhen the current directory is not a git repository (such as is the\ncase when cloning).\n\nThe location of the .git dir is specified as an absolute path.\n\nSigned-off-by: Sverre Rabbelier <srabbelier@gmail.com>\n---\n\n\tThis should probably be advertised as a required capability,\n\tbut I haven't looked into those yet.\n\n transport-helper.c |    5 +++++\n 1 files changed, 5 insertions(+), 0 deletions(-)\n\ndiff --git a/transport-helper.c b/transport-helper.c\nindex 2638781..c8705b7 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -170,6 +170,11 @@ static struct child_process *get_helper(struct transport *transport)\n \t\t\trefspecs[refspec_nr++] = strdup(buf.buf + strlen(\"refspec \"));\n \t\t} else if (!strcmp(capname, \"connect\")) {\n \t\t\tdata->connect = 1;\n+\t\t} else if (!strcmp(buf.buf, \"gitdir\")) {\n+\t\t\tstruct strbuf gitdir = STRBUF_INIT;\n+\t\t\tstrbuf_addf(&gitdir, \"gitdir %s\\n\", get_git_dir());\n+\t\t\tsendline(data, &gitdir);\n+\t\t\tstrbuf_release(&gitdir);\n \t\t} else if (mandatory) {\n \t\t\tdie(\"Unknown mandatory capability %s. This remote \"\n \t\t\t    \"helper probably needs newer version of Git.\\n\",\n-- \n1.7.0.3.317.gbb04ec\n"},{"id":"138110","messageId":"1269881309-19690-6-git-send-email-srabbelier@gmail.com","threadId":"23238","inReplyTo":"1269881309-19690-1-git-send-email-srabbelier@gmail.com","subject":"[PATCH 5/7] remote-helpers: add support for an export command","fromName":"Sverre Rabbelier","fromEmail":"srabbelier@gmail.com","sentAt":"2010-03-29T16:48:27Z","receivedAt":"2010-03-29T16:48:27Z","isPatch":true,"sender":{"key":"srabbelier@gmail.com","avatar":"https://avatars.githubusercontent.com/u/3098?v=4"},"body":"---\n\n\tThis complements the 'import' capability, making it possible\n\tto use remote-helpers with only fast-import/fast-exports\n\tstreams.\n\n transport-helper.c |  136 +++++++++++++++++++++++++++++++++++++++++++++++-----\n 1 files changed, 124 insertions(+), 12 deletions(-)\n\ndiff --git a/transport-helper.c b/transport-helper.c\nindex c8705b7..f9ce036 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -7,6 +7,7 @@\n #include \"revision.h\"\n #include \"quote.h\"\n #include \"remote.h\"\n+#include \"string-list.h\"\n \n static int debug;\n \n@@ -17,6 +18,7 @@ struct helper_data\n \tFILE *out;\n \tunsigned fetch : 1,\n \t\timport : 1,\n+\t\texport : 1,\n \t\toption : 1,\n \t\tpush : 1,\n \t\tconnect : 1,\n@@ -163,6 +165,8 @@ static struct child_process *get_helper(struct transport *transport)\n \t\t\tdata->push = 1;\n \t\telse if (!strcmp(capname, \"import\"))\n \t\t\tdata->import = 1;\n+\t\telse if (!strcmp(capname, \"export\"))\n+\t\t\tdata->export = 1;\n \t\telse if (!data->refspecs && !prefixcmp(capname, \"refspec \")) {\n \t\t\tALLOC_GROW(refspecs,\n \t\t\t\t   refspec_nr + 1,\n@@ -356,6 +360,33 @@ static int get_importer(struct transport *transport, struct child_process *fasti\n \treturn start_command(fastimport);\n }\n \n+static int get_exporter(struct transport *transport,\n+\t\t\tstruct child_process *fastexport,\n+\t\t\tconst char *export_marks,\n+\t\t\tconst char *import_marks,\n+\t\t\tstruct string_list* revlist_args)\n+{\n+\tstruct child_process *helper = get_helper(transport);\n+\tint argc = 0, i;\n+\tmemset(fastexport, 0, sizeof(*fastexport));\n+\n+\t/* we need to duplicate helper->in because we want to use it after\n+\t * fastexport is done with it. */\n+\tfastexport->out = dup(helper->in);\n+\tfastexport->argv = xcalloc(4 + revlist_args->nr, sizeof(*fastexport->argv));\n+\tfastexport->argv[argc++] = \"fast-export\";\n+\tif(export_marks)\n+\t\tfastexport->argv[argc++] = export_marks;\n+\tif(import_marks)\n+\t\tfastexport->argv[argc++] = import_marks;\n+\n+\tfor(i = 0; i < revlist_args->nr; i++)\n+\t\tfastexport->argv[argc++] = revlist_args->items[i].string;\n+\n+\tfastexport->git_cmd = 1;\n+\treturn start_command(fastexport);\n+}\n+\n static int fetch_with_import(struct transport *transport,\n \t\t\t     int nr_heads, struct ref **to_fetch)\n {\n@@ -523,7 +554,7 @@ static int fetch(struct transport *transport,\n \treturn -1;\n }\n \n-static int push_refs(struct transport *transport,\n+static int push_refs_with_push(struct transport *transport,\n \t\tstruct ref *remote_refs, int flags)\n {\n \tint force_all = flags & TRANSPORT_PUSH_FORCE;\n@@ -533,17 +564,6 @@ static int push_refs(struct transport *transport,\n \tstruct child_process *helper;\n \tstruct ref *ref;\n \n-\tif (process_connect(transport, 1)) {\n-\t\tdo_take_over(transport);\n-\t\treturn transport->push_refs(transport, remote_refs, flags);\n-\t}\n-\n-\tif (!remote_refs) {\n-\t\tfprintf(stderr, \"No refs in common and none specified; doing nothing.\\n\"\n-\t\t\t\"Perhaps you should specify a branch such as 'master'.\\n\");\n-\t\treturn 0;\n-\t}\n-\n \thelper = get_helper(transport);\n \tif (!data->push)\n \t\treturn 1;\n@@ -662,6 +682,98 @@ static int push_refs(struct transport *transport,\n \treturn 0;\n }\n \n+static int push_refs_with_export(struct transport *transport,\n+\t\tstruct ref *remote_refs, int flags)\n+{\n+\tstruct ref *ref;\n+\tstruct child_process *helper, exporter;\n+\tstruct helper_data *data = transport->data;\n+\tchar *export_marks = NULL, *import_marks = NULL;\n+\tstruct string_list revlist_args = { NULL, 0, 0 };\n+\tstruct strbuf buf = STRBUF_INIT;\n+\n+\thelper = get_helper(transport);\n+\n+\twrite_constant(helper->in, \"export\\n\");\n+\n+\trecvline(data, &buf);\n+\tif (debug)\n+\t\tfprintf(stderr, \"Debug: Got export_marks '%s'\\n\", buf.buf);\n+\tif(buf.len)\n+\t{\n+\t\tstruct strbuf arg = STRBUF_INIT;\n+\t\tstrbuf_addstr(&arg, \"--export-marks=\");\n+\t\tstrbuf_addbuf(&arg, &buf);\n+\t\texport_marks = strbuf_detach(&arg, NULL);\n+\t}\n+\n+\trecvline(data, &buf);\n+\tif (debug)\n+\t\tfprintf(stderr, \"Debug: Got import_marks '%s'\\n\", buf.buf);\n+\tif(buf.len)\n+\t{\n+\t\tstruct strbuf arg = STRBUF_INIT;\n+\t\tstrbuf_addstr(&arg, \"--import-marks=\");\n+\t\tstrbuf_addbuf(&arg, &buf);\n+\t\timport_marks = strbuf_detach(&arg, NULL);\n+\t}\n+\n+\tstrbuf_reset(&buf);\n+\n+\tfor(ref = remote_refs; ref; ref = ref->next) {\n+\t\tchar* private;\n+\t\tunsigned char sha1[20];\n+\n+\n+\t\tif(data->refspecs)\n+\t\t\tprivate = apply_refspecs(data->refspecs, data->refspec_nr, ref->name);\n+\n+\t\tif(private && !get_sha1(private, sha1))\n+\t\t{\n+\t\t\tstrbuf_addf(&buf, \"^%s\", private);\n+\t\t\tstring_list_append(strbuf_detach(&buf, NULL), &revlist_args);\n+\t\t}\n+\n+\t\tstring_list_append(ref->name, &revlist_args);\n+\n+\t}\n+\n+\tif (get_exporter(transport, &exporter,\n+\t\t\t export_marks, import_marks, &revlist_args))\n+\t\tdie(\"Couldn't run fast-export\");\n+\n+\tdata->no_disconnect_req = 1;\n+\tfinish_command(&exporter);\n+\tdisconnect_helper(transport);\n+\treturn 0;\n+}\n+\n+static int push_refs(struct transport *transport,\n+\t\tstruct ref *remote_refs, int flags)\n+{\n+\tstruct helper_data *data = transport->data;\n+\n+\tif (process_connect(transport, 1)) {\n+\t\tdo_take_over(transport);\n+\t\treturn transport->push_refs(transport, remote_refs, flags);\n+\t}\n+\n+\tif (!remote_refs) {\n+\t\tfprintf(stderr, \"No refs in common and none specified; doing nothing.\\n\"\n+\t\t\t\"Perhaps you should specify a branch such as 'master'.\\n\");\n+\t\treturn 0;\n+\t}\n+\n+\tif (data->push)\n+\t\treturn push_refs_with_push(transport, remote_refs, flags);\n+\n+\tif (data->export)\n+\t\treturn push_refs_with_export(transport, remote_refs, flags);\n+\n+\treturn -1;\n+}\n+\n+\n static int has_attribute(const char *attrs, const char *attr) {\n \tint len;\n \tif (!attrs)\n-- \n1.7.0.3.317.gbb04ec\n"},{"id":"138111","messageId":"1269881309-19690-7-git-send-email-srabbelier@gmail.com","threadId":"23238","inReplyTo":"1269881309-19690-1-git-send-email-srabbelier@gmail.com","subject":"[PATCH 6/7] remote-helpers: add testgit helper","fromName":"Sverre Rabbelier","fromEmail":"srabbelier@gmail.com","sentAt":"2010-03-29T16:48:28Z","receivedAt":"2010-03-29T16:48:28Z","isPatch":true,"sender":{"key":"srabbelier@gmail.com","avatar":"https://avatars.githubusercontent.com/u/3098?v=4"},"body":"Currently the remote helper infrastructure is only used by the curl\nhelper, which does not give a good impression of how remote helpers\ncan be used to interact with foreign repositories. Since implementing\nsuch a helper is non-trivial it would be good to have at least one\neasy-to-follow example demonstrating how to implement a helper that\ninteracts with a foreign vcs using fast-import/fast-export.\n\nThe testgit helper can be used to interact with remote git\nrepositories by prefixing the url with \"testgit::\".\n---\n\n\tWhile working on git-remote-hg I tested the export code by\n\tjust writing the fast-export stream to a file, and then\n\tmanually importing it in a repo.\n\n\tI then had the crazy idea to \"just write a native git helper\"\n\tthat would do just that. Turns out it's quite hard to get\n\teverything right. I guess that only shows that this example\n\tis really needed so that others can more easily create a\n\thelper.\n\n .gitignore                          |    1 +\n Makefile                            |    2 +\n git-remote-testgit.py               |  233 +++++++++++++++++++++++++++++++++++\n git_remote_helpers/git/exporter.py  |   51 ++++++++\n git_remote_helpers/git/importer.py  |   38 ++++++\n git_remote_helpers/git/non_local.py |   61 +++++++++\n git_remote_helpers/git/repo.py      |   70 +++++++++++\n 7 files changed, 456 insertions(+), 0 deletions(-)\n create mode 100644 git-remote-testgit.py\n create mode 100644 git_remote_helpers/git/exporter.py\n create mode 100644 git_remote_helpers/git/importer.py\n create mode 100644 git_remote_helpers/git/non_local.py\n create mode 100644 git_remote_helpers/git/repo.py\n\ndiff --git a/.gitignore b/.gitignore\nindex 7b3acb7..7aebd6b 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -112,6 +112,7 @@\n /git-remote-https\n /git-remote-ftp\n /git-remote-ftps\n+/git-remote-testgit\n /git-repack\n /git-replace\n /git-repo-config\ndiff --git a/Makefile b/Makefile\nindex 3a6c6ea..b1e5f61 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -366,6 +366,8 @@ SCRIPT_PERL += git-relink.perl\n SCRIPT_PERL += git-send-email.perl\n SCRIPT_PERL += git-svn.perl\n \n+SCRIPT_PYTHON += git-remote-testgit.py\n+\n SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \\\n \t  $(patsubst %.perl,%,$(SCRIPT_PERL)) \\\n \t  $(patsubst %.py,%,$(SCRIPT_PYTHON)) \\\ndiff --git a/git-remote-testgit.py b/git-remote-testgit.py\nnew file mode 100644\nindex 0000000..f61624e\n--- /dev/null\n+++ b/git-remote-testgit.py\n@@ -0,0 +1,233 @@\n+#!/usr/bin/env python\n+\n+import hashlib\n+import sys\n+\n+from git_remote_helpers.util import die, debug, warn\n+from git_remote_helpers.git.repo import GitRepo\n+from git_remote_helpers.git.exporter import GitExporter\n+from git_remote_helpers.git.importer import GitImporter\n+from git_remote_helpers.git.non_local import NonLocalGit\n+\n+def get_repo(alias, url):\n+    \"\"\"Returns a git repository object initialized for usage.\n+    \"\"\"\n+\n+    repo = GitRepo(url)\n+    repo.get_revs()\n+    repo.get_head()\n+\n+    hasher = hashlib.sha1()\n+    hasher.update(repo.path)\n+    repo.hash = hasher.hexdigest()\n+\n+    repo.get_base_path = lambda base: os.path.join(\n+        base, 'info', 'fast-import', repo.hash)\n+\n+    prefix = 'refs/testgit/%s/' % alias\n+    debug(\"prefix: '%s'\", prefix)\n+\n+    repo.gitdir = \"\"\n+    repo.alias = alias\n+    repo.prefix = prefix\n+\n+    repo.exporter = GitExporter(repo)\n+    repo.importer = GitImporter(repo)\n+    repo.non_local = NonLocalGit(repo)\n+\n+    return repo\n+\n+\n+def local_repo(repo, path):\n+    \"\"\"Returns a git repository object initalized for usage.\n+    \"\"\"\n+\n+    local = GitRepo(path)\n+\n+    local.non_local = None\n+    local.gitdir = repo.gitdir\n+    local.alias = repo.alias\n+    local.prefix = repo.prefix\n+    local.hash = repo.hash\n+    local.get_base_path = repo.get_base_path\n+    local.exporter = GitExporter(local)\n+    local.importer = GitImporter(local)\n+\n+    return local\n+\n+\n+def do_capabilities(repo, args):\n+    \"\"\"Prints the supported capabilities.\n+    \"\"\"\n+\n+    print \"import\"\n+    print \"export\"\n+    print \"gitdir\"\n+    print \"refspec refs/heads/*:%s*\" % repo.prefix\n+\n+    print # end capabilities\n+\n+\n+def do_list(repo, args):\n+    \"\"\"Lists all known references.\n+\n+    Bug: This will always set the remote head to master for non-local\n+    repositories, since we have no way of determining what the remote\n+    head is at clone time.\n+    \"\"\"\n+\n+    for ref in repo.revs:\n+        debug(\"? refs/heads/%s\", ref)\n+        print \"? refs/heads/%s\" % ref\n+\n+    if repo.head:\n+        debug(\"@refs/heads/%s HEAD\" % repo.head)\n+        print \"@refs/heads/%s HEAD\" % repo.head\n+    else:\n+        debug(\"@refs/heads/master HEAD\")\n+        print \"@refs/heads/master HEAD\"\n+\n+    print # end list\n+\n+\n+def update_local_repo(repo):\n+    \"\"\"Updates (or clones) a local repo.\n+    \"\"\"\n+\n+    if repo.local:\n+        return repo\n+\n+    path = repo.non_local.clone(repo.gitdir)\n+    repo.non_local.update(repo.gitdir)\n+    repo = local_repo(repo, path)\n+    return repo\n+\n+\n+def do_import(repo, args):\n+    \"\"\"Exports a fast-import stream from testgit for git to import.\n+    \"\"\"\n+\n+    if len(args) != 1:\n+        die(\"Import needs exactly one ref\")\n+\n+    if not repo.gitdir:\n+        die(\"Need gitdir to import\")\n+\n+    repo = update_local_repo(repo)\n+    repo.exporter.export_repo(repo.gitdir)\n+\n+\n+def do_export(repo, args):\n+    \"\"\"Imports a fast-import stream from git to testgit.\n+    \"\"\"\n+\n+    if not repo.gitdir:\n+        die(\"Need gitdir to export\")\n+\n+    dirname = repo.get_base_path(repo.gitdir)\n+\n+    if not os.path.exists(dirname):\n+        os.makedirs(dirname)\n+\n+    path = os.path.join(dirname, 'testgit.marks')\n+    print path\n+    print path if os.path.exists(path) else \"\"\n+    sys.stdout.flush()\n+\n+    update_local_repo(repo)\n+    repo.importer.do_import(repo.gitdir)\n+    repo.non_local.push(repo.gitdir)\n+\n+\n+def do_gitdir(repo, args):\n+    \"\"\"Stores the location of the gitdir.\n+    \"\"\"\n+\n+    if not args:\n+        die(\"gitdir needs an argument\")\n+\n+    repo.gitdir = ' '.join(args)\n+\n+\n+COMMANDS = {\n+    'capabilities': do_capabilities,\n+    'list': do_list,\n+    'import': do_import,\n+    'export': do_export,\n+    'gitdir': do_gitdir,\n+}\n+\n+\n+def sanitize(value):\n+    \"\"\"Cleans up the url.\n+    \"\"\"\n+\n+    if value.startswith('testgit::'):\n+        value = value[9:]\n+\n+    return value\n+\n+\n+def read_one_line(repo):\n+    \"\"\"Reads and processes one command.\n+    \"\"\"\n+\n+    line = sys.stdin.readline()\n+\n+    cmdline = line\n+\n+    if not cmdline:\n+        warn(\"Unexpected EOF\")\n+        return False\n+\n+    cmdline = cmdline.strip().split()\n+    if not cmdline:\n+        # Blank line means we're about to quit\n+        return False\n+\n+    cmd = cmdline.pop(0)\n+    debug(\"Got command '%s' with args '%s'\", cmd, ' '.join(cmdline))\n+\n+    if cmd not in COMMANDS:\n+        die(\"Unknown command, %s\", cmd)\n+\n+    func = COMMANDS[cmd]\n+    func(repo, cmdline)\n+    sys.stdout.flush()\n+\n+    return True\n+\n+\n+def main(args):\n+    \"\"\"Starts a new remote helper for the specified repository.\n+    \"\"\"\n+\n+    if len(args) != 3:\n+        die(\"Expecting exactly three arguments.\")\n+        sys.exit(1)\n+\n+    if os.getenv(\"GIT_DEBUG_TESTGIT\"):\n+        import git_remote_helpers.util\n+        git_remote_helpers.util.DEBUG = True\n+\n+    alias = sanitize(args[1])\n+    url = sanitize(args[2])\n+\n+    if not alias.isalnum():\n+        warn(\"non-alnum alias '%s'\", alias)\n+        alias = \"tmp\"\n+\n+    args[1] = alias\n+    args[2] = url\n+\n+    repo = get_repo(alias, url)\n+\n+    debug(\"Got arguments %s\", args[1:])\n+\n+    more = True\n+\n+    while (more):\n+        more = read_one_line(repo)\n+\n+if __name__ == '__main__':\n+    sys.exit(main(sys.argv))\ndiff --git a/git_remote_helpers/git/exporter.py b/git_remote_helpers/git/exporter.py\nnew file mode 100644\nindex 0000000..dfaab00\n--- /dev/null\n+++ b/git_remote_helpers/git/exporter.py\n@@ -0,0 +1,51 @@\n+import os\n+import subprocess\n+import sys\n+\n+\n+class GitExporter(object):\n+    \"\"\"An exporter for testgit repositories.\n+\n+    The exporter simply delegates to git fast-export.\n+    \"\"\"\n+\n+    def __init__(self, repo):\n+        \"\"\"Creates a new exporter for the specified repo.\n+        \"\"\"\n+\n+        self.repo = repo\n+\n+    def export_repo(self, base):\n+        \"\"\"Exports a fast-export stream for the given directory.\n+\n+        Simply delegates to git fast-epxort and pipes it through sed\n+        to make the refs show up under the prefix rather than the\n+        default refs/heads. This is to demonstrate how the export\n+        data can be stored under it's own ref (using the refspec\n+        capability).\n+        \"\"\"\n+\n+        dirname = self.repo.get_base_path(base)\n+        path = os.path.abspath(os.path.join(dirname, 'testgit.marks'))\n+\n+        if not os.path.exists(dirname):\n+            os.makedirs(dirname)\n+\n+        print \"feature relative-marks\"\n+        if os.path.exists(os.path.join(dirname, 'git.marks')):\n+            print \"feature import-marks=%s/git.marks\" % self.repo.hash\n+        print \"feature export-marks=%s/git.marks\" % self.repo.hash\n+        sys.stdout.flush()\n+\n+        args = [\"git\", \"--git-dir=\" + self.repo.gitpath, \"fast-export\", \"--export-marks=\" + path]\n+\n+        if os.path.exists(path):\n+            args.append(\"--import-marks=\" + path)\n+\n+        args.append(\"HEAD\")\n+\n+        p1 = subprocess.Popen(args, stdout=subprocess.PIPE)\n+\n+        args = [\"sed\", \"s_refs/heads/_\" + self.repo.prefix + \"_g\"]\n+\n+        subprocess.check_call(args, stdin=p1.stdout)\ndiff --git a/git_remote_helpers/git/importer.py b/git_remote_helpers/git/importer.py\nnew file mode 100644\nindex 0000000..af2919d\n--- /dev/null\n+++ b/git_remote_helpers/git/importer.py\n@@ -0,0 +1,38 @@\n+import os\n+import subprocess\n+\n+\n+class GitImporter(object):\n+    \"\"\"An importer for testgit repositories.\n+\n+    This importer simply delegates to git fast-import.\n+    \"\"\"\n+\n+    def __init__(self, repo):\n+        \"\"\"Creates a new importer for the specified repo.\n+        \"\"\"\n+\n+        self.repo = repo\n+\n+    def do_import(self, base):\n+        \"\"\"Imports a fast-import stream to the given directory.\n+\n+        Simply delegates to git fast-import.\n+        \"\"\"\n+\n+        dirname = self.repo.get_base_path(base)\n+        if self.repo.local:\n+            gitdir = self.repo.gitpath\n+        else:\n+            gitdir = os.path.abspath(os.path.join(dirname, '.git'))\n+        path = os.path.abspath(os.path.join(dirname, 'git.marks'))\n+\n+        if not os.path.exists(dirname):\n+            os.makedirs(dirname)\n+\n+        args = [\"git\", \"--git-dir=\" + gitdir, \"fast-import\", \"--quiet\", \"--export-marks=\" + path]\n+\n+        if os.path.exists(path):\n+            args.append(\"--import-marks=\" + path)\n+\n+        subprocess.check_call(args)\ndiff --git a/git_remote_helpers/git/non_local.py b/git_remote_helpers/git/non_local.py\nnew file mode 100644\nindex 0000000..d75ef8f\n--- /dev/null\n+++ b/git_remote_helpers/git/non_local.py\n@@ -0,0 +1,61 @@\n+import os\n+import subprocess\n+\n+from git_remote_helpers.util import die, warn\n+\n+\n+class NonLocalGit(object):\n+    \"\"\"Handler to interact with non-local repos.\n+    \"\"\"\n+\n+    def __init__(self, repo):\n+        \"\"\"Creates a new non-local handler for the specified repo.\n+        \"\"\"\n+\n+        self.repo = repo\n+\n+    def clone(self, base):\n+        \"\"\"Clones the non-local repo to base.\n+\n+        Does nothing if a clone already exists.\n+        \"\"\"\n+\n+        path = os.path.join(self.repo.get_base_path(base), '.git')\n+\n+        # already cloned\n+        if os.path.exists(path):\n+            return path\n+\n+        os.makedirs(path)\n+        args = [\"git\", \"clone\", \"--bare\", \"--quiet\", self.repo.gitpath, path]\n+\n+        subprocess.check_call(args)\n+\n+        return path\n+\n+    def update(self, base):\n+        \"\"\"Updates checkout of the non-local repo in base.\n+        \"\"\"\n+\n+        path = os.path.join(self.repo.get_base_path(base), '.git')\n+\n+        if not os.path.exists(path):\n+            die(\"could not find repo at %s\", path)\n+\n+        args = [\"git\", \"--git-dir=\" + path, \"fetch\", \"--quiet\", self.repo.gitpath]\n+        subprocess.check_call(args)\n+\n+        args = [\"git\", \"--git-dir=\" + path, \"update-ref\", \"refs/heads/master\", \"FETCH_HEAD\"]\n+        subprocess.check_call(args)\n+\n+    def push(self, base):\n+        \"\"\"Pushes from the non-local repo to base.\n+        \"\"\"\n+\n+        path = os.path.join(self.repo.get_base_path(base), '.git')\n+\n+        if not os.path.exists(path):\n+            die(\"could not find repo at %s\", path)\n+\n+        args = [\"git\", \"--git-dir=\" + path, \"push\", \"--quiet\", self.repo.gitpath]\n+        subprocess.check_call(args)\ndiff --git a/git_remote_helpers/git/repo.py b/git_remote_helpers/git/repo.py\nnew file mode 100644\nindex 0000000..82d5f78\n--- /dev/null\n+++ b/git_remote_helpers/git/repo.py\n@@ -0,0 +1,70 @@\n+import os\n+import subprocess\n+\n+def sanitize(rev, sep='\\t'):\n+    \"\"\"Converts a for-each-ref line to a name/value pair.\n+    \"\"\"\n+\n+    splitrev = rev.split(sep)\n+    branchval = splitrev[0]\n+    branchname = splitrev[1].strip()\n+    if branchname.startswith(\"refs/heads/\"):\n+        branchname = branchname[11:]\n+\n+    return branchname, branchval\n+\n+def is_remote(url):\n+    \"\"\"Checks whether the specified value is a remote url.\n+    \"\"\"\n+\n+    prefixes = [\"http\", \"file\", \"git\"]\n+\n+    return any(url.startswith(i) for i in prefixes)\n+\n+class GitRepo(object):\n+    \"\"\"Repo object representing a repo.\n+    \"\"\"\n+\n+    def __init__(self, path):\n+        \"\"\"Initializes a new repo at the given path.\n+        \"\"\"\n+\n+        self.path = path\n+        self.head = None\n+        self.revmap = {}\n+        self.local = not is_remote(self.path)\n+\n+        if(self.path.endswith('.git')):\n+            self.gitpath = self.path\n+        else:\n+            self.gitpath = os.path.join(self.path, '.git')\n+\n+        if self.local and not os.path.exists(self.gitpath):\n+            os.makedirs(self.gitpath)\n+\n+    def get_revs(self):\n+        \"\"\"Fetches all revs from the remote.\n+        \"\"\"\n+\n+        args = [\"git\", \"ls-remote\", self.gitpath]\n+        path = \".cached_revs\"\n+        ofile = open(path, \"w\")\n+\n+        subprocess.check_call(args, stdout=ofile)\n+        output = open(path).readlines()\n+        self.revmap = dict(sanitize(i) for i in output)\n+        if \"HEAD\" in self.revmap:\n+            del self.revmap[\"HEAD\"]\n+        self.revs = self.revmap.keys()\n+        ofile.close()\n+\n+    def get_head(self):\n+        \"\"\"Determines the head of a local repo.\n+        \"\"\"\n+\n+        if not self.local:\n+            return\n+\n+        path = os.path.join(self.gitpath, \"HEAD\")\n+        head = open(path).readline()\n+        self.head, _ = sanitize(head, ' ')\n-- \n1.7.0.3.317.gbb04ec\n"},{"id":"138112","messageId":"1269881309-19690-8-git-send-email-srabbelier@gmail.com","threadId":"23238","inReplyTo":"1269881309-19690-1-git-send-email-srabbelier@gmail.com","subject":"[PATCH 7/7] remote-helpers: add tests for testgit helper","fromName":"Sverre Rabbelier","fromEmail":"srabbelier@gmail.com","sentAt":"2010-03-29T16:48:29Z","receivedAt":"2010-03-29T16:48:29Z","isPatch":true,"sender":{"key":"srabbelier@gmail.com","avatar":"https://avatars.githubusercontent.com/u/3098?v=4"},"body":"---\n\n\tI used a httpd server for the remote at first, but figured\n\twe should probably not depend on those tests being run. It\n\twas fairly trivial to make the testgit helper treat file://\n\turls as 'remote', so I figured that'd work just as well.\n\n t/t5800-remote-helpers.sh |   70 +++++++++++++++++++++++++++++++++++++++++++++\n 1 files changed, 70 insertions(+), 0 deletions(-)\n create mode 100755 t/t5800-remote-helpers.sh\n\ndiff --git a/t/t5800-remote-helpers.sh b/t/t5800-remote-helpers.sh\nnew file mode 100755\nindex 0000000..33f546b\n--- /dev/null\n+++ b/t/t5800-remote-helpers.sh\n@@ -0,0 +1,70 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2010 Sverre Rabbelier\n+#\n+\n+test_description='Test remote-helper import and export commands'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'setup repository' '\n+\tgit init --bare server/.git &&\n+\tgit clone server public\n+\t(cd public &&\n+\t echo content >file &&\n+\t git add file &&\n+\t git commit -m one &&\n+\t git push origin master)\n+'\n+\n+test_expect_success 'cloning from local repo' '\n+\tgit clone \"testgit::${PWD}/server\" localclone\n+\ttest_cmp public/file localclone/file\n+'\n+\n+test_expect_success 'cloning from remote repo' '\n+\tgit clone \"testgit::file://${PWD}/server\" clone &&\n+\ttest_cmp public/file clone/file\n+'\n+\n+test_expect_success 'create new commit on remote' '\n+\t(cd public &&\n+\t echo content >>file &&\n+\t git commit -a -m two &&\n+\t git push)\n+'\n+\n+test_expect_success 'pulling from local repo' '\n+\t(cd localclone && git pull) &&\n+\ttest_cmp public/file localclone/file\n+'\n+\n+test_expect_success 'pulling from remote remote' '\n+\t(cd clone && git pull) &&\n+\ttest_cmp public/file clone/file\n+'\n+\n+test_expect_success 'pushing to local repo' '\n+\t(cd localclone &&\n+\techo content >>file &&\n+\tgit commit -a -m three &&\n+\tgit push) &&\n+\tHEAD=$(git --git-dir=localclone/.git rev-parse --verify HEAD) &&\n+\ttest $HEAD = $(git --git-dir=server/.git rev-parse --verify HEAD)\n+'\n+\n+test_expect_success 'synch with changes from localclone' '\n+\t(cd clone &&\n+\t git pull)\n+'\n+\n+test_expect_success 'pushing remote local repo' '\n+\t(cd clone &&\n+\techo content >>file &&\n+\tgit commit -a -m four &&\n+\tgit push) &&\n+\tHEAD=$(git --git-dir=clone/.git rev-parse --verify HEAD) &&\n+\ttest $HEAD = $(git --git-dir=server/.git rev-parse --verify HEAD)\n+'\n+\n+test_done\n-- \n1.7.0.3.317.gbb04ec\n"},{"id":"138116","messageId":"alpine.LNX.2.00.1003291256020.14365@iabervon.org","threadId":"23238","inReplyTo":"1269881309-19690-6-git-send-email-srabbelier@gmail.com","subject":"Re: [PATCH 5/7] remote-helpers: add support for an export command","fromName":"Daniel Barkalow","fromEmail":"barkalow@iabervon.org","sentAt":"2010-03-29T17:21:45Z","receivedAt":"2010-03-29T17:21:45Z","isPatch":true,"sender":{"key":"barkalow@iabervon.org","avatar":"https://avatars.githubusercontent.com/u/55364219?v=4"},"body":"On Mon, 29 Mar 2010, Sverre Rabbelier wrote:\n\n> ---\n> \n> \tThis complements the 'import' capability, making it possible\n> \tto use remote-helpers with only fast-import/fast-exports\n> \tstreams.\n\nIt would be good to have documentation on what the helper should do doing \nhere; it's relatively difficult to tell from this code.\n\nI think that it's getting some options (import and export marks) back from \nthe helper in a kind of ad-hoc and unclear fashion, and it should be doing \nsomething more appropriate. (I'm not sure what the more appropriate thing \nis, or really what the requirements are here as far as getting information \naround, but I think it needs to be discussed.)\n\n\t-Daniel\n*This .sig left intentionally blank*\n"},{"id":"138117","messageId":"fabb9a1e1003291028x45f0c570v4c10eadf37fc6a77@mail.gmail.com","threadId":"23238","inReplyTo":"alpine.LNX.2.00.1003291256020.14365@iabervon.org","subject":"Re: [PATCH 5/7] remote-helpers: add support for an export command","fromName":"Sverre Rabbelier","fromEmail":"srabbelier@gmail.com","sentAt":"2010-03-29T17:28:10Z","receivedAt":"2010-03-29T17:28:10Z","isPatch":true,"sender":{"key":"srabbelier@gmail.com","avatar":"https://avatars.githubusercontent.com/u/3098?v=4"},"body":"Heya,\n\nOn Mon, Mar 29, 2010 at 11:21, Daniel Barkalow <barkalow@iabervon.org> wrote:\n> It would be good to have documentation on what the helper should do doing\n> here; it's relatively difficult to tell from this code.\n\nYes, I plan to send a documentation patch as well but wanted to get\nthis out the door first (since Documentation patches don't have to\ncook in next).\n\n> I think that it's getting some options (import and export marks) back from\n> the helper in a kind of ad-hoc and unclear fashion, and it should be doing\n> something more appropriate.\n\nIt's not really that ad-hoc or unclear. When the helper receives the\nexport command it should write two lines, the first being the location\nwhere it should write the marks to, the second being the location\nwhere it should import marks from. Either line may be empty, meaning\nmarks should not be written or read respectively.\n\n> (I'm not sure what the more appropriate thing\n> is, or really what the requirements are here as far as getting information\n> around, but I think it needs to be discussed.)\n\nThe import command solves this by printing 'feature import-marks' and\n'feature export-marks' lines on the stream, but that doesn't work here\nsince fast-export doesn't read from stdin. I don't think this needs a\nseparate command.\n\n-- \nCheers,\n\nSverre Rabbelier\n"},{"id":"138319","messageId":"7v634bsrg5.fsf@alter.siamese.dyndns.org","threadId":"23238","inReplyTo":"1269881309-19690-6-git-send-email-srabbelier@gmail.com","subject":"Re: [PATCH 5/7] remote-helpers: add support for an export command","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2010-04-01T04:54:34Z","receivedAt":"2010-04-01T04:54:34Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Sverre Rabbelier <srabbelier@gmail.com> writes:\n\n> ---\n>\n> \tThis complements the 'import' capability, making it possible\n> \tto use remote-helpers with only fast-import/fast-exports\n> \tstreams.\n\nI've queued this in 'pu' only to say \"I've seen it\", but I suspect that\nthis has to wait until after 1.7.1.  I've been trying to shoot for 6-8\nweek cycle as originally envisioned, and if things go as planned, post\n1.7.1 cycle opens around mid April.\n\n> +\tfor(ref = remote_refs; ref; ref = ref->next) {\n> +\t\tchar* private;\n> +\t\tunsigned char sha1[20];\n> +\n> +\n> +\t\tif(data->refspecs)\n> +\t\t\tprivate = apply_refspecs(data->refspecs, data->refspec_nr, ref->name);\n> +\n> +\t\tif(private && !get_sha1(private, sha1))\n> +\t\t{\n> +\t\t\tstrbuf_addf(&buf, \"^%s\", private);\n> +\t\t\tstring_list_append(strbuf_detach(&buf, NULL), &revlist_args);\n> +\t\t}\n> +\n> +\t\tstring_list_append(ref->name, &revlist_args);\n> +\n> +\t}\n\nWhat's happening here?  Style violations aside, it appears that \"private\"\nis accessed uninitialized when data->refspecs is NULL.\n"},{"id":"138343","messageId":"h2ofabb9a1e1004010814v587eda10pe743efb1e269caad@mail.gmail.com","threadId":"23238","inReplyTo":"7v634bsrg5.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH 5/7] remote-helpers: add support for an export command","fromName":"Sverre Rabbelier","fromEmail":"srabbelier@gmail.com","sentAt":"2010-04-01T15:14:40Z","receivedAt":"2010-04-01T15:14:40Z","isPatch":true,"sender":{"key":"srabbelier@gmail.com","avatar":"https://avatars.githubusercontent.com/u/3098?v=4"},"body":"Heya,\n\nOn Wed, Mar 31, 2010 at 22:54, Junio C Hamano <gitster@pobox.com> wrote:\n> I've queued this in 'pu' only to say \"I've seen it\", but I suspect that\n> this has to wait until after 1.7.1.  I've been trying to shoot for 6-8\n> week cycle as originally envisioned, and if things go as planned, post\n> 1.7.1 cycle opens around mid April.\n\nOkay, fair enough.\n\n> What's happening here?  Style violations aside, it appears that \"private\"\n> is accessed uninitialized when data->refspecs is NULL.\n\nAh, you are right, it should have been \"char *private = NULL\".\n\n-- \nCheers,\n\nSverre Rabbelier\n"},{"id":"139335","messageId":"20100412142428.GA3839@progeny.tock","threadId":"23238","inReplyTo":"1269881309-19690-8-git-send-email-srabbelier@gmail.com","subject":"Re: [PATCH 7/7] remote-helpers: add tests for testgit helper","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2010-04-12T14:24:28Z","receivedAt":"2010-04-12T14:24:28Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Hi Sverre,\n\nSverre Rabbelier wrote:\n\n>  t/t5800-remote-helpers.sh |   70 +++++++++++++++++++++++++++++++++++++++++++++\n>  1 files changed, 70 insertions(+), 0 deletions(-)\n>  create mode 100755 t/t5800-remote-helpers.sh\n\nSorry I missed this before.  Maybe it would make sense to squash\nsomething like this in.\n\n-- 8< --\nSubject: t5800: testgit helper requires Python support\n\ngit remote-testgit is written in Python.  In a NO_PYTHON build, tests\nusing it would fail, so skip them.\n\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n t/t5800-remote-helpers.sh |    6 ++++++\n 1 files changed, 6 insertions(+), 0 deletions(-)\n\ndiff --git a/t/t5800-remote-helpers.sh b/t/t5800-remote-helpers.sh\nindex eb31709..75a0163 100755\n--- a/t/t5800-remote-helpers.sh\n+++ b/t/t5800-remote-helpers.sh\n@@ -7,6 +7,12 @@ test_description='Test remote-helper import and export commands'\n \n . ./test-lib.sh\n \n+if ! test_have_prereq PYTHON\n+then\n+\tsay 'skipping git remote-testgit tests: requires Python support'\n+\ttest_done\n+fi\n+\n test_expect_success 'setup repository' '\n \tgit init --bare server/.git &&\n \tgit clone server public &&\n-- \n1.7.0.4\n"},{"id":"139336","messageId":"h2qfabb9a1e1004120726q350e3361gcfb2b89e6b4bb18a@mail.gmail.com","threadId":"23238","inReplyTo":"20100412142428.GA3839@progeny.tock","subject":"Re: [PATCH 7/7] remote-helpers: add tests for testgit helper","fromName":"Sverre Rabbelier","fromEmail":"srabbelier@gmail.com","sentAt":"2010-04-12T14:26:53Z","receivedAt":"2010-04-12T14:26:53Z","isPatch":true,"sender":{"key":"srabbelier@gmail.com","avatar":"https://avatars.githubusercontent.com/u/3098?v=4"},"body":"Heya,\n\nOn Mon, Apr 12, 2010 at 16:24, Jonathan Nieder <jrnieder@gmail.com> wrote:\n> Sorry I missed this before.  Maybe it would make sense to squash\n> something like this in.\n\nSorry for not thinking of this, thanks for noticing.\n\n-- \nCheers,\n\nSverre Rabbelier\n"}]}