{"thread":{"id":"10057","subject":"[PATCH 0/2] rsync support","startedAt":"2007-09-28T05:06:28Z","lastAt":"2007-09-30T12:58:29Z","messageCount":19,"participants":["Johannes Schindelin","Shawn O. Pearce","Junio C Hamano","David Kastrup"],"isPatch":true,"patchVersion":1,"patchTotal":2},"messages":[{"id":"54232","messageId":"Pine.LNX.4.64.0709280602580.28395@racer.site","threadId":"10057","inReplyTo":null,"subject":"[PATCH 0/2] rsync support","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-09-28T05:06:28Z","receivedAt":"2007-09-28T05:06:28Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nI worked too long on this... So here it is, a 2-patch series to readd the \nrsync support.\n\nOne thing I did not do: fetch alternates.  My rationale is this: just as \nwith HTTP, the rsync protocol does allow for having a different root than \nthe file system root.  So, just as with HTTP, the alternates are likely to \nbreak.\n\nBut hey, if somebody feels like it, there is a NEEDSWORK pointer from \nwhich to start.\n\nCiao,\nDscho\n"},{"id":"54233","messageId":"Pine.LNX.4.64.0709280606350.28395@racer.site","threadId":"10057","inReplyTo":"Pine.LNX.4.64.0709280602580.28395@racer.site","subject":"[PATCH 1/2] Introduce remove_dir_recursively()","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-09-28T05:06:44Z","receivedAt":"2007-09-28T05:06:44Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"\nThere was a function called remove_empty_dir_recursive() buried\nin refs.c.  Expose a slightly enhanced version in dir.h: it can now\noptionally remove a non-empty directory.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n dir.c  |   45 +++++++++++++++++++++++++++++++++++++++++++++\n dir.h  |    2 ++\n refs.c |   36 ++----------------------------------\n 3 files changed, 49 insertions(+), 34 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex eb6c3ab..0fae400 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -685,3 +685,48 @@ int is_inside_dir(const char *dir)\n \tchar buffer[PATH_MAX];\n \treturn get_relative_cwd(buffer, sizeof(buffer), dir) != NULL;\n }\n+\n+int remove_dir_recursively(char *path, int len, int only_empty)\n+{\n+\tDIR *dir = opendir(path);\n+\tstruct dirent *e;\n+\tint ret = 0;\n+\n+\tif (!dir)\n+\t\treturn -1;\n+\tif (path[len-1] != '/')\n+\t\tpath[len++] = '/';\n+\twhile ((e = readdir(dir)) != NULL) {\n+\t\tstruct stat st;\n+\t\tint namlen;\n+\t\tif ((e->d_name[0] == '.') &&\n+\t\t    ((e->d_name[1] == 0) ||\n+\t\t     ((e->d_name[1] == '.') && e->d_name[2] == 0)))\n+\t\t\tcontinue; /* \".\" and \"..\" */\n+\n+\t\tnamlen = strlen(e->d_name);\n+\t\tif (len + namlen > PATH_MAX ||\n+\t\t\t\t!memcpy(path + len, e->d_name, namlen) ||\n+\t\t\t\t(path[len + namlen] = '\\0') ||\n+\t\t\t\tlstat(path, &st))\n+\t\t\t; /* fall thru */\n+\t\telse if (S_ISDIR(st.st_mode)) {\n+\t\t\tif (!remove_dir_recursively(path, len + namlen,\n+\t\t\t\t\t\tonly_empty))\n+\t\t\t\tcontinue; /* happy */\n+\t\t} else if (!only_empty &&\n+\t\t\t\tlen + namlen + 1 < PATH_MAX &&\n+\t\t\t\t!unlink(path))\n+\t\t\tcontinue; /* happy, too */\n+\n+\t\t/* path too long, stat fails, or non-directory still exists */\n+\t\tret = -1;\n+\t\tbreak;\n+\t}\n+\tclosedir(dir);\n+\tif (!ret) {\n+\t\tpath[len] = 0;\n+\t\tret = rmdir(path);\n+\t}\n+\treturn ret;\n+}\ndiff --git a/dir.h b/dir.h\nindex f55a87b..f52eea8 100644\n--- a/dir.h\n+++ b/dir.h\n@@ -64,4 +64,6 @@ extern struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathna\n extern char *get_relative_cwd(char *buffer, int size, const char *dir);\n extern int is_inside_dir(const char *dir);\n \n+extern int remove_dir_recursively(char *dir, int len, int only_empty);\n+\n #endif\ndiff --git a/refs.c b/refs.c\nindex 07e260c..f0336ee 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2,6 +2,7 @@\n #include \"refs.h\"\n #include \"object.h\"\n #include \"tag.h\"\n+#include \"dir.h\"\n \n /* ISSYMREF=01 and ISPACKED=02 are public interfaces */\n #define REF_KNOWS_PEELED 04\n@@ -673,40 +674,7 @@ static struct ref_lock *verify_lock(struct ref_lock *lock,\n \n static int remove_empty_dir_recursive(char *path, int len)\n {\n-\tDIR *dir = opendir(path);\n-\tstruct dirent *e;\n-\tint ret = 0;\n-\n-\tif (!dir)\n-\t\treturn -1;\n-\tif (path[len-1] != '/')\n-\t\tpath[len++] = '/';\n-\twhile ((e = readdir(dir)) != NULL) {\n-\t\tstruct stat st;\n-\t\tint namlen;\n-\t\tif ((e->d_name[0] == '.') &&\n-\t\t    ((e->d_name[1] == 0) ||\n-\t\t     ((e->d_name[1] == '.') && e->d_name[2] == 0)))\n-\t\t\tcontinue; /* \".\" and \"..\" */\n-\n-\t\tnamlen = strlen(e->d_name);\n-\t\tif ((len + namlen < PATH_MAX) &&\n-\t\t    strcpy(path + len, e->d_name) &&\n-\t\t    !lstat(path, &st) &&\n-\t\t    S_ISDIR(st.st_mode) &&\n-\t\t    !remove_empty_dir_recursive(path, len + namlen))\n-\t\t\tcontinue; /* happy */\n-\n-\t\t/* path too long, stat fails, or non-directory still exists */\n-\t\tret = -1;\n-\t\tbreak;\n-\t}\n-\tclosedir(dir);\n-\tif (!ret) {\n-\t\tpath[len] = 0;\n-\t\tret = rmdir(path);\n-\t}\n-\treturn ret;\n+\treturn remove_dir_recursively(path, len, 1);\n }\n \n static int remove_empty_directories(char *file)\n-- \n1.5.3.2.1102.g9487\n"},{"id":"54234","messageId":"Pine.LNX.4.64.0709280606530.28395@racer.site","threadId":"10057","inReplyTo":"Pine.LNX.4.64.0709280602580.28395@racer.site","subject":"[PATCH 2/2] fetch/push: readd rsync support","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-09-28T05:07:19Z","receivedAt":"2007-09-28T05:07:19Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"\nWe lost rsync support when transitioning from shell to C.  Support it\nagain (even if the transport is technically deprecated, some people just\ndo not have any chance to use anything else).\n\nAlso, add a test to t5510.  Since rsync transport is not configured by\ndefault on most machines, and especially not such that you can\nwrite to rsync://127.0.0.1$(pwd)/, it is disabled by default; you can\nenable it by setting the environment variable TEST_RSYNC.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n\n\tMaybe this should be moved into transport-rsync.c...\n\n t/t5510-fetch.sh |   35 ++++++\n transport.c      |  333 +++++++++++++++++++++++++++++++++++++++++++++++++++++-\n 2 files changed, 367 insertions(+), 1 deletions(-)\n\ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex 439430f..73a4e3c 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -153,4 +153,39 @@ test_expect_success 'bundle should be able to create a full history' '\n \n '\n \n+test \"$TEST_RSYNC\" && {\n+test_expect_success 'fetch via rsync' '\n+\tgit pack-refs &&\n+\tmkdir rsynced &&\n+\tcd rsynced &&\n+\tgit init &&\n+\tgit fetch rsync://127.0.0.1$(pwd)/../.git master:refs/heads/master &&\n+\tgit gc --prune &&\n+\ttest $(git rev-parse master) = $(cd .. && git rev-parse master) &&\n+\tgit fsck --full\n+'\n+\n+test_expect_success 'push via rsync' '\n+\tmkdir ../rsynced2 &&\n+\t(cd ../rsynced2 &&\n+\t git init) &&\n+\tgit push rsync://127.0.0.1$(pwd)/../rsynced2/.git master &&\n+\tcd ../rsynced2 &&\n+\tgit gc --prune &&\n+\ttest $(git rev-parse master) = $(cd .. && git rev-parse master) &&\n+\tgit fsck --full\n+'\n+\n+test_expect_success 'push via rsync' '\n+\tcd .. &&\n+\tmkdir rsynced3 &&\n+\t(cd rsynced3 &&\n+\t git init) &&\n+\tgit push --all rsync://127.0.0.1$(pwd)/rsynced3/.git &&\n+\tcd rsynced3 &&\n+\ttest $(git rev-parse master) = $(cd .. && git rev-parse master) &&\n+\tgit fsck --full\n+'\n+}\n+\n test_done\ndiff --git a/transport.c b/transport.c\nindex 4f9cddc..d50a311 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -6,6 +6,334 @@\n #include \"fetch-pack.h\"\n #include \"walker.h\"\n #include \"bundle.h\"\n+#include \"dir.h\"\n+#include \"refs.h\"\n+\n+/* rsync support */\n+\n+/*\n+ * We copy packed-refs and refs/ into a temporary file, then read the\n+ * loose refs recursively (sorting whenever possible), and then inserting\n+ * those packed refs that are not yet in the list (not validating, but\n+ * assuming that the file is sorted).\n+ *\n+ * Appears refactoring this from refs.c is too cumbersome.\n+ */\n+\n+static int direntry_cmp(const void *a, const void *b)\n+{\n+\tconst struct dirent *d1 = a;\n+\tconst struct dirent *d2 = b;\n+\n+\treturn strcmp(d1->d_name, d2->d_name);\n+}\n+\n+/*\n+ * path is assumed to point to a buffer of PATH_MAX bytes, and\n+ * path + name_offset is expected to point to \"refs/\".\n+ */\n+\n+static int read_loose_refs(char *path, int name_offset, struct ref **tail)\n+{\n+\tDIR *dir = opendir(path);\n+\tstruct dirent *de;\n+\tstruct {\n+\t\tstruct dirent *entries;\n+\t\tint nr, alloc;\n+\t} list;\n+\tint i, pathlen;\n+\n+\tif (!dir)\n+\t\treturn -1;\n+\n+\tmemset (&list, 0, sizeof(list));\n+\n+\twhile ((de = readdir(dir))) {\n+\t\tif (de->d_name[0] == '.' && (de->d_name[1] == '\\0' ||\n+\t\t\t\t(de->d_name[1] == '.' &&\n+\t\t\t\t de->d_name[2] == '\\0')))\n+\t\t\tcontinue;\n+\t\tif (list.nr >= list.alloc) {\n+\t\t\tlist.alloc = alloc_nr(list.nr);\n+\t\t\tlist.entries = xrealloc(list.entries,\n+\t\t\t\tlist.alloc * sizeof(*de));\n+\t\t}\n+\t\tlist.entries[list.nr++] = *de;\n+\t}\n+\tclosedir(dir);\n+\n+\t/* sort the list */\n+\n+\tqsort(list.entries, list.nr, sizeof(*de), direntry_cmp);\n+\n+\tpathlen = strlen(path);\n+\tpath[pathlen] = '/';\n+\n+\tfor (i = 0; i < list.nr; i++) {\n+\t\tstrlcpy(path + pathlen + 1, list.entries[i].d_name,\n+\t\t\t\tPATH_MAX - pathlen - 1);\n+\t\tif (read_loose_refs(path, name_offset, tail)) {\n+\t\t\tint fd = open(path, O_RDONLY);\n+\t\t\tchar buffer[40];\n+\t\t\tstruct ref *next;\n+\n+\t\t\tif (fd < 0)\n+\t\t\t\tcontinue;\n+\t\t\tnext = alloc_ref(strlen(path + name_offset));\n+\t\t\tif (read_in_full(fd, buffer, 40) != 40 ||\n+\t\t\t\t\tget_sha1_hex(buffer, next->old_sha1)) {\n+\t\t\t\tfree(next);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\tclose(fd);\n+\t\t\tstrcpy(next->name, path + name_offset);\n+\t\t\t(*tail)->next = next;\n+\t\t\t*tail = next;\n+\t\t}\n+\t}\n+\n+\tpath[pathlen] = '\\0';\n+\n+\tfree(list.entries);\n+\treturn 0;\n+}\n+\n+/* insert the packed refs for which no loose refs were found */\n+\n+static void insert_packed_refs(const char *packed_refs, struct ref **list)\n+{\n+\tFILE *f = fopen(packed_refs, \"r\");\n+\tstatic char buffer[PATH_MAX];\n+\n+\tif (!f)\n+\t\treturn;\n+\n+\tfor (;;) {\n+\t\tint cmp, len;\n+\n+\t\tif (!fgets(buffer, sizeof(buffer), f)) {\n+\t\t\tfclose(f);\n+\t\t\treturn;\n+\t\t}\n+\n+\t\tif (hexval(buffer[0]) & 0x10)\n+\t\t\tcontinue;\n+\t\tlen = strlen(buffer);\n+\t\tif (buffer[len - 1] == '\\n')\n+\t\t\tbuffer[--len] = '\\0';\n+\t\tif (len < 41)\n+\t\t\tcontinue;\n+\t\twhile ((*list)->next &&\n+\t\t\t\t(cmp = strcmp(buffer + 41,\n+\t\t\t\t      (*list)->next->name)) > 0)\n+\t\t\tlist = &(*list)->next;\n+\t\tif (!(*list)->next || cmp < 0) {\n+\t\t\tstruct ref *next = alloc_ref(len - 40);\n+\t\t\tbuffer[40] = '\\0';\n+\t\t\tif (get_sha1_hex(buffer, next->old_sha1)) {\n+\t\t\t\twarning (\"invalid SHA-1: %s\", buffer);\n+\t\t\t\tfree(next);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\tstrcpy(next->name, buffer + 41);\n+\t\t\tnext->next = (*list)->next;\n+\t\t\t(*list)->next = next;\n+\t\t\tlist = &(*list)->next;\n+\t\t}\n+\t}\n+}\n+\n+static struct ref *get_refs_via_rsync(const struct transport *transport)\n+{\n+\tchar buffer[PATH_MAX];\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct ref dummy, *result = &dummy;\n+\tstruct child_process rsync;\n+\tconst char *args[5];\n+\tchar *temp_dir;\n+\n+\t/* copy the refs to the temporary directory */\n+\n+\ttemp_dir = mkdtemp(git_path(\"rsync-refs-XXXXXX\"));\n+\tif (temp_dir == NULL)\n+\t\tdie (\"Could not make temporary directory\");\n+\ttemp_dir = strcpy(buffer, temp_dir);\n+\n+\tstrbuf_addstr(&buf, transport->url);\n+\tstrbuf_addstr(&buf, \"/refs\");\n+\n+\tmemset(&rsync, 0, sizeof(rsync));\n+\trsync.argv = args;\n+\trsync.stdout_to_stderr = 1;\n+\targs[0] = \"rsync\";\n+\targs[1] = transport->verbose ? \"-rv\" : \"-r\";\n+\targs[2] = buf.buf;\n+\targs[3] = temp_dir;\n+\targs[4] = NULL;\n+\n+\tif (run_command(&rsync))\n+\t\tdie (\"Could not run rsync to get refs\");\n+\n+\tstrbuf_reset(&buf);\n+\tstrbuf_addstr(&buf, transport->url);\n+\tstrbuf_addstr(&buf, \"/packed-refs\");\n+\n+\targs[2] = buf.buf;\n+\n+\tif (run_command(&rsync))\n+\t\tdie (\"Could not run rsync to get refs\");\n+\n+\t/* read the copied refs */\n+\n+\tstrbuf_reset(&buf);\n+\tstrbuf_addstr(&buf, temp_dir);\n+\tstrbuf_addstr(&buf, \"/refs\");\n+\tread_loose_refs(buf.buf, strlen(buf.buf) - 4, &result);\n+\n+\tresult = &dummy;\n+\tstrbuf_reset(&buf);\n+\tstrbuf_addstr(&buf, temp_dir);\n+\tstrbuf_addstr(&buf, \"/packed-refs\");\n+\tinsert_packed_refs(buf.buf, &result);\n+\n+\tif (remove_dir_recursively(temp_dir, strlen(temp_dir), 0))\n+\t\twarning (\"Could not remove temporary directory %s.\", temp_dir);\n+\n+\treturn dummy.next;\n+}\n+\n+static int fetch_objs_via_rsync(struct transport *transport,\n+\t\t\t\t int nr_objs, struct ref **to_fetch)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct child_process rsync;\n+\tconst char *args[8];\n+\n+\tstrbuf_addstr(&buf, transport->url);\n+\tstrbuf_addstr(&buf, \"/objects/\");\n+\n+\tmemset(&rsync, 0, sizeof(rsync));\n+\trsync.argv = args;\n+\trsync.stdout_to_stderr = 1;\n+\targs[0] = \"rsync\";\n+\targs[1] = transport->verbose ? \"-rv\" : \"-r\";\n+\targs[2] = \"--ignore-existing\";\n+\targs[3] = \"--exclude\";\n+\targs[4] = \"info\";\n+\targs[5] = buf.buf;\n+\targs[6] = get_object_directory();\n+\targs[7] = NULL;\n+\n+\t/* NEEDSWORK: handle one level of alternates */\n+\treturn run_command(&rsync);\n+}\n+\n+static int write_one_ref(const char *name, const unsigned char *sha1,\n+\t\tint flags, void *data)\n+{\n+\tstruct strbuf *buf = data;\n+\tint len = buf->len;\n+\tFILE *f;\n+\n+\tif (flags && prefixcmp(name, \"refs/heads/\") &&\n+\t\t\tprefixcmp(name, \"refs/tags/\"))\n+\t\treturn 0;\n+\n+\tstrbuf_addstr(buf, name);\n+\tif (safe_create_leading_directories(buf->buf) ||\n+\t\t\t!(f = fopen(buf->buf, \"w\")) ||\n+\t\t\t!fwrite(sha1_to_hex(sha1), 40, 1, f) ||\n+\t\t\tfputc('\\n', f) == EOF ||\n+\t\t\tfclose(f))\n+\t\treturn error(\"problems writing temporary file %s\", buf->buf);\n+\tstrbuf_setlen(buf, len);\n+\treturn 0;\n+}\n+\n+static int write_refs_to_temp_dir(struct strbuf *temp_dir,\n+\t\tint refspec_nr, const char **refspec)\n+{\n+\tint i;\n+\n+\tfor (i = 0; i < refspec_nr; i++) {\n+\t\tunsigned char sha1[20];\n+\t\tchar *ref;\n+\n+\t\tif (dwim_ref(refspec[i], strlen(refspec[i]), sha1, &ref) != 1)\n+\t\t\treturn error(\"Could not get ref %s\", refspec[i]);\n+\n+\t\tif (write_one_ref(ref, sha1, 0, temp_dir)) {\n+\t\t\tfree(ref);\n+\t\t\treturn -1;\n+\t\t}\n+\t\tfree(ref);\n+\t}\n+\treturn 0;\n+}\n+\n+static int rsync_transport_push(struct transport *transport,\n+\t\tint refspec_nr, const char **refspec, int flags) {\n+\tchar buffer[PATH_MAX];\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint result = 0, i;\n+\tstruct child_process rsync;\n+\tconst char *args[8];\n+\tchar *temp_dir;\n+\n+\t/* first push the objects */\n+\n+\tstrbuf_addstr(&buf, transport->url);\n+\tstrbuf_addch(&buf, '/');\n+\n+\tmemset(&rsync, 0, sizeof(rsync));\n+\trsync.argv = args;\n+\trsync.stdout_to_stderr = 1;\n+\targs[0] = \"rsync\";\n+\targs[1] = transport->verbose ? \"-av\" : \"-a\";\n+\targs[2] = \"--ignore-existing\";\n+\targs[3] = \"--exclude\";\n+\targs[4] = \"info\";\n+\targs[5] = get_object_directory();;\n+\targs[6] = buf.buf;\n+\targs[7] = NULL;\n+\n+\tif (run_command(&rsync))\n+\t\treturn error(\"Could not push objects to %s\", transport->url);\n+\n+\t/* copy the refs to the temporary directory; they could be packed. */\n+\n+\ttemp_dir = mkdtemp(git_path(\"rsync-refs-XXXXXX\"));\n+\tif (temp_dir == NULL)\n+\t\tdie (\"Could not make temporary directory\");\n+\ttemp_dir = strcpy(buffer, temp_dir);\n+\n+\tstrbuf_reset(&buf);\n+\tstrbuf_addstr(&buf, temp_dir);\n+\tstrbuf_addch(&buf, '/');\n+\n+\tif (flags & TRANSPORT_PUSH_ALL) {\n+\t\tif (for_each_ref(write_one_ref, &buf))\n+\t\t\treturn -1;\n+\t} else if (write_refs_to_temp_dir(&buf, refspec_nr, refspec))\n+\t\treturn -1;\n+\n+\ti = (flags & TRANSPORT_PUSH_FORCE) ? 2 : 3;\n+\targs[i++] = buf.buf;\n+\targs[i++] = transport->url;\n+\targs[i++] = NULL;\n+\tif (run_command(&rsync))\n+\t\tresult = error(\"Could not push to %s\", transport->url);\n+\n+\tif (remove_dir_recursively(temp_dir, strlen(temp_dir), 0))\n+\t\twarning (\"Could not remove temporary directory %s.\", temp_dir);\n+\n+\treturn result;\n+}\n+\n+static int disconnect_rsync(struct transport *transport)\n+{\n+\treturn 0;\n+}\n \n /* Generic functions for using commit walkers */\n \n@@ -402,7 +730,10 @@ struct transport *transport_get(struct remote *remote, const char *url)\n \tret->url = url;\n \n \tif (!prefixcmp(url, \"rsync://\")) {\n-\t\t/* not supported; don't populate any ops */\n+\t\tret->get_refs_list = get_refs_via_rsync;\n+\t\tret->fetch = fetch_objs_via_rsync;\n+\t\tret->push = rsync_transport_push;\n+\t\tret->disconnect = disconnect_rsync;\n \n \t} else if (!prefixcmp(url, \"http://\")\n \t        || !prefixcmp(url, \"https://\")\n-- \n1.5.3.2.1102.g9487\n"},{"id":"54237","messageId":"20070928052018.GW3099@spearce.org","threadId":"10057","inReplyTo":"Pine.LNX.4.64.0709280606530.28395@racer.site","subject":"Re: [PATCH 2/2] fetch/push: readd rsync support","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2007-09-28T05:20:18Z","receivedAt":"2007-09-28T05:20:18Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> wrote:\n> +static int disconnect_rsync(struct transport *transport)\n> +{\n> +\treturn 0;\n> +}\n>  \n>  /* Generic functions for using commit walkers */\n>  \n> @@ -402,7 +730,10 @@ struct transport *transport_get(struct remote *remote, const char *url)\n>  \tret->url = url;\n>  \n>  \tif (!prefixcmp(url, \"rsync://\")) {\n> -\t\t/* not supported; don't populate any ops */\n> +\t\tret->get_refs_list = get_refs_via_rsync;\n> +\t\tret->fetch = fetch_objs_via_rsync;\n> +\t\tret->push = rsync_transport_push;\n> +\t\tret->disconnect = disconnect_rsync;\n>  \n>  \t} else if (!prefixcmp(url, \"http://\")\n>  \t        || !prefixcmp(url, \"https://\")\n\nFor what it's worth disconnect is an optional operation.  You did\nnot need to implement it if you don't allocate a data member in\nthe struct transport.  So removing disconnect_rsync() could save\nyou 6 lines or so.\n\nI see push is now supported again.  Didn't we remove rsync push\nsupport a long time ago?  Like say in:\n\n  commit c485104741ccdf32dd0c96fcb886c38a0b5badbd\n  Author: c.shoemaker@cox.net <c.shoemaker@cox.net>\n  Date:   Sat Oct 29 00:16:33 2005 -0400\n\n    Add usage help to git-push.sh\n    \n    Also clarify failure to push to read-only remote.  Especially,\n    state why rsync:// is not used for pushing.\n    \n    [jc: ideally rsync should not be used for anything]\n    \n    Signed-off-by: Chris Shoemaker <c.shoemaker at cox.net>\n    Signed-off-by: Junio C Hamano <junkio@cox.net>\n\nI guess it is nice to see that you can't kill rsync.  Like Windows\nit always finds it way back into your life.\n\n-- \nShawn.\n"},{"id":"54248","messageId":"7v8x6rqhwy.fsf@gitster.siamese.dyndns.org","threadId":"10057","inReplyTo":"Pine.LNX.4.64.0709280606350.28395@racer.site","subject":"Re: [PATCH 1/2] Introduce remove_dir_recursively()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2007-09-28T09:05:17Z","receivedAt":"2007-09-28T09:05:17Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> +int remove_dir_recursively(char *path, int len, int only_empty)\n> +{\n> ...\n> +\t\tnamlen = strlen(e->d_name);\n> +\t\tif (len + namlen > PATH_MAX ||\n> +\t\t\t\t!memcpy(path + len, e->d_name, namlen) ||\n> +\t\t\t\t(path[len + namlen] = '\\0') ||\n> +\t\t\t\tlstat(path, &st))\n> +\t\t\t; /* fall thru */\n> +\t\telse if (S_ISDIR(st.st_mode)) {\n> +\t\t\tif (!remove_dir_recursively(path, len + namlen,\n> +\t\t\t\t\t\tonly_empty))\n> +\t\t\t\tcontinue; /* happy */\n> +\t\t} else if (!only_empty &&\n> +\t\t\t\tlen + namlen + 1 < PATH_MAX &&\n> +\t\t\t\t!unlink(path))\n> +\t\t\tcontinue; /* happy, too */\n> +\n> +\t\t/* path too long, stat fails, or non-directory still exists */\n> +\t\tret = -1;\n> +\t\tbreak;\n\nIs it only me who finds the first if () condition way too\nconvoluted and needs to read three times to convince oneself\nthat it is doing a sane thing?\n\nPlease, especially...\n\n * For $DEITY's sake, memcpy() returns pointer to dst which you\n   know is not NULL. so !memcpy() is always false here, which\n   might be _convenient_ for you and the compiler but not for\n   a human reader of the code who needs to blink twice wondering\n   if you meant !memcmp().\n\n * Same for (path[] = '\\0'), wondering if it is misspelled\n   (path[] == '\\0').\n"},{"id":"54251","messageId":"7v3awzqgqc.fsf@gitster.siamese.dyndns.org","threadId":"10057","inReplyTo":"Pine.LNX.4.64.0709280606530.28395@racer.site","subject":"Re: [PATCH 2/2] fetch/push: readd rsync support","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2007-09-28T09:30:51Z","receivedAt":"2007-09-28T09:30:51Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> +/*\n> + * path is assumed to point to a buffer of PATH_MAX bytes, and\n> + * path + name_offset is expected to point to \"refs/\".\n> + */\n> +\n> +static int read_loose_refs(char *path, int name_offset, struct ref **tail)\n> +{\n> +\tDIR *dir = opendir(path);\n> +\tstruct dirent *de;\n> +\tstruct {\n> +\t\tstruct dirent *entries;\n> +\t\tint nr, alloc;\n> +\t} list;\n> +\tint i, pathlen;\n> +\n> +\tif (!dir)\n> +\t\treturn -1;\n> +\n> +\tmemset (&list, 0, sizeof(list));\n> +\n> +\twhile ((de = readdir(dir))) {\n> +\t\tif (de->d_name[0] == '.' && (de->d_name[1] == '\\0' ||\n> +\t\t\t\t(de->d_name[1] == '.' &&\n> +\t\t\t\t de->d_name[2] == '\\0')))\n> +\t\t\tcontinue;\n> +\t\tif (list.nr >= list.alloc) {\n> +\t\t\tlist.alloc = alloc_nr(list.nr);\n> +\t\t\tlist.entries = xrealloc(list.entries,\n> +\t\t\t\tlist.alloc * sizeof(*de));\n> +\t\t}\n\nALLOC_GROW() not applicable here?\n\n> +\t\tlist.entries[list.nr++] = *de;\n\nAre you sure about this?\n\nThe last paragraph in Rationale section, in\n\n    http://www.opengroup.org/onlinepubs/000095399/basedefs/dirent.h.html\n\nsuggests that the d_name[] member in struct dirent could be\ndeclared at the very end of the structure as length of 1 (the\ntraditional trick to implement a flex-array); your assignment\nfrom *de into entries[] would not work as expected on such an\nimplementation.\n\nOn Linux with glibc it appears bits/dirent.h defines dirent with\n\"char d_name[256]\", so you may not see a breakage there, though.\n\nYou only use a list of strings (char **), don't you?\n\n> ...\n> +\t\t\tif (fd < 0)\n> +\t\t\t\tcontinue;\n> +\t\t\tnext = alloc_ref(strlen(path + name_offset));\n\nAnd as we discussed earlier you would need one more byte here ;-).\n"},{"id":"54257","messageId":"Pine.LNX.4.64.0709281317170.28395@racer.site","threadId":"10057","inReplyTo":"20070928052018.GW3099@spearce.org","subject":"Re: [PATCH 2/2] fetch/push: readd rsync support","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-09-28T12:22:09Z","receivedAt":"2007-09-28T12:22:09Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Fri, 28 Sep 2007, Shawn O. Pearce wrote:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> wrote:\n> > +static int disconnect_rsync(struct transport *transport)\n> > +{\n> > +\treturn 0;\n> > +}\n> >  \n> >  /* Generic functions for using commit walkers */\n> >  \n> > @@ -402,7 +730,10 @@ struct transport *transport_get(struct remote *remote, const char *url)\n> >  \tret->url = url;\n> >  \n> >  \tif (!prefixcmp(url, \"rsync://\")) {\n> > -\t\t/* not supported; don't populate any ops */\n> > +\t\tret->get_refs_list = get_refs_via_rsync;\n> > +\t\tret->fetch = fetch_objs_via_rsync;\n> > +\t\tret->push = rsync_transport_push;\n> > +\t\tret->disconnect = disconnect_rsync;\n> >  \n> >  \t} else if (!prefixcmp(url, \"http://\")\n> >  \t        || !prefixcmp(url, \"https://\")\n> \n> For what it's worth disconnect is an optional operation.\n\nStrange.  When not assigning disconnect, I had an error last night.  But \nit could well be something independent.\n\nReading the code again, I should not even have to assign NULL to \nret->disconnect.\n\nWill fix.\n\n> I see push is now supported again.  Didn't we remove rsync push support \n> a long time ago?  Like say in:\n> \n>   commit c485104741ccdf32dd0c96fcb886c38a0b5badbd\n>   Author: c.shoemaker@cox.net <c.shoemaker@cox.net>\n>   Date:   Sat Oct 29 00:16:33 2005 -0400\n> \n>     Add usage help to git-push.sh\n>     \n>     Also clarify failure to push to read-only remote.  Especially,\n>     state why rsync:// is not used for pushing.\n>     \n>     [jc: ideally rsync should not be used for anything]\n>     \n>     Signed-off-by: Chris Shoemaker <c.shoemaker at cox.net>\n>     Signed-off-by: Junio C Hamano <junkio@cox.net>\n\nI know.  I hesitated, but then: it was all too easy to have it in, so why \nshould we relinquish it?\n\n> I guess it is nice to see that you can't kill rsync.  Like Windows it \n> always finds it way back into your life.\n\nHehe.\n\nCiao,\nDscho\n"},{"id":"54258","messageId":"Pine.LNX.4.64.0709281323300.28395@racer.site","threadId":"10057","inReplyTo":"7v8x6rqhwy.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH 1/2] Introduce remove_dir_recursively()","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-09-28T12:35:21Z","receivedAt":"2007-09-28T12:35:21Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Fri, 28 Sep 2007, Junio C Hamano wrote:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> \n> > +int remove_dir_recursively(char *path, int len, int only_empty)\n> > +{\n> > ...\n> > +\t\tnamlen = strlen(e->d_name);\n> > +\t\tif (len + namlen > PATH_MAX ||\n> > +\t\t\t\t!memcpy(path + len, e->d_name, namlen) ||\n> > +\t\t\t\t(path[len + namlen] = '\\0') ||\n> > +\t\t\t\tlstat(path, &st))\n> > +\t\t\t; /* fall thru */\n> > +\t\telse if (S_ISDIR(st.st_mode)) {\n> > +\t\t\tif (!remove_dir_recursively(path, len + namlen,\n> > +\t\t\t\t\t\tonly_empty))\n> > +\t\t\t\tcontinue; /* happy */\n> > +\t\t} else if (!only_empty &&\n> > +\t\t\t\tlen + namlen + 1 < PATH_MAX &&\n> > +\t\t\t\t!unlink(path))\n> > +\t\t\tcontinue; /* happy, too */\n> > +\n> > +\t\t/* path too long, stat fails, or non-directory still exists */\n> > +\t\tret = -1;\n> > +\t\tbreak;\n> \n> Is it only me who finds the first if () condition way too\n> convoluted and needs to read three times to convince oneself\n> that it is doing a sane thing?\n> \n> Please, especially...\n> \n>  * For $DEITY's sake, memcpy() returns pointer to dst which you\n>    know is not NULL. so !memcpy() is always false here, which\n>    might be _convenient_ for you and the compiler but not for\n>    a human reader of the code who needs to blink twice wondering\n>    if you meant !memcmp().\n> \n>  * Same for (path[] = '\\0'), wondering if it is misspelled\n>    (path[] == '\\0').\n\nOkay, will fix (with an evil goto).\n\nBTW it just hit me that this magic reliance on a buffer of size PATH_MAX \nis not good at all.  It even hit _me_ while developing that series.\n\nSo I'll change that to a strbuf, too.  (Which will fix the convoluted \nlogic quite some, incidentally.)\n\nCiao,\nDscho\n"},{"id":"54261","messageId":"Pine.LNX.4.64.0709281350140.28395@racer.site","threadId":"10057","inReplyTo":"7v3awzqgqc.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH 2/2] fetch/push: readd rsync support","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-09-28T12:53:48Z","receivedAt":"2007-09-28T12:53:48Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Fri, 28 Sep 2007, Junio C Hamano wrote:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> \n> > +/*\n> > + * path is assumed to point to a buffer of PATH_MAX bytes, and\n> > + * path + name_offset is expected to point to \"refs/\".\n> > + */\n> > +\n> > +static int read_loose_refs(char *path, int name_offset, struct ref **tail)\n> > +{\n> > +\tDIR *dir = opendir(path);\n> > +\tstruct dirent *de;\n> > +\tstruct {\n> > +\t\tstruct dirent *entries;\n> > +\t\tint nr, alloc;\n> > +\t} list;\n> > +\tint i, pathlen;\n> > +\n> > +\tif (!dir)\n> > +\t\treturn -1;\n> > +\n> > +\tmemset (&list, 0, sizeof(list));\n> > +\n> > +\twhile ((de = readdir(dir))) {\n> > +\t\tif (de->d_name[0] == '.' && (de->d_name[1] == '\\0' ||\n> > +\t\t\t\t(de->d_name[1] == '.' &&\n> > +\t\t\t\t de->d_name[2] == '\\0')))\n> > +\t\t\tcontinue;\n> > +\t\tif (list.nr >= list.alloc) {\n> > +\t\t\tlist.alloc = alloc_nr(list.nr);\n> > +\t\t\tlist.entries = xrealloc(list.entries,\n> > +\t\t\t\tlist.alloc * sizeof(*de));\n> > +\t\t}\n> \n> ALLOC_GROW() not applicable here?\n> \n> > +\t\tlist.entries[list.nr++] = *de;\n> \n> Are you sure about this?\n> \n> The last paragraph in Rationale section, in\n> \n>     http://www.opengroup.org/onlinepubs/000095399/basedefs/dirent.h.html\n> \n> suggests that the d_name[] member in struct dirent could be\n> declared at the very end of the structure as length of 1 (the\n> traditional trick to implement a flex-array); your assignment\n> >from *de into entries[] would not work as expected on such an\n> implementation.\n> \n> On Linux with glibc it appears bits/dirent.h defines dirent with\n> \"char d_name[256]\", so you may not see a breakage there, though.\n\nD'oh!  You're completely right.\n\n> You only use a list of strings (char **), don't you?\n\nOriginally, I wanted to use the d_type, too, but as I mentioned on IRC, it \nis not reliable enough (shows DT_UNKNOWN _all_ the time here).\n\nBut yes, I'll redo it, using ALLOC_GROW.\n\n> > ...\n> > +\t\t\tif (fd < 0)\n> > +\t\t\t\tcontinue;\n> > +\t\t\tnext = alloc_ref(strlen(path + name_offset));\n> \n> And as we discussed earlier you would need one more byte here ;-).\n\nWell, not after my patch, which I thought of as (1/3), but then decided it \nis good enough to be (1/1) until you shot it down...\n\nWill fix.\n\nCiao,\nDscho\n"},{"id":"54272","messageId":"Pine.LNX.4.64.0709281624290.28395@racer.site","threadId":"10057","inReplyTo":"Pine.LNX.4.64.0709280602580.28395@racer.site","subject":"[PATCH v2 0/2] rsync support, was Re: [PATCH 0/2] rsync support","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-09-28T15:28:33Z","receivedAt":"2007-09-28T15:28:33Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Okay,\n\nhere we go again.  The changes are\n\n- do not implement a dummy disconnect\n\n- use strbuf much more (makes the code cleaner and shorter, too)\n\n- do not rely on the rejected alloc_ref() patch\n\nCiao,\nDscho\n"},{"id":"54273","messageId":"Pine.LNX.4.64.0709281628410.28395@racer.site","threadId":"10057","inReplyTo":"Pine.LNX.4.64.0709280602580.28395@racer.site","subject":"[PATCH v2 1/2] Introduce remove_dir_recursively()","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-09-28T15:28:54Z","receivedAt":"2007-09-28T15:28:54Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"\nThere was a function called remove_empty_dir_recursive() buried\nin refs.c.  Expose a slightly enhanced version in dir.h: it can now\noptionally remove a non-empty directory.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n dir.c  |   41 +++++++++++++++++++++++++++++++++++++++++\n dir.h  |    2 ++\n refs.c |   57 ++++++++++++---------------------------------------------\n 3 files changed, 55 insertions(+), 45 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex eb6c3ab..b18257e 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -685,3 +685,44 @@ int is_inside_dir(const char *dir)\n \tchar buffer[PATH_MAX];\n \treturn get_relative_cwd(buffer, sizeof(buffer), dir) != NULL;\n }\n+\n+int remove_dir_recursively(struct strbuf *path, int only_empty)\n+{\n+\tDIR *dir = opendir(path->buf);\n+\tstruct dirent *e;\n+\tint ret = 0, original_len = path->len, len;\n+\n+\tif (!dir)\n+\t\treturn -1;\n+\tif (path->buf[original_len - 1] != '/')\n+\t\tstrbuf_addch(path, '/');\n+\n+\tlen = path->len;\n+\twhile ((e = readdir(dir)) != NULL) {\n+\t\tstruct stat st;\n+\t\tif ((e->d_name[0] == '.') &&\n+\t\t    ((e->d_name[1] == 0) ||\n+\t\t     ((e->d_name[1] == '.') && e->d_name[2] == 0)))\n+\t\t\tcontinue; /* \".\" and \"..\" */\n+\n+\t\tstrbuf_setlen(path, len);\n+\t\tstrbuf_addstr(path, e->d_name);\n+\t\tif (lstat(path->buf, &st))\n+\t\t\t; /* fall thru */\n+\t\telse if (S_ISDIR(st.st_mode)) {\n+\t\t\tif (!remove_dir_recursively(path, only_empty))\n+\t\t\t\tcontinue; /* happy */\n+\t\t} else if (!only_empty && !unlink(path->buf))\n+\t\t\tcontinue; /* happy, too */\n+\n+\t\t/* path too long, stat fails, or non-directory still exists */\n+\t\tret = -1;\n+\t\tbreak;\n+\t}\n+\tclosedir(dir);\n+\n+\tstrbuf_setlen(path, original_len);\n+\tif (!ret)\n+\t\tret = rmdir(path->buf);\n+\treturn ret;\n+}\ndiff --git a/dir.h b/dir.h\nindex f55a87b..a248a23 100644\n--- a/dir.h\n+++ b/dir.h\n@@ -64,4 +64,6 @@ extern struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathna\n extern char *get_relative_cwd(char *buffer, int size, const char *dir);\n extern int is_inside_dir(const char *dir);\n \n+extern int remove_dir_recursively(struct strbuf *path, int only_empty);\n+\n #endif\ndiff --git a/refs.c b/refs.c\nindex 07e260c..aff02cd 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2,6 +2,7 @@\n #include \"refs.h\"\n #include \"object.h\"\n #include \"tag.h\"\n+#include \"dir.h\"\n \n /* ISSYMREF=01 and ISPACKED=02 are public interfaces */\n #define REF_KNOWS_PEELED 04\n@@ -671,57 +672,23 @@ static struct ref_lock *verify_lock(struct ref_lock *lock,\n \treturn lock;\n }\n \n-static int remove_empty_dir_recursive(char *path, int len)\n-{\n-\tDIR *dir = opendir(path);\n-\tstruct dirent *e;\n-\tint ret = 0;\n-\n-\tif (!dir)\n-\t\treturn -1;\n-\tif (path[len-1] != '/')\n-\t\tpath[len++] = '/';\n-\twhile ((e = readdir(dir)) != NULL) {\n-\t\tstruct stat st;\n-\t\tint namlen;\n-\t\tif ((e->d_name[0] == '.') &&\n-\t\t    ((e->d_name[1] == 0) ||\n-\t\t     ((e->d_name[1] == '.') && e->d_name[2] == 0)))\n-\t\t\tcontinue; /* \".\" and \"..\" */\n-\n-\t\tnamlen = strlen(e->d_name);\n-\t\tif ((len + namlen < PATH_MAX) &&\n-\t\t    strcpy(path + len, e->d_name) &&\n-\t\t    !lstat(path, &st) &&\n-\t\t    S_ISDIR(st.st_mode) &&\n-\t\t    !remove_empty_dir_recursive(path, len + namlen))\n-\t\t\tcontinue; /* happy */\n-\n-\t\t/* path too long, stat fails, or non-directory still exists */\n-\t\tret = -1;\n-\t\tbreak;\n-\t}\n-\tclosedir(dir);\n-\tif (!ret) {\n-\t\tpath[len] = 0;\n-\t\tret = rmdir(path);\n-\t}\n-\treturn ret;\n-}\n-\n-static int remove_empty_directories(char *file)\n+static int remove_empty_directories(const char *file)\n {\n \t/* we want to create a file but there is a directory there;\n \t * if that is an empty directory (or a directory that contains\n \t * only empty directories), remove them.\n \t */\n-\tchar path[PATH_MAX];\n-\tint len = strlen(file);\n+\tstruct strbuf path;\n+\tint result;\n \n-\tif (len >= PATH_MAX) /* path too long ;-) */\n-\t\treturn -1;\n-\tstrcpy(path, file);\n-\treturn remove_empty_dir_recursive(path, len);\n+\tstrbuf_init(&path, 20);\n+\tstrbuf_addstr(&path, file);\n+\n+\tresult = remove_dir_recursively(&path, 1);\n+\n+\tstrbuf_release(&path);\n+\n+\treturn result;\n }\n \n static int is_refname_available(const char *ref, const char *oldref,\n-- \n1.5.3.2.1102.g9487\n"},{"id":"54274","messageId":"Pine.LNX.4.64.0709281629270.28395@racer.site","threadId":"10057","inReplyTo":"Pine.LNX.4.64.0709280602580.28395@racer.site","subject":"[PATCH 2/2] fetch/push: readd rsync support","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-09-28T15:29:46Z","receivedAt":"2007-09-28T15:29:46Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"\nWe lost rsync support when transitioning from shell to C.  Support it\nagain (even if the transport is technically deprecated, some people just\ndo not have any chance to use anything else).\n\nAlso, add a test to t5510.  Since rsync transport is not configured by\ndefault on most machines, and especially not such that you can\nwrite to rsync://127.0.0.1$(pwd)/, it is disabled by default; you can\nenable it by setting the environment variable TEST_RSYNC.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n t/t5510-fetch.sh |   35 ++++++\n transport.c      |  328 +++++++++++++++++++++++++++++++++++++++++++++++++++++-\n 2 files changed, 362 insertions(+), 1 deletions(-)\n\ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex 439430f..73a4e3c 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -153,4 +153,39 @@ test_expect_success 'bundle should be able to create a full history' '\n \n '\n \n+test \"$TEST_RSYNC\" && {\n+test_expect_success 'fetch via rsync' '\n+\tgit pack-refs &&\n+\tmkdir rsynced &&\n+\tcd rsynced &&\n+\tgit init &&\n+\tgit fetch rsync://127.0.0.1$(pwd)/../.git master:refs/heads/master &&\n+\tgit gc --prune &&\n+\ttest $(git rev-parse master) = $(cd .. && git rev-parse master) &&\n+\tgit fsck --full\n+'\n+\n+test_expect_success 'push via rsync' '\n+\tmkdir ../rsynced2 &&\n+\t(cd ../rsynced2 &&\n+\t git init) &&\n+\tgit push rsync://127.0.0.1$(pwd)/../rsynced2/.git master &&\n+\tcd ../rsynced2 &&\n+\tgit gc --prune &&\n+\ttest $(git rev-parse master) = $(cd .. && git rev-parse master) &&\n+\tgit fsck --full\n+'\n+\n+test_expect_success 'push via rsync' '\n+\tcd .. &&\n+\tmkdir rsynced3 &&\n+\t(cd rsynced3 &&\n+\t git init) &&\n+\tgit push --all rsync://127.0.0.1$(pwd)/rsynced3/.git &&\n+\tcd rsynced3 &&\n+\ttest $(git rev-parse master) = $(cd .. && git rev-parse master) &&\n+\tgit fsck --full\n+'\n+}\n+\n test_done\ndiff --git a/transport.c b/transport.c\nindex 4f9cddc..c8eed95 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -6,6 +6,330 @@\n #include \"fetch-pack.h\"\n #include \"walker.h\"\n #include \"bundle.h\"\n+#include \"dir.h\"\n+#include \"refs.h\"\n+\n+/* rsync support */\n+\n+/*\n+ * We copy packed-refs and refs/ into a temporary file, then read the\n+ * loose refs recursively (sorting whenever possible), and then inserting\n+ * those packed refs that are not yet in the list (not validating, but\n+ * assuming that the file is sorted).\n+ *\n+ * Appears refactoring this from refs.c is too cumbersome.\n+ */\n+\n+static int direntry_cmp(const void *a, const void *b)\n+{\n+\tconst struct dirent *d1 = a;\n+\tconst struct dirent *d2 = b;\n+\n+\treturn strcmp(d1->d_name, d2->d_name);\n+}\n+\n+/*\n+ * path is assumed to point to a buffer of PATH_MAX bytes, and\n+ * path + name_offset is expected to point to \"refs/\".\n+ */\n+\n+static int read_loose_refs(struct strbuf *path, int name_offset,\n+\t\tstruct ref **tail)\n+{\n+\tDIR *dir = opendir(path->buf);\n+\tstruct dirent *de;\n+\tstruct {\n+\t\tchar **entries;\n+\t\tint nr, alloc;\n+\t} list;\n+\tint i, pathlen;\n+\n+\tif (!dir)\n+\t\treturn -1;\n+\n+\tmemset (&list, 0, sizeof(list));\n+\n+\twhile ((de = readdir(dir))) {\n+\t\tif (de->d_name[0] == '.' && (de->d_name[1] == '\\0' ||\n+\t\t\t\t(de->d_name[1] == '.' &&\n+\t\t\t\t de->d_name[2] == '\\0')))\n+\t\t\tcontinue;\n+\t\tALLOC_GROW(list.entries, list.nr + 1, list.alloc);\n+\t\tlist.entries[list.nr++] = xstrdup(de->d_name);\n+\t}\n+\tclosedir(dir);\n+\n+\t/* sort the list */\n+\n+\tqsort(list.entries, list.nr, sizeof(*de), direntry_cmp);\n+\n+\tpathlen = path->len;\n+\tstrbuf_addch(path, '/');\n+\n+\tfor (i = 0; i < list.nr; i++, strbuf_setlen(path, pathlen + 1)) {\n+\t\tstrbuf_addstr(path, list.entries[i]);\n+\t\tif (read_loose_refs(path, name_offset, tail)) {\n+\t\t\tint fd = open(path->buf, O_RDONLY);\n+\t\t\tchar buffer[40];\n+\t\t\tstruct ref *next;\n+\n+\t\t\tif (fd < 0)\n+\t\t\t\tcontinue;\n+\t\t\tnext = alloc_ref(path->len - name_offset + 1);\n+\t\t\tif (read_in_full(fd, buffer, 40) != 40 ||\n+\t\t\t\t\tget_sha1_hex(buffer, next->old_sha1)) {\n+\t\t\t\tfree(next);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\tclose(fd);\n+\t\t\tstrcpy(next->name, path->buf + name_offset);\n+\t\t\t(*tail)->next = next;\n+\t\t\t*tail = next;\n+\t\t}\n+\t}\n+\n+\tfor (i = 0; i < list.nr; i++)\n+\t\tfree(list.entries[i]);\n+\tfree(list.entries);\n+\n+\treturn 0;\n+}\n+\n+/* insert the packed refs for which no loose refs were found */\n+\n+static void insert_packed_refs(const char *packed_refs, struct ref **list)\n+{\n+\tFILE *f = fopen(packed_refs, \"r\");\n+\tstatic char buffer[PATH_MAX];\n+\n+\tif (!f)\n+\t\treturn;\n+\n+\tfor (;;) {\n+\t\tint cmp, len;\n+\n+\t\tif (!fgets(buffer, sizeof(buffer), f)) {\n+\t\t\tfclose(f);\n+\t\t\treturn;\n+\t\t}\n+\n+\t\tif (hexval(buffer[0]) & 0x10)\n+\t\t\tcontinue;\n+\t\tlen = strlen(buffer);\n+\t\tif (buffer[len - 1] == '\\n')\n+\t\t\tbuffer[--len] = '\\0';\n+\t\tif (len < 41)\n+\t\t\tcontinue;\n+\t\twhile ((*list)->next &&\n+\t\t\t\t(cmp = strcmp(buffer + 41,\n+\t\t\t\t      (*list)->next->name)) > 0)\n+\t\t\tlist = &(*list)->next;\n+\t\tif (!(*list)->next || cmp < 0) {\n+\t\t\tstruct ref *next = alloc_ref(len - 40);\n+\t\t\tbuffer[40] = '\\0';\n+\t\t\tif (get_sha1_hex(buffer, next->old_sha1)) {\n+\t\t\t\twarning (\"invalid SHA-1: %s\", buffer);\n+\t\t\t\tfree(next);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\tstrcpy(next->name, buffer + 41);\n+\t\t\tnext->next = (*list)->next;\n+\t\t\t(*list)->next = next;\n+\t\t\tlist = &(*list)->next;\n+\t\t}\n+\t}\n+}\n+\n+static struct ref *get_refs_via_rsync(const struct transport *transport)\n+{\n+\tstruct strbuf buf = STRBUF_INIT, temp_dir = STRBUF_INIT;\n+\tstruct ref dummy, *result = &dummy;\n+\tstruct child_process rsync;\n+\tconst char *args[5];\n+\tint temp_dir_len;\n+\n+\t/* copy the refs to the temporary directory */\n+\n+\tstrbuf_addstr(&temp_dir, git_path(\"rsync-refs-XXXXXX\"));\n+\tif (!mkdtemp(temp_dir.buf))\n+\t\tdie (\"Could not make temporary directory\");\n+\ttemp_dir_len = temp_dir.len;\n+\n+\tstrbuf_addstr(&buf, transport->url);\n+\tstrbuf_addstr(&buf, \"/refs\");\n+\n+\tmemset(&rsync, 0, sizeof(rsync));\n+\trsync.argv = args;\n+\trsync.stdout_to_stderr = 1;\n+\targs[0] = \"rsync\";\n+\targs[1] = transport->verbose ? \"-rv\" : \"-r\";\n+\targs[2] = buf.buf;\n+\targs[3] = temp_dir.buf;\n+\targs[4] = NULL;\n+\n+\tif (run_command(&rsync))\n+\t\tdie (\"Could not run rsync to get refs\");\n+\n+\tstrbuf_reset(&buf);\n+\tstrbuf_addstr(&buf, transport->url);\n+\tstrbuf_addstr(&buf, \"/packed-refs\");\n+\n+\targs[2] = buf.buf;\n+\n+\tif (run_command(&rsync))\n+\t\tdie (\"Could not run rsync to get refs\");\n+\n+\t/* read the copied refs */\n+\n+\tstrbuf_addstr(&temp_dir, \"/refs\");\n+\tread_loose_refs(&temp_dir, temp_dir_len + 1, &result);\n+\tstrbuf_setlen(&temp_dir, temp_dir_len);\n+\n+\tresult = &dummy;\n+\tstrbuf_addstr(&temp_dir, \"/packed-refs\");\n+\tinsert_packed_refs(temp_dir.buf, &result);\n+\tstrbuf_setlen(&temp_dir, temp_dir_len);\n+\n+\tif (remove_dir_recursively(&temp_dir, 0))\n+\t\twarning (\"Error removing temporary directory %s.\",\n+\t\t\t\ttemp_dir.buf);\n+\n+\tstrbuf_release(&buf);\n+\tstrbuf_release(&temp_dir);\n+\n+\treturn dummy.next;\n+}\n+\n+static int fetch_objs_via_rsync(struct transport *transport,\n+\t\t\t\t int nr_objs, struct ref **to_fetch)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct child_process rsync;\n+\tconst char *args[8];\n+\tint result;\n+\n+\tstrbuf_addstr(&buf, transport->url);\n+\tstrbuf_addstr(&buf, \"/objects/\");\n+\n+\tmemset(&rsync, 0, sizeof(rsync));\n+\trsync.argv = args;\n+\trsync.stdout_to_stderr = 1;\n+\targs[0] = \"rsync\";\n+\targs[1] = transport->verbose ? \"-rv\" : \"-r\";\n+\targs[2] = \"--ignore-existing\";\n+\targs[3] = \"--exclude\";\n+\targs[4] = \"info\";\n+\targs[5] = buf.buf;\n+\targs[6] = get_object_directory();\n+\targs[7] = NULL;\n+\n+\t/* NEEDSWORK: handle one level of alternates */\n+\tresult = run_command(&rsync);\n+\n+\tstrbuf_release(&buf);\n+\n+\treturn result;\n+}\n+\n+static int write_one_ref(const char *name, const unsigned char *sha1,\n+\t\tint flags, void *data)\n+{\n+\tstruct strbuf *buf = data;\n+\tint len = buf->len;\n+\tFILE *f;\n+\n+\tif (flags && prefixcmp(name, \"refs/heads/\") &&\n+\t\t\tprefixcmp(name, \"refs/tags/\"))\n+\t\treturn 0;\n+\n+\tstrbuf_addstr(buf, name);\n+\tif (safe_create_leading_directories(buf->buf) ||\n+\t\t\t!(f = fopen(buf->buf, \"w\")) ||\n+\t\t\t!fwrite(sha1_to_hex(sha1), 40, 1, f) ||\n+\t\t\tfputc('\\n', f) == EOF ||\n+\t\t\tfclose(f))\n+\t\treturn error(\"problems writing temporary file %s\", buf->buf);\n+\tstrbuf_setlen(buf, len);\n+\treturn 0;\n+}\n+\n+static int write_refs_to_temp_dir(struct strbuf *temp_dir,\n+\t\tint refspec_nr, const char **refspec)\n+{\n+\tint i;\n+\n+\tfor (i = 0; i < refspec_nr; i++) {\n+\t\tunsigned char sha1[20];\n+\t\tchar *ref;\n+\n+\t\tif (dwim_ref(refspec[i], strlen(refspec[i]), sha1, &ref) != 1)\n+\t\t\treturn error(\"Could not get ref %s\", refspec[i]);\n+\n+\t\tif (write_one_ref(ref, sha1, 0, temp_dir)) {\n+\t\t\tfree(ref);\n+\t\t\treturn -1;\n+\t\t}\n+\t\tfree(ref);\n+\t}\n+\treturn 0;\n+}\n+\n+static int rsync_transport_push(struct transport *transport,\n+\t\tint refspec_nr, const char **refspec, int flags) {\n+\tstruct strbuf buf = STRBUF_INIT, temp_dir = STRBUF_INIT;\n+\tint result = 0, i;\n+\tstruct child_process rsync;\n+\tconst char *args[8];\n+\n+\t/* first push the objects */\n+\n+\tstrbuf_addstr(&buf, transport->url);\n+\tstrbuf_addch(&buf, '/');\n+\n+\tmemset(&rsync, 0, sizeof(rsync));\n+\trsync.argv = args;\n+\trsync.stdout_to_stderr = 1;\n+\targs[0] = \"rsync\";\n+\targs[1] = transport->verbose ? \"-av\" : \"-a\";\n+\targs[2] = \"--ignore-existing\";\n+\targs[3] = \"--exclude\";\n+\targs[4] = \"info\";\n+\targs[5] = get_object_directory();;\n+\targs[6] = buf.buf;\n+\targs[7] = NULL;\n+\n+\tif (run_command(&rsync))\n+\t\treturn error(\"Could not push objects to %s\", transport->url);\n+\n+\t/* copy the refs to the temporary directory; they could be packed. */\n+\n+\tstrbuf_addstr(&temp_dir, git_path(\"rsync-refs-XXXXXX\"));\n+\tif (!mkdtemp(temp_dir.buf))\n+\t\tdie (\"Could not make temporary directory\");\n+\tstrbuf_addch(&temp_dir, '/');\n+\n+\tif (flags & TRANSPORT_PUSH_ALL) {\n+\t\tif (for_each_ref(write_one_ref, &temp_dir))\n+\t\t\treturn -1;\n+\t} else if (write_refs_to_temp_dir(&temp_dir, refspec_nr, refspec))\n+\t\treturn -1;\n+\n+\ti = (flags & TRANSPORT_PUSH_FORCE) ? 2 : 3;\n+\targs[i++] = temp_dir.buf;\n+\targs[i++] = transport->url;\n+\targs[i++] = NULL;\n+\tif (run_command(&rsync))\n+\t\tresult = error(\"Could not push to %s\", transport->url);\n+\n+\tif (remove_dir_recursively(&temp_dir, 0))\n+\t\twarning (\"Could not remove temporary directory %s.\",\n+\t\t\t\ttemp_dir.buf);\n+\n+\tstrbuf_release(&buf);\n+\tstrbuf_release(&temp_dir);\n+\n+\treturn result;\n+}\n \n /* Generic functions for using commit walkers */\n \n@@ -402,7 +726,9 @@ struct transport *transport_get(struct remote *remote, const char *url)\n \tret->url = url;\n \n \tif (!prefixcmp(url, \"rsync://\")) {\n-\t\t/* not supported; don't populate any ops */\n+\t\tret->get_refs_list = get_refs_via_rsync;\n+\t\tret->fetch = fetch_objs_via_rsync;\n+\t\tret->push = rsync_transport_push;\n \n \t} else if (!prefixcmp(url, \"http://\")\n \t        || !prefixcmp(url, \"https://\")\n-- \n1.5.3.2.1102.g9487\n"},{"id":"54275","messageId":"Pine.LNX.4.64.0709281630180.28395@racer.site","threadId":"10057","inReplyTo":"Pine.LNX.4.64.0709281629270.28395@racer.site","subject":"Re: [PATCH 2/2] fetch/push: readd rsync support","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-09-28T15:30:31Z","receivedAt":"2007-09-28T15:30:31Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Darn.  this should read [PATCH v2 2/2].\n"},{"id":"54290","messageId":"7vtzpeo5ar.fsf@gitster.siamese.dyndns.org","threadId":"10057","inReplyTo":"Pine.LNX.4.64.0709281629270.28395@racer.site","subject":"Re: [PATCH 2/2] fetch/push: readd rsync support","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2007-09-28T21:20:44Z","receivedAt":"2007-09-28T21:20:44Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> diff --git a/transport.c b/transport.c\n> index 4f9cddc..c8eed95 100644\n> --- a/transport.c\n> +++ b/transport.c\n> @@ -6,6 +6,330 @@\n> ...\n> +static int direntry_cmp(const void *a, const void *b)\n> +{\n> +\tconst struct dirent *d1 = a;\n> +\tconst struct dirent *d2 = b;\n> +\n> +\treturn strcmp(d1->d_name, d2->d_name);\n> +}\n> ...\n> +/*\n> + * path is assumed to point to a buffer of PATH_MAX bytes, and\n> + * path + name_offset is expected to point to \"refs/\".\n> + */\n> +\n> +static int read_loose_refs(struct strbuf *path, int name_offset,\n> +\t\tstruct ref **tail)\n> +{\n> +\tDIR *dir = opendir(path->buf);\n> +\tstruct dirent *de;\n> +\tstruct {\n> +\t\tchar **entries;\n> +\t\tint nr, alloc;\n> +\t} list;\n> ...\n> +\twhile ((de = readdir(dir))) {\n> +\t\tif (de->d_name[0] == '.' && (de->d_name[1] == '\\0' ||\n> +\t\t\t\t(de->d_name[1] == '.' &&\n> +\t\t\t\t de->d_name[2] == '\\0')))\n> +\t\t\tcontinue;\n> +\t\tALLOC_GROW(list.entries, list.nr + 1, list.alloc);\n> +\t\tlist.entries[list.nr++] = xstrdup(de->d_name);\n> +\t}\n> +\tclosedir(dir);\n> +\n> +\t/* sort the list */\n> +\n> +\tqsort(list.entries, list.nr, sizeof(*de), direntry_cmp);\n\nHmmmph...?\n"},{"id":"54296","messageId":"85k5qabfop.fsf@lola.goethe.zz","threadId":"10057","inReplyTo":"Pine.LNX.4.64.0709281323300.28395@racer.site","subject":"Re: [PATCH 1/2] Introduce remove_dir_recursively()","fromName":"David Kastrup","fromEmail":"dak@gnu.org","sentAt":"2007-09-28T22:14:46Z","receivedAt":"2007-09-28T22:14:46Z","isPatch":true,"sender":{"key":"dak@gnu.org","avatar":"https://avatars.githubusercontent.com/u/52141349?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> On Fri, 28 Sep 2007, Junio C Hamano wrote:\n>\n>> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n>> \n>> > +int remove_dir_recursively(char *path, int len, int only_empty)\n>> > +{\n>> > ...\n>> > +\t\tnamlen = strlen(e->d_name);\n>> > +\t\tif (len + namlen > PATH_MAX ||\n>> > +\t\t\t\t!memcpy(path + len, e->d_name, namlen) ||\n>> > +\t\t\t\t(path[len + namlen] = '\\0') ||\n>> > +\t\t\t\tlstat(path, &st))\n>> > +\t\t\t; /* fall thru */\n>> > +\t\telse if (S_ISDIR(st.st_mode)) {\n>> > +\t\t\tif (!remove_dir_recursively(path, len + namlen,\n>> > +\t\t\t\t\t\tonly_empty))\n>> > +\t\t\t\tcontinue; /* happy */\n>> > +\t\t} else if (!only_empty &&\n>> > +\t\t\t\tlen + namlen + 1 < PATH_MAX &&\n>> > +\t\t\t\t!unlink(path))\n>> > +\t\t\tcontinue; /* happy, too */\n>> > +\n>> > +\t\t/* path too long, stat fails, or non-directory still exists */\n>> > +\t\tret = -1;\n>> > +\t\tbreak;\n>> \n>> Is it only me who finds the first if () condition way too\n>> convoluted and needs to read three times to convince oneself\n>> that it is doing a sane thing?\n>> \n>> Please, especially...\n>> \n>>  * For $DEITY's sake, memcpy() returns pointer to dst which you\n>>    know is not NULL. so !memcpy() is always false here, which\n>>    might be _convenient_ for you and the compiler but not for\n>>    a human reader of the code who needs to blink twice wondering\n>>    if you meant !memcmp().\n>> \n>>  * Same for (path[] = '\\0'), wondering if it is misspelled\n>>    (path[] == '\\0').\n>\n> Okay, will fix (with an evil goto).\n\nUh, C has a comma operator.  And inverting the first condition makes\nfor a nicer flow, no goto.\n\n\t\tnamlen = strlen(e->d_name);\n\t\tif (len + namlen <= PATH_MAX\n\t\t\t&& (memcpy(path + len, e->d_name, namlen),\n\t\t\t    path[len + namlen] = '\\0',\n\t\t\t    lstat(path, &st) == 0)\n\t\tif (S_ISDIR(st.st_mode)) {\n\t\t\tif (!remove_dir_recursively(path, len + namlen,\n\t\t\t\t\t\tonly_empty))\n\t\t\tcontinue; /* happy */\n\t\t} else if (!only_empty &&\n\t\t\t\tlen + namlen + 1 < PATH_MAX &&\n\t\t\t\t!unlink(path))\n\t\t\tcontinue; /* happy, too */\n\n\t\t/* path too long, stat fails, or non-directory still exists */\n\t\tret = -1;\n\t\tbreak;\n\n\n-- \nDavid Kastrup, Kriemhildstr. 15, 44793 Bochum\n"},{"id":"54299","messageId":"Pine.LNX.4.64.0709282348040.28395@racer.site","threadId":"10057","inReplyTo":"7vtzpeo5ar.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH 2/2] fetch/push: readd rsync support","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-09-28T22:48:43Z","receivedAt":"2007-09-28T22:48:43Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Fri, 28 Sep 2007, Junio C Hamano wrote:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> \n> > diff --git a/transport.c b/transport.c\n> > index 4f9cddc..c8eed95 100644\n> > --- a/transport.c\n> > +++ b/transport.c\n> > @@ -6,6 +6,330 @@\n> > ...\n> > +static int direntry_cmp(const void *a, const void *b)\n> > +{\n> > +\tconst struct dirent *d1 = a;\n> > +\tconst struct dirent *d2 = b;\n> > +\n> > +\treturn strcmp(d1->d_name, d2->d_name);\n> > +}\n> > ...\n> > +/*\n> > + * path is assumed to point to a buffer of PATH_MAX bytes, and\n> > + * path + name_offset is expected to point to \"refs/\".\n> > + */\n> > +\n> > +static int read_loose_refs(struct strbuf *path, int name_offset,\n> > +\t\tstruct ref **tail)\n> > +{\n> > +\tDIR *dir = opendir(path->buf);\n> > +\tstruct dirent *de;\n> > +\tstruct {\n> > +\t\tchar **entries;\n> > +\t\tint nr, alloc;\n> > +\t} list;\n> > ...\n> > +\twhile ((de = readdir(dir))) {\n> > +\t\tif (de->d_name[0] == '.' && (de->d_name[1] == '\\0' ||\n> > +\t\t\t\t(de->d_name[1] == '.' &&\n> > +\t\t\t\t de->d_name[2] == '\\0')))\n> > +\t\t\tcontinue;\n> > +\t\tALLOC_GROW(list.entries, list.nr + 1, list.alloc);\n> > +\t\tlist.entries[list.nr++] = xstrdup(de->d_name);\n> > +\t}\n> > +\tclosedir(dir);\n> > +\n> > +\t/* sort the list */\n> > +\n> > +\tqsort(list.entries, list.nr, sizeof(*de), direntry_cmp);\n> \n> Hmmmph...?\n\nOuch.  Sorry.\n\nWill resend in a few minutes. (After reading the patch -- again)\n\nCiao,\nDscho\n"},{"id":"54304","messageId":"Pine.LNX.4.64.0709290134000.28395@racer.site","threadId":"10057","inReplyTo":"7vtzpeo5ar.fsf@gitster.siamese.dyndns.org","subject":"[PATCH v3 2/2] fetch/push: readd rsync support","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-09-29T00:35:06Z","receivedAt":"2007-09-29T00:35:06Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"\nWe lost rsync support when transitioning from shell to C.  Support it\nagain (even if the transport is technically deprecated, some people just\ndo not have any chance to use anything else).\n\nAlso, add a test to t5510.  Since rsync transport is not configured by\ndefault on most machines, and especially not such that you can\nwrite to rsync://127.0.0.1$(pwd)/, it is disabled by default; you can\nenable it by setting the environment variable TEST_RSYNC.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n\n\tSorry for the borked v2 of this patch...\n\n\tI cleaned up some other parts, such as free()ing or close()ing\n\talso in case of an error.\n\n t/t5510-fetch.sh |   35 ++++++\n transport.c      |  328 +++++++++++++++++++++++++++++++++++++++++++++++++++++-\n 2 files changed, 362 insertions(+), 1 deletions(-)\n\ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex 439430f..73a4e3c 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -153,4 +153,39 @@ test_expect_success 'bundle should be able to create a full history' '\n \n '\n \n+test \"$TEST_RSYNC\" && {\n+test_expect_success 'fetch via rsync' '\n+\tgit pack-refs &&\n+\tmkdir rsynced &&\n+\tcd rsynced &&\n+\tgit init &&\n+\tgit fetch rsync://127.0.0.1$(pwd)/../.git master:refs/heads/master &&\n+\tgit gc --prune &&\n+\ttest $(git rev-parse master) = $(cd .. && git rev-parse master) &&\n+\tgit fsck --full\n+'\n+\n+test_expect_success 'push via rsync' '\n+\tmkdir ../rsynced2 &&\n+\t(cd ../rsynced2 &&\n+\t git init) &&\n+\tgit push rsync://127.0.0.1$(pwd)/../rsynced2/.git master &&\n+\tcd ../rsynced2 &&\n+\tgit gc --prune &&\n+\ttest $(git rev-parse master) = $(cd .. && git rev-parse master) &&\n+\tgit fsck --full\n+'\n+\n+test_expect_success 'push via rsync' '\n+\tcd .. &&\n+\tmkdir rsynced3 &&\n+\t(cd rsynced3 &&\n+\t git init) &&\n+\tgit push --all rsync://127.0.0.1$(pwd)/rsynced3/.git &&\n+\tcd rsynced3 &&\n+\ttest $(git rev-parse master) = $(cd .. && git rev-parse master) &&\n+\tgit fsck --full\n+'\n+}\n+\n test_done\ndiff --git a/transport.c b/transport.c\nindex 4f9cddc..a2ee8f3 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -6,6 +6,330 @@\n #include \"fetch-pack.h\"\n #include \"walker.h\"\n #include \"bundle.h\"\n+#include \"dir.h\"\n+#include \"refs.h\"\n+\n+/* rsync support */\n+\n+/*\n+ * We copy packed-refs and refs/ into a temporary file, then read the\n+ * loose refs recursively (sorting whenever possible), and then inserting\n+ * those packed refs that are not yet in the list (not validating, but\n+ * assuming that the file is sorted).\n+ *\n+ * Appears refactoring this from refs.c is too cumbersome.\n+ */\n+\n+static int str_cmp(const void *a, const void *b)\n+{\n+\tconst char *s1 = a;\n+\tconst char *s2 = b;\n+\n+\treturn strcmp(s1, s2);\n+}\n+\n+/* path->buf + name_offset is expected to point to \"refs/\" */\n+\n+static int read_loose_refs(struct strbuf *path, int name_offset,\n+\t\tstruct ref **tail)\n+{\n+\tDIR *dir = opendir(path->buf);\n+\tstruct dirent *de;\n+\tstruct {\n+\t\tchar **entries;\n+\t\tint nr, alloc;\n+\t} list;\n+\tint i, pathlen;\n+\n+\tif (!dir)\n+\t\treturn -1;\n+\n+\tmemset (&list, 0, sizeof(list));\n+\n+\twhile ((de = readdir(dir))) {\n+\t\tif (de->d_name[0] == '.' && (de->d_name[1] == '\\0' ||\n+\t\t\t\t(de->d_name[1] == '.' &&\n+\t\t\t\t de->d_name[2] == '\\0')))\n+\t\t\tcontinue;\n+\t\tALLOC_GROW(list.entries, list.nr + 1, list.alloc);\n+\t\tlist.entries[list.nr++] = xstrdup(de->d_name);\n+\t}\n+\tclosedir(dir);\n+\n+\t/* sort the list */\n+\n+\tqsort(list.entries, list.nr, sizeof(char *), str_cmp);\n+\n+\tpathlen = path->len;\n+\tstrbuf_addch(path, '/');\n+\n+\tfor (i = 0; i < list.nr; i++, strbuf_setlen(path, pathlen + 1)) {\n+\t\tstrbuf_addstr(path, list.entries[i]);\n+\t\tif (read_loose_refs(path, name_offset, tail)) {\n+\t\t\tint fd = open(path->buf, O_RDONLY);\n+\t\t\tchar buffer[40];\n+\t\t\tstruct ref *next;\n+\n+\t\t\tif (fd < 0)\n+\t\t\t\tcontinue;\n+\t\t\tnext = alloc_ref(path->len - name_offset + 1);\n+\t\t\tif (read_in_full(fd, buffer, 40) != 40 ||\n+\t\t\t\t\tget_sha1_hex(buffer, next->old_sha1)) {\n+\t\t\t\tclose(fd);\n+\t\t\t\tfree(next);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\tclose(fd);\n+\t\t\tstrcpy(next->name, path->buf + name_offset);\n+\t\t\t(*tail)->next = next;\n+\t\t\t*tail = next;\n+\t\t}\n+\t}\n+\tstrbuf_setlen(path, pathlen);\n+\n+\tfor (i = 0; i < list.nr; i++)\n+\t\tfree(list.entries[i]);\n+\tfree(list.entries);\n+\n+\treturn 0;\n+}\n+\n+/* insert the packed refs for which no loose refs were found */\n+\n+static void insert_packed_refs(const char *packed_refs, struct ref **list)\n+{\n+\tFILE *f = fopen(packed_refs, \"r\");\n+\tstatic char buffer[PATH_MAX];\n+\n+\tif (!f)\n+\t\treturn;\n+\n+\tfor (;;) {\n+\t\tint cmp, len;\n+\n+\t\tif (!fgets(buffer, sizeof(buffer), f)) {\n+\t\t\tfclose(f);\n+\t\t\treturn;\n+\t\t}\n+\n+\t\tif (hexval(buffer[0]) > 0xf)\n+\t\t\tcontinue;\n+\t\tlen = strlen(buffer);\n+\t\tif (buffer[len - 1] == '\\n')\n+\t\t\tbuffer[--len] = '\\0';\n+\t\tif (len < 41)\n+\t\t\tcontinue;\n+\t\twhile ((*list)->next &&\n+\t\t\t\t(cmp = strcmp(buffer + 41,\n+\t\t\t\t      (*list)->next->name)) > 0)\n+\t\t\tlist = &(*list)->next;\n+\t\tif (!(*list)->next || cmp < 0) {\n+\t\t\tstruct ref *next = alloc_ref(len - 40);\n+\t\t\tbuffer[40] = '\\0';\n+\t\t\tif (get_sha1_hex(buffer, next->old_sha1)) {\n+\t\t\t\twarning (\"invalid SHA-1: %s\", buffer);\n+\t\t\t\tfree(next);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\tstrcpy(next->name, buffer + 41);\n+\t\t\tnext->next = (*list)->next;\n+\t\t\t(*list)->next = next;\n+\t\t\tlist = &(*list)->next;\n+\t\t}\n+\t}\n+}\n+\n+static struct ref *get_refs_via_rsync(const struct transport *transport)\n+{\n+\tstruct strbuf buf = STRBUF_INIT, temp_dir = STRBUF_INIT;\n+\tstruct ref dummy, *tail = &dummy;\n+\tstruct child_process rsync;\n+\tconst char *args[5];\n+\tint temp_dir_len;\n+\n+\t/* copy the refs to the temporary directory */\n+\n+\tstrbuf_addstr(&temp_dir, git_path(\"rsync-refs-XXXXXX\"));\n+\tif (!mkdtemp(temp_dir.buf))\n+\t\tdie (\"Could not make temporary directory\");\n+\ttemp_dir_len = temp_dir.len;\n+\n+\tstrbuf_addstr(&buf, transport->url);\n+\tstrbuf_addstr(&buf, \"/refs\");\n+\n+\tmemset(&rsync, 0, sizeof(rsync));\n+\trsync.argv = args;\n+\trsync.stdout_to_stderr = 1;\n+\targs[0] = \"rsync\";\n+\targs[1] = transport->verbose ? \"-rv\" : \"-r\";\n+\targs[2] = buf.buf;\n+\targs[3] = temp_dir.buf;\n+\targs[4] = NULL;\n+\n+\tif (run_command(&rsync))\n+\t\tdie (\"Could not run rsync to get refs\");\n+\n+\tstrbuf_reset(&buf);\n+\tstrbuf_addstr(&buf, transport->url);\n+\tstrbuf_addstr(&buf, \"/packed-refs\");\n+\n+\targs[2] = buf.buf;\n+\n+\tif (run_command(&rsync))\n+\t\tdie (\"Could not run rsync to get refs\");\n+\n+\t/* read the copied refs */\n+\n+\tstrbuf_addstr(&temp_dir, \"/refs\");\n+\tread_loose_refs(&temp_dir, temp_dir_len + 1, &tail);\n+\tstrbuf_setlen(&temp_dir, temp_dir_len);\n+\n+\ttail = &dummy;\n+\tstrbuf_addstr(&temp_dir, \"/packed-refs\");\n+\tinsert_packed_refs(temp_dir.buf, &tail);\n+\tstrbuf_setlen(&temp_dir, temp_dir_len);\n+\n+\tif (remove_dir_recursively(&temp_dir, 0))\n+\t\twarning (\"Error removing temporary directory %s.\",\n+\t\t\t\ttemp_dir.buf);\n+\n+\tstrbuf_release(&buf);\n+\tstrbuf_release(&temp_dir);\n+\n+\treturn dummy.next;\n+}\n+\n+static int fetch_objs_via_rsync(struct transport *transport,\n+\t\t\t\t int nr_objs, struct ref **to_fetch)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct child_process rsync;\n+\tconst char *args[8];\n+\tint result;\n+\n+\tstrbuf_addstr(&buf, transport->url);\n+\tstrbuf_addstr(&buf, \"/objects/\");\n+\n+\tmemset(&rsync, 0, sizeof(rsync));\n+\trsync.argv = args;\n+\trsync.stdout_to_stderr = 1;\n+\targs[0] = \"rsync\";\n+\targs[1] = transport->verbose ? \"-rv\" : \"-r\";\n+\targs[2] = \"--ignore-existing\";\n+\targs[3] = \"--exclude\";\n+\targs[4] = \"info\";\n+\targs[5] = buf.buf;\n+\targs[6] = get_object_directory();\n+\targs[7] = NULL;\n+\n+\t/* NEEDSWORK: handle one level of alternates */\n+\tresult = run_command(&rsync);\n+\n+\tstrbuf_release(&buf);\n+\n+\treturn result;\n+}\n+\n+static int write_one_ref(const char *name, const unsigned char *sha1,\n+\t\tint flags, void *data)\n+{\n+\tstruct strbuf *buf = data;\n+\tint len = buf->len;\n+\tFILE *f;\n+\n+\t/* when called via for_each_ref(), flags is non-zero */\n+\tif (flags && prefixcmp(name, \"refs/heads/\") &&\n+\t\t\tprefixcmp(name, \"refs/tags/\"))\n+\t\treturn 0;\n+\n+\tstrbuf_addstr(buf, name);\n+\tif (safe_create_leading_directories(buf->buf) ||\n+\t\t\t!(f = fopen(buf->buf, \"w\")) ||\n+\t\t\tfprintf(f, \"%s\\n\", sha1_to_hex(sha1)) < 0 ||\n+\t\t\tfclose(f))\n+\t\treturn error(\"problems writing temporary file %s\", buf->buf);\n+\tstrbuf_setlen(buf, len);\n+\treturn 0;\n+}\n+\n+static int write_refs_to_temp_dir(struct strbuf *temp_dir,\n+\t\tint refspec_nr, const char **refspec)\n+{\n+\tint i;\n+\n+\tfor (i = 0; i < refspec_nr; i++) {\n+\t\tunsigned char sha1[20];\n+\t\tchar *ref;\n+\n+\t\tif (dwim_ref(refspec[i], strlen(refspec[i]), sha1, &ref) != 1)\n+\t\t\treturn error(\"Could not get ref %s\", refspec[i]);\n+\n+\t\tif (write_one_ref(ref, sha1, 0, temp_dir)) {\n+\t\t\tfree(ref);\n+\t\t\treturn -1;\n+\t\t}\n+\t\tfree(ref);\n+\t}\n+\treturn 0;\n+}\n+\n+static int rsync_transport_push(struct transport *transport,\n+\t\tint refspec_nr, const char **refspec, int flags)\n+{\n+\tstruct strbuf buf = STRBUF_INIT, temp_dir = STRBUF_INIT;\n+\tint result = 0, i;\n+\tstruct child_process rsync;\n+\tconst char *args[8];\n+\n+\t/* first push the objects */\n+\n+\tstrbuf_addstr(&buf, transport->url);\n+\tstrbuf_addch(&buf, '/');\n+\n+\tmemset(&rsync, 0, sizeof(rsync));\n+\trsync.argv = args;\n+\trsync.stdout_to_stderr = 1;\n+\targs[0] = \"rsync\";\n+\targs[1] = transport->verbose ? \"-av\" : \"-a\";\n+\targs[2] = \"--ignore-existing\";\n+\targs[3] = \"--exclude\";\n+\targs[4] = \"info\";\n+\targs[5] = get_object_directory();;\n+\targs[6] = buf.buf;\n+\targs[7] = NULL;\n+\n+\tif (run_command(&rsync))\n+\t\treturn error(\"Could not push objects to %s\", transport->url);\n+\n+\t/* copy the refs to the temporary directory; they could be packed. */\n+\n+\tstrbuf_addstr(&temp_dir, git_path(\"rsync-refs-XXXXXX\"));\n+\tif (!mkdtemp(temp_dir.buf))\n+\t\tdie (\"Could not make temporary directory\");\n+\tstrbuf_addch(&temp_dir, '/');\n+\n+\tif (flags & TRANSPORT_PUSH_ALL) {\n+\t\tif (for_each_ref(write_one_ref, &temp_dir))\n+\t\t\treturn -1;\n+\t} else if (write_refs_to_temp_dir(&temp_dir, refspec_nr, refspec))\n+\t\treturn -1;\n+\n+\ti = (flags & TRANSPORT_PUSH_FORCE) ? 2 : 3;\n+\targs[i++] = temp_dir.buf;\n+\targs[i++] = transport->url;\n+\targs[i++] = NULL;\n+\tif (run_command(&rsync))\n+\t\tresult = error(\"Could not push to %s\", transport->url);\n+\n+\tif (remove_dir_recursively(&temp_dir, 0))\n+\t\twarning (\"Could not remove temporary directory %s.\",\n+\t\t\t\ttemp_dir.buf);\n+\n+\tstrbuf_release(&buf);\n+\tstrbuf_release(&temp_dir);\n+\n+\treturn result;\n+}\n \n /* Generic functions for using commit walkers */\n \n@@ -402,7 +726,9 @@ struct transport *transport_get(struct remote *remote, const char *url)\n \tret->url = url;\n \n \tif (!prefixcmp(url, \"rsync://\")) {\n-\t\t/* not supported; don't populate any ops */\n+\t\tret->get_refs_list = get_refs_via_rsync;\n+\t\tret->fetch = fetch_objs_via_rsync;\n+\t\tret->push = rsync_transport_push;\n \n \t} else if (!prefixcmp(url, \"http://\")\n \t        || !prefixcmp(url, \"https://\")\n-- \n1.5.3.2.1102.g9487\n"},{"id":"54393","messageId":"7vhclciszz.fsf@gitster.siamese.dyndns.org","threadId":"10057","inReplyTo":"Pine.LNX.4.64.0709290134000.28395@racer.site","subject":"Re: [PATCH v3 2/2] fetch/push: readd rsync support","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2007-09-30T06:09:52Z","receivedAt":"2007-09-30T06:09:52Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> We lost rsync support when transitioning from shell to C.  Support it\n> again (even if the transport is technically deprecated, some people just\n> do not have any chance to use anything else).\n\ns/chance/choice/?\n\n> +test \"$TEST_RSYNC\" && {\n\nSomehow this feels dirty ... perhaps leave early like:\n\n\tif test -z \"$TEST_RSYNC\"\n        then\n        \ttest_expect_success 'skipping rsync transport tests' :\n\t\ttest_done\n                exit\n\tfi\n\n> diff --git a/transport.c b/transport.c\n> index 4f9cddc..a2ee8f3 100644\n> --- a/transport.c\n> +++ b/transport.c\n> @@ -6,6 +6,330 @@\n> ...\n> +{\n> +\tstruct strbuf buf = STRBUF_INIT, temp_dir = STRBUF_INIT;\n> +\tstruct ref dummy, *tail = &dummy;\n> +\tstruct child_process rsync;\n> +\tconst char *args[5];\n> +\tint temp_dir_len;\n> +\n> +\t/* copy the refs to the temporary directory */\n> +\n> +\tstrbuf_addstr(&temp_dir, git_path(\"rsync-refs-XXXXXX\"));\n> +\tif (!mkdtemp(temp_dir.buf))\n> +\t\tdie (\"Could not make temporary directory\");\n\nI wonder how portable mkdtemp() is (it does not seem to be POSIX);\nwould we need something in compat/ perhaps based on tempnam()?\n\n> +static int fetch_objs_via_rsync(struct transport *transport,\n> +\t\t\t\t int nr_objs, struct ref **to_fetch)\n> +{\n> +\tstruct strbuf buf = STRBUF_INIT;\n> +\tstruct child_process rsync;\n> +\tconst char *args[8];\n> +\tint result;\n> +\n> +\tstrbuf_addstr(&buf, transport->url);\n> +\tstrbuf_addstr(&buf, \"/objects/\");\n> +\n> +\tmemset(&rsync, 0, sizeof(rsync));\n> +\trsync.argv = args;\n> +\trsync.stdout_to_stderr = 1;\n> +\targs[0] = \"rsync\";\n> +\targs[1] = transport->verbose ? \"-rv\" : \"-r\";\n> +\targs[2] = \"--ignore-existing\";\n> +\targs[3] = \"--exclude\";\n> +\targs[4] = \"info\";\n> +\targs[5] = buf.buf;\n> +\targs[6] = get_object_directory();\n> +\targs[7] = NULL;\n\nHmm, we used to do \"rsync $remote/objects/ $our/.git/objects/\",\nbut this omits the trailing \"/\" from our side.  I suspect the\nreason was to deal with the case where our .git/objects was a\nsymlink to elsewhere (which was how you did alternates before\nalternates was invented), which may not matter anymore these\ndays.\n"},{"id":"54408","messageId":"Pine.LNX.4.64.0709301344560.28395@racer.site","threadId":"10057","inReplyTo":"7vhclciszz.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH v3 2/2] fetch/push: readd rsync support","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-09-30T12:58:29Z","receivedAt":"2007-09-30T12:58:29Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Sat, 29 Sep 2007, Junio C Hamano wrote:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> \n> > We lost rsync support when transitioning from shell to C.  Support it\n> > again (even if the transport is technically deprecated, some people just\n> > do not have any chance to use anything else).\n> \n> s/chance/choice/?\n\nHehe. I did mean it like that, but feel free to edit!\n\n> > +test \"$TEST_RSYNC\" && {\n> \n> Somehow this feels dirty ... perhaps leave early like:\n> \n> \tif test -z \"$TEST_RSYNC\"\n>         then\n>         \ttest_expect_success 'skipping rsync transport tests' :\n> \t\ttest_done\n>                 exit\n> \tfi\n\nOkay.\n\n> > diff --git a/transport.c b/transport.c\n> > index 4f9cddc..a2ee8f3 100644\n> > --- a/transport.c\n> > +++ b/transport.c\n> > @@ -6,6 +6,330 @@\n> > ...\n> > +{\n> > +\tstruct strbuf buf = STRBUF_INIT, temp_dir = STRBUF_INIT;\n> > +\tstruct ref dummy, *tail = &dummy;\n> > +\tstruct child_process rsync;\n> > +\tconst char *args[5];\n> > +\tint temp_dir_len;\n> > +\n> > +\t/* copy the refs to the temporary directory */\n> > +\n> > +\tstrbuf_addstr(&temp_dir, git_path(\"rsync-refs-XXXXXX\"));\n> > +\tif (!mkdtemp(temp_dir.buf))\n> > +\t\tdie (\"Could not make temporary directory\");\n> \n> I wonder how portable mkdtemp() is (it does not seem to be POSIX);\n> would we need something in compat/ perhaps based on tempnam()?\n\nMy man page said BSD, so I did not bother any more...\n\nBesides, using \"tmpnam()\" makes gcc say that it is dangerous, and I should \nuse mkstemp() instead...\n\n> > +static int fetch_objs_via_rsync(struct transport *transport,\n> > +\t\t\t\t int nr_objs, struct ref **to_fetch)\n> > +{\n> > +\tstruct strbuf buf = STRBUF_INIT;\n> > +\tstruct child_process rsync;\n> > +\tconst char *args[8];\n> > +\tint result;\n> > +\n> > +\tstrbuf_addstr(&buf, transport->url);\n> > +\tstrbuf_addstr(&buf, \"/objects/\");\n> > +\n> > +\tmemset(&rsync, 0, sizeof(rsync));\n> > +\trsync.argv = args;\n> > +\trsync.stdout_to_stderr = 1;\n> > +\targs[0] = \"rsync\";\n> > +\targs[1] = transport->verbose ? \"-rv\" : \"-r\";\n> > +\targs[2] = \"--ignore-existing\";\n> > +\targs[3] = \"--exclude\";\n> > +\targs[4] = \"info\";\n> > +\targs[5] = buf.buf;\n> > +\targs[6] = get_object_directory();\n> > +\targs[7] = NULL;\n> \n> Hmm, we used to do \"rsync $remote/objects/ $our/.git/objects/\", but this \n> omits the trailing \"/\" from our side.  I suspect the reason was to deal \n> with the case where our .git/objects was a symlink to elsewhere (which \n> was how you did alternates before alternates was invented), which may \n> not matter anymore these days.\n\nNevertheless, this was an oversight on my side. Will change to use a \nsecond strbuf for the local objects directory.\n"}]}