{"thread":{"id":"2246","subject":"Subject: [PATCH] git-fetch-pack: Do not use git-rev-list","startedAt":"2005-10-28T02:46:27Z","lastAt":"2005-10-28T02:46:27Z","messageCount":1,"participants":["Johannes Schindelin"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"10733","messageId":"Pine.LNX.4.63.0510280445420.20516@wbgn013.biozentrum.uni-wuerzburg.de","threadId":"2246","inReplyTo":null,"subject":"Subject: [PATCH] git-fetch-pack: Do not use git-rev-list","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2005-10-28T02:46:27Z","receivedAt":"2005-10-28T02:46:27Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"\nThe code used to call git-rev-list to enumerate the local revisions.\nA disadvantage of that method was that git-rev-list, lacking a\ncontrol apart from the command line, would happily enumerate\nancestors of acknowledged common commits, which was just taking\nunnecessary bandwidth.\n\nTherefore, do not use git-rev-list on the fetching side, but rather\nconstruct the list on the go. Send the revisions starting from the\nlocal heads, ignoring the revisions known to be common.\n\nSigned-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n\n---\n\n fetch-pack.c |  164 +++++++++++++++++++++++++++++++++++++++++++++++-----------\n 1 files changed, 132 insertions(+), 32 deletions(-)\n\napplies-to: 196fedc0bea0ee34dc3f807bb444cfcf5fb947dc\nbdee3e007e604b3ca00e48f9c8378533c1a0d7af\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 8566ab1..3be77c3 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -13,18 +13,130 @@ static const char fetch_pack_usage[] =\n static const char *exec = \"git-upload-pack\";\n \n #define COMPLETE\t(1U << 0)\n+#define COMMON\t\t(1U << 1)\n+#define COMMON_REF\t(1U << 2)\n+#define SEEN\t\t(1U << 3)\n+#define POPPED\t\t(1U << 4)\n+\n+static struct commit_list *rev_list = NULL;\n+static int non_common_revs = 0;\n+\n+static void rev_list_push(struct commit *commit, int mark)\n+{\n+\tif (!(commit->object.flags & mark)) {\n+\t\tcommit->object.flags |= mark;\n+\n+\t\tif (!(commit->object.parsed))\n+\t\t\tparse_commit(commit);\n+\n+\t\tinsert_by_date(commit, &rev_list);\n+\n+\t\tif (!(commit->object.flags & COMMON))\n+\t\t\tnon_common_revs++;\n+\t}\n+}\n+\n+static int rev_list_insert_ref(const char *path, const unsigned char *sha1)\n+{\n+\tstruct object *o = deref_tag(parse_object(sha1));\n+\n+\tif (o->type == commit_type)\n+\t\trev_list_push((struct commit *)o, SEEN);\n+\n+\treturn 0;\n+}\n+\n+/*\n+   This function marks a rev and its ancestors as common.\n+   In some cases, it is desirable to mark only the ancestors (for example\n+   when only the server does not yet know that they are common).\n+*/\n+\n+static void mark_common(struct commit *commit,\n+\t\tint ancestors_only, int dont_parse)\n+{\n+\tif (commit != NULL && !(commit->object.flags & COMMON)) {\n+\t\tstruct object *o = (struct object *)commit;\n+\n+\t\tif (!ancestors_only)\n+\t\t\to->flags |= COMMON;\n+\n+\t\tif (!(o->flags & SEEN))\n+\t\t\trev_list_push(commit, SEEN);\n+\t\telse {\n+\t\t\tstruct commit_list *parents;\n+\n+\t\t\tif (!ancestors_only && !(o->flags & POPPED))\n+\t\t\t\tnon_common_revs--;\n+\t\t\tif (!o->parsed && !dont_parse)\n+\t\t\t\tparse_commit(commit);\n+\n+\t\t\tfor (parents = commit->parents;\n+\t\t\t\t\tparents;\n+\t\t\t\t\tparents = parents->next)\n+\t\t\t\tmark_common(parents->item, 0, dont_parse);\n+\t\t}\n+\t}\n+}\n+\n+/*\n+  Get the next rev to send, ignoring the common.\n+*/\n+\n+static const unsigned char* get_rev()\n+{\n+\tstruct commit *commit = NULL;\n+\n+\twhile (commit == NULL) {\n+\t\tunsigned int mark;\n+\t\tstruct commit_list* parents;\n+\n+\t\tif (rev_list == NULL || non_common_revs == 0)\n+\t\t\treturn NULL;\n+\n+\t\tcommit = rev_list->item;\n+\t\tif (!(commit->object.parsed))\n+\t\t\tparse_commit(commit);\n+\t\tcommit->object.flags |= POPPED;\n+\t\tif (!(commit->object.flags & COMMON))\n+\t\t\tnon_common_revs--;\n+\t\n+\t\tparents = commit->parents;\n+\n+\t\tif (commit->object.flags & COMMON) {\n+\t\t\t/* do not send \"have\", and ignore ancestors */\n+\t\t\tcommit = NULL;\n+\t\t\tmark = COMMON | SEEN;\n+\t\t} else if (commit->object.flags & COMMON_REF)\n+\t\t\t/* send \"have\", and ignore ancestors */\n+\t\t\tmark = COMMON | SEEN;\n+\t\telse\n+\t\t\t/* send \"have\", also for its ancestors */\n+\t\t\tmark = SEEN;\n+\n+\t\twhile (parents) {\n+\t\t\tif (!(parents->item->object.flags & SEEN))\n+\t\t\t\trev_list_push(parents->item, mark);\n+\t\t\tif (mark & COMMON)\n+\t\t\t\tmark_common(parents->item, 1, 0);\n+\t\t\tparents = parents->next;\n+\t\t}\n+\n+\t\trev_list = rev_list->next;\n+\t}\n+\n+\treturn commit->object.sha1;\n+}\n \n static int find_common(int fd[2], unsigned char *result_sha1,\n \t\t       struct ref *refs)\n {\n \tint fetching;\n-\tstatic char line[1000];\n-\tstatic char rev_command[1024];\n-\tint count = 0, flushes = 0, retval, rev_command_len;\n-\tFILE *revs;\n+\tint count = 0, flushes = 0, retval;\n+\tconst unsigned char *sha1;\n+\n+\tfor_each_ref(rev_list_insert_ref);\n \n-\tstrcpy(rev_command, \"git-rev-list $(git-rev-parse --all)\");\n-\trev_command_len = strlen(rev_command);\n \tfetching = 0;\n \tfor ( ; refs ; refs = refs->next) {\n \t\tunsigned char *remote = refs->old_sha1;\n@@ -42,25 +154,18 @@ static int find_common(int fd[2], unsign\n \t\t */\n \t\tif (((o = lookup_object(remote)) != NULL) &&\n \t\t    (o->flags & COMPLETE)) {\n-\t\t\tstruct commit_list *p;\n-\t\t\tstruct commit *commit =\n-\t\t\t\t(struct commit *) (o = deref_tag(o));\n-\t\t\tif (!o)\n-\t\t\t\tgoto repair;\n-\t\t\tif (o->type != commit_type)\n-\t\t\t\tcontinue;\n-\t\t\tp = commit->parents;\n-\t\t\twhile (p &&\n-\t\t\t       rev_command_len + 44 < sizeof(rev_command)) {\n-\t\t\t\tsnprintf(rev_command + rev_command_len, 44,\n-\t\t\t\t\t \" ^%s\",\n-\t\t\t\t\t sha1_to_hex(p->item->object.sha1));\n-\t\t\t\trev_command_len += 43;\n-\t\t\t\tp = p->next;\n+\t\t\to = deref_tag(o);\n+\n+\t\t\tif (o->type == commit_type) {\n+\t\t\t\tstruct commit *commit = (struct commit *)o;\n+\n+\t\t\t\trev_list_push(commit, COMMON_REF | SEEN);\n+\n+\t\t\t\tmark_common(commit, 1, 1);\n \t\t\t}\n \t\t\tcontinue;\n \t\t}\n-\trepair:\n+\n \t\tpacket_write(fd[1], \"want %s\\n\", sha1_to_hex(remote));\n \t\tfetching++;\n \t}\n@@ -68,16 +173,9 @@ static int find_common(int fd[2], unsign\n \tif (!fetching)\n \t\treturn 1;\n \n-\trevs = popen(rev_command, \"r\");\n-\tif (!revs)\n-\t\tdie(\"unable to run 'git-rev-list'\");\n-\n-\tflushes = 1;\n+\tflushes = 0;\n \tretval = -1;\n-\twhile (fgets(line, sizeof(line), revs) != NULL) {\n-\t\tunsigned char sha1[20];\n-\t\tif (get_sha1_hex(line, sha1))\n-\t\t\tdie(\"git-fetch-pack: expected object name, got crud\");\n+\twhile ((sha1 = get_rev())) {\n \t\tpacket_write(fd[1], \"have %s\\n\", sha1_to_hex(sha1));\n \t\tif (verbose)\n \t\t\tfprintf(stderr, \"have %s\\n\", sha1_to_hex(sha1));\n@@ -101,10 +199,12 @@ static int find_common(int fd[2], unsign\n \t\t\tflushes--;\n \t\t}\n \t}\n-\tpclose(revs);\n+\n \tpacket_write(fd[1], \"done\\n\");\n \tif (verbose)\n \t\tfprintf(stderr, \"done\\n\");\n+\tif (retval != 0)\n+\t\tflushes++;\n \twhile (flushes) {\n \t\tflushes--;\n \t\tif (get_ack(fd[0], result_sha1)) {\n---\n0.99.8.GIT\n"}]}