{"thread":{"id":"9160","subject":"[PATCH 4/4] Modularize commit-walker","startedAt":"2007-07-22T22:09:28Z","lastAt":"2007-07-22T22:09:28Z","messageCount":1,"participants":["Daniel Barkalow"],"isPatch":true,"patchVersion":1,"patchTotal":4},"messages":[{"id":"48213","messageId":"Pine.LNX.4.64.0707221809050.29679@iabervon.org","threadId":"9160","inReplyTo":null,"subject":"[PATCH 4/4] Modularize commit-walker","fromName":"Daniel Barkalow","fromEmail":"barkalow@iabervon.org","sentAt":"2007-07-22T22:09:28Z","receivedAt":"2007-07-22T22:09:28Z","isPatch":true,"sender":{"key":"barkalow@iabervon.org","avatar":"https://avatars.githubusercontent.com/u/55364219?v=4"},"body":"This turns the extern functions to be provided by the backend into a\nstruct of pointers, renames the functions to be more\nnamespace-friendly, and updates http-fetch to this interface. It\nremoves the unused include from http-push.c. It makes git-http-fetch a\nbuiltin (with the implementation a separate file, accessible\ndirectly).\n\nSigned-off-by: Daniel Barkalow <barkalow@iabervon.org>\n---\n Makefile             |   17 +-\n builtin-http-fetch.c |   77 ++++\n builtin.h            |    1 +\n fetch.c              |  317 ---------------\n fetch.h              |   54 ---\n git.c                |    3 +\n http-fetch.c         | 1059 --------------------------------------------------\n http-push.c          |    1 -\n http-walker.c        | 1035 ++++++++++++++++++++++++++++++++++++++++++++++++\n walker.c             |  318 +++++++++++++++\n walker.h             |   37 ++\n 11 files changed, 1480 insertions(+), 1439 deletions(-)\n create mode 100644 builtin-http-fetch.c\n delete mode 100644 fetch.c\n delete mode 100644 fetch.h\n delete mode 100644 http-fetch.c\n create mode 100644 http-walker.c\n create mode 100644 walker.c\n create mode 100644 walker.h\n\ndiff --git a/Makefile b/Makefile\nindex 705dde5..5f880c6 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -508,7 +508,9 @@ else\n \tCC_LD_DYNPATH = -R\n endif\n \n-ifndef NO_CURL\n+ifdef NO_CURL\n+\tBASIC_CFLAGS += -DNO_CURL\n+else\n \tifdef CURLDIR\n \t\t# Try \"-Wl,-rpath=$(CURLDIR)/lib\" in such a case.\n \t\tBASIC_CFLAGS += -I$(CURLDIR)/include\n@@ -516,7 +518,9 @@ ifndef NO_CURL\n \telse\n \t\tCURL_LIBCURL = -lcurl\n \tendif\n-\tPROGRAMS += git-http-fetch$X\n+\tBUILTIN_OBJS += builtin-http-fetch.o\n+\tEXTLIBS += $(CURL_LIBCURL)\n+\tLIB_OBJS += http.o walker.o http-walker.o\n \tcurl_check := $(shell (echo 070908; curl-config --vernum) | sort -r | sed -ne 2p)\n \tifeq \"$(curl_check)\" \"070908\"\n \t\tifndef NO_EXPAT\n@@ -874,7 +878,7 @@ http.o: http.c GIT-CFLAGS\n \t$(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) -DGIT_USER_AGENT='\"git/$(GIT_VERSION)\"' $<\n \n ifdef NO_EXPAT\n-http-fetch.o: http-fetch.c http.h GIT-CFLAGS\n+http-walker.o: http-walker.c http.h GIT-CFLAGS\n \t$(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) -DNO_EXPAT $<\n endif\n \n@@ -883,16 +887,13 @@ git-%$X: %.o $(GITLIBS)\n \n git-imap-send$X: imap-send.o $(LIB_FILE)\n \n-http.o http-fetch.o http-push.o: http.h\n-git-http-fetch$X: fetch.o http.o http-fetch.o $(GITLIBS)\n-\t$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \\\n-\t\t$(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)\n+http.o http-walker.o http-push.o: http.h\n \n git-http-push$X: revision.o http.o http-push.o $(GITLIBS)\n \t$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \\\n \t\t$(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)\n \n-$(LIB_OBJS) $(BUILTIN_OBJS) fetch.o: $(LIB_H)\n+$(LIB_OBJS) $(BUILTIN_OBJS) walker.o: $(LIB_H)\n $(patsubst git-%$X,%.o,$(PROGRAMS)): $(LIB_H) $(wildcard */*.h)\n $(DIFF_OBJS): diffcore.h\n \ndiff --git a/builtin-http-fetch.c b/builtin-http-fetch.c\nnew file mode 100644\nindex 0000000..4a50dbd\n--- /dev/null\n+++ b/builtin-http-fetch.c\n@@ -0,0 +1,77 @@\n+#include \"cache.h\"\n+#include \"walker.h\"\n+\n+int cmd_http_fetch(int argc, const char **argv, const char *prefix)\n+{\n+\tstruct walker *walker;\n+\tint commits_on_stdin = 0;\n+\tint commits;\n+\tconst char **write_ref = NULL;\n+\tchar **commit_id;\n+\tconst char *url;\n+\tint arg = 1;\n+\tint rc = 0;\n+\tint get_tree = 0;\n+\tint get_history = 0;\n+\tint get_all = 0;\n+\tint get_verbosely = 0;\n+\tint get_recover = 0;\n+\n+\tgit_config(git_default_config);\n+\n+\twhile (arg < argc && argv[arg][0] == '-') {\n+\t\tif (argv[arg][1] == 't') {\n+\t\t\tget_tree = 1;\n+\t\t} else if (argv[arg][1] == 'c') {\n+\t\t\tget_history = 1;\n+\t\t} else if (argv[arg][1] == 'a') {\n+\t\t\tget_all = 1;\n+\t\t\tget_tree = 1;\n+\t\t\tget_history = 1;\n+\t\t} else if (argv[arg][1] == 'v') {\n+\t\t\tget_verbosely = 1;\n+\t\t} else if (argv[arg][1] == 'w') {\n+\t\t\twrite_ref = &argv[arg + 1];\n+\t\t\targ++;\n+\t\t} else if (!strcmp(argv[arg], \"--recover\")) {\n+\t\t\tget_recover = 1;\n+\t\t} else if (!strcmp(argv[arg], \"--stdin\")) {\n+\t\t\tcommits_on_stdin = 1;\n+\t\t}\n+\t\targ++;\n+\t}\n+\tif (argc < arg + 2 - commits_on_stdin) {\n+\t\tusage(\"git-http-fetch [-c] [-t] [-a] [-v] [--recover] [-w ref] [--stdin] commit-id url\");\n+\t\treturn 1;\n+\t}\n+\tif (commits_on_stdin) {\n+\t\tcommits = walker_targets_stdin(&commit_id, &write_ref);\n+\t} else {\n+\t\tcommit_id = (char **) &argv[arg++];\n+\t\tcommits = 1;\n+\t}\n+\turl = argv[arg];\n+\n+\twalker = get_http_walker(url);\n+\twalker->get_tree = get_tree;\n+\twalker->get_history = get_history;\n+\twalker->get_all = get_all;\n+\twalker->get_verbosely = get_verbosely;\n+\twalker->get_recover = get_recover;\n+\n+\trc = walker_fetch(walker, commits, commit_id, write_ref, url);\n+\n+\tif (commits_on_stdin)\n+\t\twalker_targets_free(commits, commit_id, write_ref);\n+\n+\tif (walker->corrupt_object_found) {\n+\t\tfprintf(stderr,\n+\"Some loose object were found to be corrupt, but they might be just\\n\"\n+\"a false '404 Not Found' error message sent with incorrect HTTP\\n\"\n+\"status code.  Suggest running git-fsck.\\n\");\n+\t}\n+\n+\twalker_free(walker);\n+\n+\treturn rc;\n+}\ndiff --git a/builtin.h b/builtin.h\nindex 661a92f..bd06af7 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -40,6 +40,7 @@ extern int cmd_gc(int argc, const char **argv, const char *prefix);\n extern int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix);\n extern int cmd_grep(int argc, const char **argv, const char *prefix);\n extern int cmd_help(int argc, const char **argv, const char *prefix);\n+extern int cmd_http_fetch(int argc, const char **argv, const char *prefix);\n extern int cmd_init_db(int argc, const char **argv, const char *prefix);\n extern int cmd_log(int argc, const char **argv, const char *prefix);\n extern int cmd_log_reflog(int argc, const char **argv, const char *prefix);\ndiff --git a/fetch.c b/fetch.c\ndeleted file mode 100644\nindex 811be87..0000000\n--- a/fetch.c\n+++ /dev/null\n@@ -1,317 +0,0 @@\n-#include \"cache.h\"\n-#include \"fetch.h\"\n-#include \"commit.h\"\n-#include \"tree.h\"\n-#include \"tree-walk.h\"\n-#include \"tag.h\"\n-#include \"blob.h\"\n-#include \"refs.h\"\n-#include \"strbuf.h\"\n-\n-int get_tree = 0;\n-int get_history = 0;\n-int get_all = 0;\n-int get_verbosely = 0;\n-int get_recover = 0;\n-static unsigned char current_commit_sha1[20];\n-\n-void pull_say(const char *fmt, const char *hex)\n-{\n-\tif (get_verbosely)\n-\t\tfprintf(stderr, fmt, hex);\n-}\n-\n-static void report_missing(const struct object *obj)\n-{\n-\tchar missing_hex[41];\n-\tstrcpy(missing_hex, sha1_to_hex(obj->sha1));;\n-\tfprintf(stderr, \"Cannot obtain needed %s %s\\n\",\n-\t\tobj->type ? typename(obj->type): \"object\", missing_hex);\n-\tif (!is_null_sha1(current_commit_sha1))\n-\t\tfprintf(stderr, \"while processing commit %s.\\n\",\n-\t\t\tsha1_to_hex(current_commit_sha1));\n-}\n-\n-static int process(struct object *obj);\n-\n-static int process_tree(struct tree *tree)\n-{\n-\tstruct tree_desc desc;\n-\tstruct name_entry entry;\n-\n-\tif (parse_tree(tree))\n-\t\treturn -1;\n-\n-\tinit_tree_desc(&desc, tree->buffer, tree->size);\n-\twhile (tree_entry(&desc, &entry)) {\n-\t\tstruct object *obj = NULL;\n-\n-\t\t/* submodule commits are not stored in the superproject */\n-\t\tif (S_ISGITLINK(entry.mode))\n-\t\t\tcontinue;\n-\t\tif (S_ISDIR(entry.mode)) {\n-\t\t\tstruct tree *tree = lookup_tree(entry.sha1);\n-\t\t\tif (tree)\n-\t\t\t\tobj = &tree->object;\n-\t\t}\n-\t\telse {\n-\t\t\tstruct blob *blob = lookup_blob(entry.sha1);\n-\t\t\tif (blob)\n-\t\t\t\tobj = &blob->object;\n-\t\t}\n-\t\tif (!obj || process(obj))\n-\t\t\treturn -1;\n-\t}\n-\tfree(tree->buffer);\n-\ttree->buffer = NULL;\n-\ttree->size = 0;\n-\treturn 0;\n-}\n-\n-#define COMPLETE\t(1U << 0)\n-#define SEEN\t\t(1U << 1)\n-#define TO_SCAN\t\t(1U << 2)\n-\n-static struct commit_list *complete = NULL;\n-\n-static int process_commit(struct commit *commit)\n-{\n-\tif (parse_commit(commit))\n-\t\treturn -1;\n-\n-\twhile (complete && complete->item->date >= commit->date) {\n-\t\tpop_most_recent_commit(&complete, COMPLETE);\n-\t}\n-\n-\tif (commit->object.flags & COMPLETE)\n-\t\treturn 0;\n-\n-\thashcpy(current_commit_sha1, commit->object.sha1);\n-\n-\tpull_say(\"walk %s\\n\", sha1_to_hex(commit->object.sha1));\n-\n-\tif (get_tree) {\n-\t\tif (process(&commit->tree->object))\n-\t\t\treturn -1;\n-\t\tif (!get_all)\n-\t\t\tget_tree = 0;\n-\t}\n-\tif (get_history) {\n-\t\tstruct commit_list *parents = commit->parents;\n-\t\tfor (; parents; parents = parents->next) {\n-\t\t\tif (process(&parents->item->object))\n-\t\t\t\treturn -1;\n-\t\t}\n-\t}\n-\treturn 0;\n-}\n-\n-static int process_tag(struct tag *tag)\n-{\n-\tif (parse_tag(tag))\n-\t\treturn -1;\n-\treturn process(tag->tagged);\n-}\n-\n-static struct object_list *process_queue = NULL;\n-static struct object_list **process_queue_end = &process_queue;\n-\n-static int process_object(struct object *obj)\n-{\n-\tif (obj->type == OBJ_COMMIT) {\n-\t\tif (process_commit((struct commit *)obj))\n-\t\t\treturn -1;\n-\t\treturn 0;\n-\t}\n-\tif (obj->type == OBJ_TREE) {\n-\t\tif (process_tree((struct tree *)obj))\n-\t\t\treturn -1;\n-\t\treturn 0;\n-\t}\n-\tif (obj->type == OBJ_BLOB) {\n-\t\treturn 0;\n-\t}\n-\tif (obj->type == OBJ_TAG) {\n-\t\tif (process_tag((struct tag *)obj))\n-\t\t\treturn -1;\n-\t\treturn 0;\n-\t}\n-\treturn error(\"Unable to determine requirements \"\n-\t\t     \"of type %s for %s\",\n-\t\t     typename(obj->type), sha1_to_hex(obj->sha1));\n-}\n-\n-static int process(struct object *obj)\n-{\n-\tif (obj->flags & SEEN)\n-\t\treturn 0;\n-\tobj->flags |= SEEN;\n-\n-\tif (has_sha1_file(obj->sha1)) {\n-\t\t/* We already have it, so we should scan it now. */\n-\t\tobj->flags |= TO_SCAN;\n-\t}\n-\telse {\n-\t\tif (obj->flags & COMPLETE)\n-\t\t\treturn 0;\n-\t\tprefetch(obj->sha1);\n-\t}\n-\n-\tobject_list_insert(obj, process_queue_end);\n-\tprocess_queue_end = &(*process_queue_end)->next;\n-\treturn 0;\n-}\n-\n-static int loop(void)\n-{\n-\tstruct object_list *elem;\n-\n-\twhile (process_queue) {\n-\t\tstruct object *obj = process_queue->item;\n-\t\telem = process_queue;\n-\t\tprocess_queue = elem->next;\n-\t\tfree(elem);\n-\t\tif (!process_queue)\n-\t\t\tprocess_queue_end = &process_queue;\n-\n-\t\t/* If we are not scanning this object, we placed it in\n-\t\t * the queue because we needed to fetch it first.\n-\t\t */\n-\t\tif (! (obj->flags & TO_SCAN)) {\n-\t\t\tif (fetch(obj->sha1)) {\n-\t\t\t\treport_missing(obj);\n-\t\t\t\treturn -1;\n-\t\t\t}\n-\t\t}\n-\t\tif (!obj->type)\n-\t\t\tparse_object(obj->sha1);\n-\t\tif (process_object(obj))\n-\t\t\treturn -1;\n-\t}\n-\treturn 0;\n-}\n-\n-static int interpret_target(char *target, unsigned char *sha1)\n-{\n-\tif (!get_sha1_hex(target, sha1))\n-\t\treturn 0;\n-\tif (!check_ref_format(target)) {\n-\t\tif (!fetch_ref(target, sha1)) {\n-\t\t\treturn 0;\n-\t\t}\n-\t}\n-\treturn -1;\n-}\n-\n-static int mark_complete(const char *path, const unsigned char *sha1, int flag, void *cb_data)\n-{\n-\tstruct commit *commit = lookup_commit_reference_gently(sha1, 1);\n-\tif (commit) {\n-\t\tcommit->object.flags |= COMPLETE;\n-\t\tinsert_by_date(commit, &complete);\n-\t}\n-\treturn 0;\n-}\n-\n-int pull_targets_stdin(char ***target, const char ***write_ref)\n-{\n-\tint targets = 0, targets_alloc = 0;\n-\tstruct strbuf buf;\n-\t*target = NULL; *write_ref = NULL;\n-\tstrbuf_init(&buf);\n-\twhile (1) {\n-\t\tchar *rf_one = NULL;\n-\t\tchar *tg_one;\n-\n-\t\tread_line(&buf, stdin, '\\n');\n-\t\tif (buf.eof)\n-\t\t\tbreak;\n-\t\ttg_one = buf.buf;\n-\t\trf_one = strchr(tg_one, '\\t');\n-\t\tif (rf_one)\n-\t\t\t*rf_one++ = 0;\n-\n-\t\tif (targets >= targets_alloc) {\n-\t\t\ttargets_alloc = targets_alloc ? targets_alloc * 2 : 64;\n-\t\t\t*target = xrealloc(*target, targets_alloc * sizeof(**target));\n-\t\t\t*write_ref = xrealloc(*write_ref, targets_alloc * sizeof(**write_ref));\n-\t\t}\n-\t\t(*target)[targets] = xstrdup(tg_one);\n-\t\t(*write_ref)[targets] = rf_one ? xstrdup(rf_one) : NULL;\n-\t\ttargets++;\n-\t}\n-\treturn targets;\n-}\n-\n-void pull_targets_free(int targets, char **target, const char **write_ref)\n-{\n-\twhile (targets--) {\n-\t\tfree(target[targets]);\n-\t\tif (write_ref && write_ref[targets])\n-\t\t\tfree((char *) write_ref[targets]);\n-\t}\n-}\n-\n-int pull(int targets, char **target, const char **write_ref,\n-         const char *write_ref_log_details)\n-{\n-\tstruct ref_lock **lock = xcalloc(targets, sizeof(struct ref_lock *));\n-\tunsigned char *sha1 = xmalloc(targets * 20);\n-\tchar *msg;\n-\tint ret;\n-\tint i;\n-\n-\tsave_commit_buffer = 0;\n-\ttrack_object_refs = 0;\n-\n-\tfor (i = 0; i < targets; i++) {\n-\t\tif (!write_ref || !write_ref[i])\n-\t\t\tcontinue;\n-\n-\t\tlock[i] = lock_ref_sha1(write_ref[i], NULL);\n-\t\tif (!lock[i]) {\n-\t\t\terror(\"Can't lock ref %s\", write_ref[i]);\n-\t\t\tgoto unlock_and_fail;\n-\t\t}\n-\t}\n-\n-\tif (!get_recover)\n-\t\tfor_each_ref(mark_complete, NULL);\n-\n-\tfor (i = 0; i < targets; i++) {\n-\t\tif (interpret_target(target[i], &sha1[20 * i])) {\n-\t\t\terror(\"Could not interpret %s as something to pull\", target[i]);\n-\t\t\tgoto unlock_and_fail;\n-\t\t}\n-\t\tif (process(lookup_unknown_object(&sha1[20 * i])))\n-\t\t\tgoto unlock_and_fail;\n-\t}\n-\n-\tif (loop())\n-\t\tgoto unlock_and_fail;\n-\n-\tif (write_ref_log_details) {\n-\t\tmsg = xmalloc(strlen(write_ref_log_details) + 12);\n-\t\tsprintf(msg, \"fetch from %s\", write_ref_log_details);\n-\t} else {\n-\t\tmsg = NULL;\n-\t}\n-\tfor (i = 0; i < targets; i++) {\n-\t\tif (!write_ref || !write_ref[i])\n-\t\t\tcontinue;\n-\t\tret = write_ref_sha1(lock[i], &sha1[20 * i], msg ? msg : \"fetch (unknown)\");\n-\t\tlock[i] = NULL;\n-\t\tif (ret)\n-\t\t\tgoto unlock_and_fail;\n-\t}\n-\tfree(msg);\n-\n-\treturn 0;\n-\n-\n-unlock_and_fail:\n-\tfor (i = 0; i < targets; i++)\n-\t\tif (lock[i])\n-\t\t\tunlock_ref(lock[i]);\n-\treturn -1;\n-}\ndiff --git a/fetch.h b/fetch.h\ndeleted file mode 100644\nindex be48c6f..0000000\n--- a/fetch.h\n+++ /dev/null\n@@ -1,54 +0,0 @@\n-#ifndef PULL_H\n-#define PULL_H\n-\n-/*\n- * Fetch object given SHA1 from the remote, and store it locally under\n- * GIT_OBJECT_DIRECTORY.  Return 0 on success, -1 on failure.  To be\n- * provided by the particular implementation.\n- */\n-extern int fetch(unsigned char *sha1);\n-\n-/*\n- * Fetch the specified object and store it locally; fetch() will be\n- * called later to determine success. To be provided by the particular\n- * implementation.\n- */\n-extern void prefetch(unsigned char *sha1);\n-\n-/*\n- * Fetch ref (relative to $GIT_DIR/refs) from the remote, and store\n- * the 20-byte SHA1 in sha1.  Return 0 on success, -1 on failure.  To\n- * be provided by the particular implementation.\n- */\n-extern int fetch_ref(char *ref, unsigned char *sha1);\n-\n-/* Set to fetch the target tree. */\n-extern int get_tree;\n-\n-/* Set to fetch the commit history. */\n-extern int get_history;\n-\n-/* Set to fetch the trees in the commit history. */\n-extern int get_all;\n-\n-/* Set to be verbose */\n-extern int get_verbosely;\n-\n-/* Set to check on all reachable objects. */\n-extern int get_recover;\n-\n-/* Report what we got under get_verbosely */\n-extern void pull_say(const char *, const char *);\n-\n-/* Load pull targets from stdin */\n-extern int pull_targets_stdin(char ***target, const char ***write_ref);\n-\n-/* Free up loaded targets */\n-extern void pull_targets_free(int targets, char **target, const char **write_ref);\n-\n-/* If write_ref is set, the ref filename to write the target value to. */\n-/* If write_ref_log_details is set, additional text will appear in the ref log. */\n-extern int pull(int targets, char **target, const char **write_ref,\n-\t\tconst char *write_ref_log_details);\n-\n-#endif /* PULL_H */\ndiff --git a/git.c b/git.c\nindex b949cbb..4c2d6da 100644\n--- a/git.c\n+++ b/git.c\n@@ -317,6 +317,9 @@ static void handle_internal_command(int argc, const char **argv)\n \t\t{ \"get-tar-commit-id\", cmd_get_tar_commit_id },\n \t\t{ \"grep\", cmd_grep, RUN_SETUP | USE_PAGER },\n \t\t{ \"help\", cmd_help },\n+#ifndef NO_CURL\n+\t\t{ \"http-fetch\", cmd_http_fetch, RUN_SETUP },\n+#endif\n \t\t{ \"init\", cmd_init_db },\n \t\t{ \"init-db\", cmd_init_db },\n \t\t{ \"log\", cmd_log, RUN_SETUP | USE_PAGER },\ndiff --git a/http-fetch.c b/http-fetch.c\ndeleted file mode 100644\nindex 7786110..0000000\n--- a/http-fetch.c\n+++ /dev/null\n@@ -1,1059 +0,0 @@\n-#include \"cache.h\"\n-#include \"commit.h\"\n-#include \"pack.h\"\n-#include \"fetch.h\"\n-#include \"http.h\"\n-\n-#define PREV_BUF_SIZE 4096\n-#define RANGE_HEADER_SIZE 30\n-\n-static int commits_on_stdin;\n-\n-static int got_alternates = -1;\n-static int corrupt_object_found;\n-\n-static struct curl_slist *no_pragma_header;\n-\n-struct alt_base\n-{\n-\tchar *base;\n-\tint got_indices;\n-\tstruct packed_git *packs;\n-\tstruct alt_base *next;\n-};\n-\n-static struct alt_base *alt;\n-\n-enum object_request_state {\n-\tWAITING,\n-\tABORTED,\n-\tACTIVE,\n-\tCOMPLETE,\n-};\n-\n-struct object_request\n-{\n-\tunsigned char sha1[20];\n-\tstruct alt_base *repo;\n-\tchar *url;\n-\tchar filename[PATH_MAX];\n-\tchar tmpfile[PATH_MAX];\n-\tint local;\n-\tenum object_request_state state;\n-\tCURLcode curl_result;\n-\tchar errorstr[CURL_ERROR_SIZE];\n-\tlong http_code;\n-\tunsigned char real_sha1[20];\n-\tSHA_CTX c;\n-\tz_stream stream;\n-\tint zret;\n-\tint rename;\n-\tstruct active_request_slot *slot;\n-\tstruct object_request *next;\n-};\n-\n-struct alternates_request {\n-\tconst char *base;\n-\tchar *url;\n-\tstruct buffer *buffer;\n-\tstruct active_request_slot *slot;\n-\tint http_specific;\n-};\n-\n-static struct object_request *object_queue_head;\n-\n-static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb,\n-\t\t\t       void *data)\n-{\n-\tunsigned char expn[4096];\n-\tsize_t size = eltsize * nmemb;\n-\tint posn = 0;\n-\tstruct object_request *obj_req = (struct object_request *)data;\n-\tdo {\n-\t\tssize_t retval = xwrite(obj_req->local,\n-\t\t\t\t       (char *) ptr + posn, size - posn);\n-\t\tif (retval < 0)\n-\t\t\treturn posn;\n-\t\tposn += retval;\n-\t} while (posn < size);\n-\n-\tobj_req->stream.avail_in = size;\n-\tobj_req->stream.next_in = ptr;\n-\tdo {\n-\t\tobj_req->stream.next_out = expn;\n-\t\tobj_req->stream.avail_out = sizeof(expn);\n-\t\tobj_req->zret = inflate(&obj_req->stream, Z_SYNC_FLUSH);\n-\t\tSHA1_Update(&obj_req->c, expn,\n-\t\t\t    sizeof(expn) - obj_req->stream.avail_out);\n-\t} while (obj_req->stream.avail_in && obj_req->zret == Z_OK);\n-\tdata_received++;\n-\treturn size;\n-}\n-\n-static int missing__target(int code, int result)\n-{\n-\treturn\t/* file:// URL -- do we ever use one??? */\n-\t\t(result == CURLE_FILE_COULDNT_READ_FILE) ||\n-\t\t/* http:// and https:// URL */\n-\t\t(code == 404 && result == CURLE_HTTP_RETURNED_ERROR) ||\n-\t\t/* ftp:// URL */\n-\t\t(code == 550 && result == CURLE_FTP_COULDNT_RETR_FILE)\n-\t\t;\n-}\n-\n-#define missing_target(a) missing__target((a)->http_code, (a)->curl_result)\n-\n-static void fetch_alternates(const char *base);\n-\n-static void process_object_response(void *callback_data);\n-\n-static void start_object_request(struct object_request *obj_req)\n-{\n-\tchar *hex = sha1_to_hex(obj_req->sha1);\n-\tchar prevfile[PATH_MAX];\n-\tchar *url;\n-\tchar *posn;\n-\tint prevlocal;\n-\tunsigned char prev_buf[PREV_BUF_SIZE];\n-\tssize_t prev_read = 0;\n-\tlong prev_posn = 0;\n-\tchar range[RANGE_HEADER_SIZE];\n-\tstruct curl_slist *range_header = NULL;\n-\tstruct active_request_slot *slot;\n-\n-\tsnprintf(prevfile, sizeof(prevfile), \"%s.prev\", obj_req->filename);\n-\tunlink(prevfile);\n-\trename(obj_req->tmpfile, prevfile);\n-\tunlink(obj_req->tmpfile);\n-\n-\tif (obj_req->local != -1)\n-\t\terror(\"fd leakage in start: %d\", obj_req->local);\n-\tobj_req->local = open(obj_req->tmpfile,\n-\t\t\t      O_WRONLY | O_CREAT | O_EXCL, 0666);\n-\t/* This could have failed due to the \"lazy directory creation\";\n-\t * try to mkdir the last path component.\n-\t */\n-\tif (obj_req->local < 0 && errno == ENOENT) {\n-\t\tchar *dir = strrchr(obj_req->tmpfile, '/');\n-\t\tif (dir) {\n-\t\t\t*dir = 0;\n-\t\t\tmkdir(obj_req->tmpfile, 0777);\n-\t\t\t*dir = '/';\n-\t\t}\n-\t\tobj_req->local = open(obj_req->tmpfile,\n-\t\t\t\t      O_WRONLY | O_CREAT | O_EXCL, 0666);\n-\t}\n-\n-\tif (obj_req->local < 0) {\n-\t\tobj_req->state = ABORTED;\n-\t\terror(\"Couldn't create temporary file %s for %s: %s\",\n-\t\t      obj_req->tmpfile, obj_req->filename, strerror(errno));\n-\t\treturn;\n-\t}\n-\n-\tmemset(&obj_req->stream, 0, sizeof(obj_req->stream));\n-\n-\tinflateInit(&obj_req->stream);\n-\n-\tSHA1_Init(&obj_req->c);\n-\n-\turl = xmalloc(strlen(obj_req->repo->base) + 51);\n-\tobj_req->url = xmalloc(strlen(obj_req->repo->base) + 51);\n-\tstrcpy(url, obj_req->repo->base);\n-\tposn = url + strlen(obj_req->repo->base);\n-\tstrcpy(posn, \"/objects/\");\n-\tposn += 9;\n-\tmemcpy(posn, hex, 2);\n-\tposn += 2;\n-\t*(posn++) = '/';\n-\tstrcpy(posn, hex + 2);\n-\tstrcpy(obj_req->url, url);\n-\n-\t/* If a previous temp file is present, process what was already\n-\t   fetched. */\n-\tprevlocal = open(prevfile, O_RDONLY);\n-\tif (prevlocal != -1) {\n-\t\tdo {\n-\t\t\tprev_read = xread(prevlocal, prev_buf, PREV_BUF_SIZE);\n-\t\t\tif (prev_read>0) {\n-\t\t\t\tif (fwrite_sha1_file(prev_buf,\n-\t\t\t\t\t\t     1,\n-\t\t\t\t\t\t     prev_read,\n-\t\t\t\t\t\t     obj_req) == prev_read) {\n-\t\t\t\t\tprev_posn += prev_read;\n-\t\t\t\t} else {\n-\t\t\t\t\tprev_read = -1;\n-\t\t\t\t}\n-\t\t\t}\n-\t\t} while (prev_read > 0);\n-\t\tclose(prevlocal);\n-\t}\n-\tunlink(prevfile);\n-\n-\t/* Reset inflate/SHA1 if there was an error reading the previous temp\n-\t   file; also rewind to the beginning of the local file. */\n-\tif (prev_read == -1) {\n-\t\tmemset(&obj_req->stream, 0, sizeof(obj_req->stream));\n-\t\tinflateInit(&obj_req->stream);\n-\t\tSHA1_Init(&obj_req->c);\n-\t\tif (prev_posn>0) {\n-\t\t\tprev_posn = 0;\n-\t\t\tlseek(obj_req->local, 0, SEEK_SET);\n-\t\t\tftruncate(obj_req->local, 0);\n-\t\t}\n-\t}\n-\n-\tslot = get_active_slot();\n-\tslot->callback_func = process_object_response;\n-\tslot->callback_data = obj_req;\n-\tobj_req->slot = slot;\n-\n-\tcurl_easy_setopt(slot->curl, CURLOPT_FILE, obj_req);\n-\tcurl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_sha1_file);\n-\tcurl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, obj_req->errorstr);\n-\tcurl_easy_setopt(slot->curl, CURLOPT_URL, url);\n-\tcurl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_pragma_header);\n-\n-\t/* If we have successfully processed data from a previous fetch\n-\t   attempt, only fetch the data we don't already have. */\n-\tif (prev_posn>0) {\n-\t\tif (get_verbosely)\n-\t\t\tfprintf(stderr,\n-\t\t\t\t\"Resuming fetch of object %s at byte %ld\\n\",\n-\t\t\t\thex, prev_posn);\n-\t\tsprintf(range, \"Range: bytes=%ld-\", prev_posn);\n-\t\trange_header = curl_slist_append(range_header, range);\n-\t\tcurl_easy_setopt(slot->curl,\n-\t\t\t\t CURLOPT_HTTPHEADER, range_header);\n-\t}\n-\n-\t/* Try to get the request started, abort the request on error */\n-\tobj_req->state = ACTIVE;\n-\tif (!start_active_slot(slot)) {\n-\t\tobj_req->state = ABORTED;\n-\t\tobj_req->slot = NULL;\n-\t\tclose(obj_req->local); obj_req->local = -1;\n-\t\tfree(obj_req->url);\n-\t\treturn;\n-\t}\n-}\n-\n-static void finish_object_request(struct object_request *obj_req)\n-{\n-\tstruct stat st;\n-\n-\tfchmod(obj_req->local, 0444);\n-\tclose(obj_req->local); obj_req->local = -1;\n-\n-\tif (obj_req->http_code == 416) {\n-\t\tfprintf(stderr, \"Warning: requested range invalid; we may already have all the data.\\n\");\n-\t} else if (obj_req->curl_result != CURLE_OK) {\n-\t\tif (stat(obj_req->tmpfile, &st) == 0)\n-\t\t\tif (st.st_size == 0)\n-\t\t\t\tunlink(obj_req->tmpfile);\n-\t\treturn;\n-\t}\n-\n-\tinflateEnd(&obj_req->stream);\n-\tSHA1_Final(obj_req->real_sha1, &obj_req->c);\n-\tif (obj_req->zret != Z_STREAM_END) {\n-\t\tunlink(obj_req->tmpfile);\n-\t\treturn;\n-\t}\n-\tif (hashcmp(obj_req->sha1, obj_req->real_sha1)) {\n-\t\tunlink(obj_req->tmpfile);\n-\t\treturn;\n-\t}\n-\tobj_req->rename =\n-\t\tmove_temp_to_file(obj_req->tmpfile, obj_req->filename);\n-\n-\tif (obj_req->rename == 0)\n-\t\tpull_say(\"got %s\\n\", sha1_to_hex(obj_req->sha1));\n-}\n-\n-static void process_object_response(void *callback_data)\n-{\n-\tstruct object_request *obj_req =\n-\t\t(struct object_request *)callback_data;\n-\n-\tobj_req->curl_result = obj_req->slot->curl_result;\n-\tobj_req->http_code = obj_req->slot->http_code;\n-\tobj_req->slot = NULL;\n-\tobj_req->state = COMPLETE;\n-\n-\t/* Use alternates if necessary */\n-\tif (missing_target(obj_req)) {\n-\t\tfetch_alternates(alt->base);\n-\t\tif (obj_req->repo->next != NULL) {\n-\t\t\tobj_req->repo =\n-\t\t\t\tobj_req->repo->next;\n-\t\t\tclose(obj_req->local);\n-\t\t\tobj_req->local = -1;\n-\t\t\tstart_object_request(obj_req);\n-\t\t\treturn;\n-\t\t}\n-\t}\n-\n-\tfinish_object_request(obj_req);\n-}\n-\n-static void release_object_request(struct object_request *obj_req)\n-{\n-\tstruct object_request *entry = object_queue_head;\n-\n-\tif (obj_req->local != -1)\n-\t\terror(\"fd leakage in release: %d\", obj_req->local);\n-\tif (obj_req == object_queue_head) {\n-\t\tobject_queue_head = obj_req->next;\n-\t} else {\n-\t\twhile (entry->next != NULL && entry->next != obj_req)\n-\t\t\tentry = entry->next;\n-\t\tif (entry->next == obj_req)\n-\t\t\tentry->next = entry->next->next;\n-\t}\n-\n-\tfree(obj_req->url);\n-\tfree(obj_req);\n-}\n-\n-#ifdef USE_CURL_MULTI\n-static int fill_active_slot(void *unused)\n-{\n-\tstruct object_request *obj_req;\n-\n-\tfor (obj_req = object_queue_head; obj_req; obj_req = obj_req->next) {\n-\t\tif (obj_req->state == WAITING) {\n-\t\t\tif (has_sha1_file(obj_req->sha1))\n-\t\t\t\tobj_req->state = COMPLETE;\n-\t\t\telse {\n-\t\t\t\tstart_object_request(obj_req);\n-\t\t\t\treturn 1;\n-\t\t\t}\n-\t\t}\n-\t}\n-\treturn 0;\n-}\n-#endif\n-\n-void prefetch(unsigned char *sha1)\n-{\n-\tstruct object_request *newreq;\n-\tstruct object_request *tail;\n-\tchar *filename = sha1_file_name(sha1);\n-\n-\tnewreq = xmalloc(sizeof(*newreq));\n-\thashcpy(newreq->sha1, sha1);\n-\tnewreq->repo = alt;\n-\tnewreq->url = NULL;\n-\tnewreq->local = -1;\n-\tnewreq->state = WAITING;\n-\tsnprintf(newreq->filename, sizeof(newreq->filename), \"%s\", filename);\n-\tsnprintf(newreq->tmpfile, sizeof(newreq->tmpfile),\n-\t\t \"%s.temp\", filename);\n-\tnewreq->slot = NULL;\n-\tnewreq->next = NULL;\n-\n-\tif (object_queue_head == NULL) {\n-\t\tobject_queue_head = newreq;\n-\t} else {\n-\t\ttail = object_queue_head;\n-\t\twhile (tail->next != NULL) {\n-\t\t\ttail = tail->next;\n-\t\t}\n-\t\ttail->next = newreq;\n-\t}\n-\n-#ifdef USE_CURL_MULTI\n-\tfill_active_slots();\n-\tstep_active_slots();\n-#endif\n-}\n-\n-static int fetch_index(struct alt_base *repo, unsigned char *sha1)\n-{\n-\tchar *hex = sha1_to_hex(sha1);\n-\tchar *filename;\n-\tchar *url;\n-\tchar tmpfile[PATH_MAX];\n-\tlong prev_posn = 0;\n-\tchar range[RANGE_HEADER_SIZE];\n-\tstruct curl_slist *range_header = NULL;\n-\n-\tFILE *indexfile;\n-\tstruct active_request_slot *slot;\n-\tstruct slot_results results;\n-\n-\tif (has_pack_index(sha1))\n-\t\treturn 0;\n-\n-\tif (get_verbosely)\n-\t\tfprintf(stderr, \"Getting index for pack %s\\n\", hex);\n-\n-\turl = xmalloc(strlen(repo->base) + 64);\n-\tsprintf(url, \"%s/objects/pack/pack-%s.idx\", repo->base, hex);\n-\n-\tfilename = sha1_pack_index_name(sha1);\n-\tsnprintf(tmpfile, sizeof(tmpfile), \"%s.temp\", filename);\n-\tindexfile = fopen(tmpfile, \"a\");\n-\tif (!indexfile)\n-\t\treturn error(\"Unable to open local file %s for pack index\",\n-\t\t\t     filename);\n-\n-\tslot = get_active_slot();\n-\tslot->results = &results;\n-\tcurl_easy_setopt(slot->curl, CURLOPT_FILE, indexfile);\n-\tcurl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite);\n-\tcurl_easy_setopt(slot->curl, CURLOPT_URL, url);\n-\tcurl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_pragma_header);\n-\tslot->local = indexfile;\n-\n-\t/* If there is data present from a previous transfer attempt,\n-\t   resume where it left off */\n-\tprev_posn = ftell(indexfile);\n-\tif (prev_posn>0) {\n-\t\tif (get_verbosely)\n-\t\t\tfprintf(stderr,\n-\t\t\t\t\"Resuming fetch of index for pack %s at byte %ld\\n\",\n-\t\t\t\thex, prev_posn);\n-\t\tsprintf(range, \"Range: bytes=%ld-\", prev_posn);\n-\t\trange_header = curl_slist_append(range_header, range);\n-\t\tcurl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, range_header);\n-\t}\n-\n-\tif (start_active_slot(slot)) {\n-\t\trun_active_slot(slot);\n-\t\tif (results.curl_result != CURLE_OK) {\n-\t\t\tfclose(indexfile);\n-\t\t\treturn error(\"Unable to get pack index %s\\n%s\", url,\n-\t\t\t\t     curl_errorstr);\n-\t\t}\n-\t} else {\n-\t\tfclose(indexfile);\n-\t\treturn error(\"Unable to start request\");\n-\t}\n-\n-\tfclose(indexfile);\n-\n-\treturn move_temp_to_file(tmpfile, filename);\n-}\n-\n-static int setup_index(struct alt_base *repo, unsigned char *sha1)\n-{\n-\tstruct packed_git *new_pack;\n-\tif (has_pack_file(sha1))\n-\t\treturn 0; /* don't list this as something we can get */\n-\n-\tif (fetch_index(repo, sha1))\n-\t\treturn -1;\n-\n-\tnew_pack = parse_pack_index(sha1);\n-\tnew_pack->next = repo->packs;\n-\trepo->packs = new_pack;\n-\treturn 0;\n-}\n-\n-static void process_alternates_response(void *callback_data)\n-{\n-\tstruct alternates_request *alt_req =\n-\t\t(struct alternates_request *)callback_data;\n-\tstruct active_request_slot *slot = alt_req->slot;\n-\tstruct alt_base *tail = alt;\n-\tconst char *base = alt_req->base;\n-\tstatic const char null_byte = '\\0';\n-\tchar *data;\n-\tint i = 0;\n-\n-\tif (alt_req->http_specific) {\n-\t\tif (slot->curl_result != CURLE_OK ||\n-\t\t    !alt_req->buffer->posn) {\n-\n-\t\t\t/* Try reusing the slot to get non-http alternates */\n-\t\t\talt_req->http_specific = 0;\n-\t\t\tsprintf(alt_req->url, \"%s/objects/info/alternates\",\n-\t\t\t\tbase);\n-\t\t\tcurl_easy_setopt(slot->curl, CURLOPT_URL,\n-\t\t\t\t\t alt_req->url);\n-\t\t\tactive_requests++;\n-\t\t\tslot->in_use = 1;\n-\t\t\tif (slot->finished != NULL)\n-\t\t\t\t(*slot->finished) = 0;\n-\t\t\tif (!start_active_slot(slot)) {\n-\t\t\t\tgot_alternates = -1;\n-\t\t\t\tslot->in_use = 0;\n-\t\t\t\tif (slot->finished != NULL)\n-\t\t\t\t\t(*slot->finished) = 1;\n-\t\t\t}\n-\t\t\treturn;\n-\t\t}\n-\t} else if (slot->curl_result != CURLE_OK) {\n-\t\tif (!missing_target(slot)) {\n-\t\t\tgot_alternates = -1;\n-\t\t\treturn;\n-\t\t}\n-\t}\n-\n-\tfwrite_buffer(&null_byte, 1, 1, alt_req->buffer);\n-\talt_req->buffer->posn--;\n-\tdata = alt_req->buffer->buffer;\n-\n-\twhile (i < alt_req->buffer->posn) {\n-\t\tint posn = i;\n-\t\twhile (posn < alt_req->buffer->posn && data[posn] != '\\n')\n-\t\t\tposn++;\n-\t\tif (data[posn] == '\\n') {\n-\t\t\tint okay = 0;\n-\t\t\tint serverlen = 0;\n-\t\t\tstruct alt_base *newalt;\n-\t\t\tchar *target = NULL;\n-\t\t\tif (data[i] == '/') {\n-\t\t\t\t/* This counts\n-\t\t\t\t * http://git.host/pub/scm/linux.git/\n-\t\t\t\t * -----------here^\n-\t\t\t\t * so memcpy(dst, base, serverlen) will\n-\t\t\t\t * copy up to \"...git.host\".\n-\t\t\t\t */\n-\t\t\t\tconst char *colon_ss = strstr(base,\"://\");\n-\t\t\t\tif (colon_ss) {\n-\t\t\t\t\tserverlen = (strchr(colon_ss + 3, '/')\n-\t\t\t\t\t\t     - base);\n-\t\t\t\t\tokay = 1;\n-\t\t\t\t}\n-\t\t\t} else if (!memcmp(data + i, \"../\", 3)) {\n-\t\t\t\t/* Relative URL; chop the corresponding\n-\t\t\t\t * number of subpath from base (and ../\n-\t\t\t\t * from data), and concatenate the result.\n-\t\t\t\t *\n-\t\t\t\t * The code first drops ../ from data, and\n-\t\t\t\t * then drops one ../ from data and one path\n-\t\t\t\t * from base.  IOW, one extra ../ is dropped\n-\t\t\t\t * from data than path is dropped from base.\n-\t\t\t\t *\n-\t\t\t\t * This is not wrong.  The alternate in\n-\t\t\t\t *     http://git.host/pub/scm/linux.git/\n-\t\t\t\t * to borrow from\n-\t\t\t\t *     http://git.host/pub/scm/linus.git/\n-\t\t\t\t * is ../../linus.git/objects/.  You need\n-\t\t\t\t * two ../../ to borrow from your direct\n-\t\t\t\t * neighbour.\n-\t\t\t\t */\n-\t\t\t\ti += 3;\n-\t\t\t\tserverlen = strlen(base);\n-\t\t\t\twhile (i + 2 < posn &&\n-\t\t\t\t       !memcmp(data + i, \"../\", 3)) {\n-\t\t\t\t\tdo {\n-\t\t\t\t\t\tserverlen--;\n-\t\t\t\t\t} while (serverlen &&\n-\t\t\t\t\t\t base[serverlen - 1] != '/');\n-\t\t\t\t\ti += 3;\n-\t\t\t\t}\n-\t\t\t\t/* If the server got removed, give up. */\n-\t\t\t\tokay = strchr(base, ':') - base + 3 <\n-\t\t\t\t\tserverlen;\n-\t\t\t} else if (alt_req->http_specific) {\n-\t\t\t\tchar *colon = strchr(data + i, ':');\n-\t\t\t\tchar *slash = strchr(data + i, '/');\n-\t\t\t\tif (colon && slash && colon < data + posn &&\n-\t\t\t\t    slash < data + posn && colon < slash) {\n-\t\t\t\t\tokay = 1;\n-\t\t\t\t}\n-\t\t\t}\n-\t\t\t/* skip \"objects\\n\" at end */\n-\t\t\tif (okay) {\n-\t\t\t\ttarget = xmalloc(serverlen + posn - i - 6);\n-\t\t\t\tmemcpy(target, base, serverlen);\n-\t\t\t\tmemcpy(target + serverlen, data + i,\n-\t\t\t\t       posn - i - 7);\n-\t\t\t\ttarget[serverlen + posn - i - 7] = 0;\n-\t\t\t\tif (get_verbosely)\n-\t\t\t\t\tfprintf(stderr,\n-\t\t\t\t\t\t\"Also look at %s\\n\", target);\n-\t\t\t\tnewalt = xmalloc(sizeof(*newalt));\n-\t\t\t\tnewalt->next = NULL;\n-\t\t\t\tnewalt->base = target;\n-\t\t\t\tnewalt->got_indices = 0;\n-\t\t\t\tnewalt->packs = NULL;\n-\n-\t\t\t\twhile (tail->next != NULL)\n-\t\t\t\t\ttail = tail->next;\n-\t\t\t\ttail->next = newalt;\n-\t\t\t}\n-\t\t}\n-\t\ti = posn + 1;\n-\t}\n-\n-\tgot_alternates = 1;\n-}\n-\n-static void fetch_alternates(const char *base)\n-{\n-\tstruct buffer buffer;\n-\tchar *url;\n-\tchar *data;\n-\tstruct active_request_slot *slot;\n-\tstruct alternates_request alt_req;\n-\n-\t/* If another request has already started fetching alternates,\n-\t   wait for them to arrive and return to processing this request's\n-\t   curl message */\n-#ifdef USE_CURL_MULTI\n-\twhile (got_alternates == 0) {\n-\t\tstep_active_slots();\n-\t}\n-#endif\n-\n-\t/* Nothing to do if they've already been fetched */\n-\tif (got_alternates == 1)\n-\t\treturn;\n-\n-\t/* Start the fetch */\n-\tgot_alternates = 0;\n-\n-\tdata = xmalloc(4096);\n-\tbuffer.size = 4096;\n-\tbuffer.posn = 0;\n-\tbuffer.buffer = data;\n-\n-\tif (get_verbosely)\n-\t\tfprintf(stderr, \"Getting alternates list for %s\\n\", base);\n-\n-\turl = xmalloc(strlen(base) + 31);\n-\tsprintf(url, \"%s/objects/info/http-alternates\", base);\n-\n-\t/* Use a callback to process the result, since another request\n-\t   may fail and need to have alternates loaded before continuing */\n-\tslot = get_active_slot();\n-\tslot->callback_func = process_alternates_response;\n-\tslot->callback_data = &alt_req;\n-\n-\tcurl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);\n-\tcurl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);\n-\tcurl_easy_setopt(slot->curl, CURLOPT_URL, url);\n-\n-\talt_req.base = base;\n-\talt_req.url = url;\n-\talt_req.buffer = &buffer;\n-\talt_req.http_specific = 1;\n-\talt_req.slot = slot;\n-\n-\tif (start_active_slot(slot))\n-\t\trun_active_slot(slot);\n-\telse\n-\t\tgot_alternates = -1;\n-\n-\tfree(data);\n-\tfree(url);\n-}\n-\n-static int fetch_indices(struct alt_base *repo)\n-{\n-\tunsigned char sha1[20];\n-\tchar *url;\n-\tstruct buffer buffer;\n-\tchar *data;\n-\tint i = 0;\n-\n-\tstruct active_request_slot *slot;\n-\tstruct slot_results results;\n-\n-\tif (repo->got_indices)\n-\t\treturn 0;\n-\n-\tdata = xmalloc(4096);\n-\tbuffer.size = 4096;\n-\tbuffer.posn = 0;\n-\tbuffer.buffer = data;\n-\n-\tif (get_verbosely)\n-\t\tfprintf(stderr, \"Getting pack list for %s\\n\", repo->base);\n-\n-\turl = xmalloc(strlen(repo->base) + 21);\n-\tsprintf(url, \"%s/objects/info/packs\", repo->base);\n-\n-\tslot = get_active_slot();\n-\tslot->results = &results;\n-\tcurl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);\n-\tcurl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);\n-\tcurl_easy_setopt(slot->curl, CURLOPT_URL, url);\n-\tcurl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);\n-\tif (start_active_slot(slot)) {\n-\t\trun_active_slot(slot);\n-\t\tif (results.curl_result != CURLE_OK) {\n-\t\t\tif (missing_target(&results)) {\n-\t\t\t\trepo->got_indices = 1;\n-\t\t\t\tfree(buffer.buffer);\n-\t\t\t\treturn 0;\n-\t\t\t} else {\n-\t\t\t\trepo->got_indices = 0;\n-\t\t\t\tfree(buffer.buffer);\n-\t\t\t\treturn error(\"%s\", curl_errorstr);\n-\t\t\t}\n-\t\t}\n-\t} else {\n-\t\trepo->got_indices = 0;\n-\t\tfree(buffer.buffer);\n-\t\treturn error(\"Unable to start request\");\n-\t}\n-\n-\tdata = buffer.buffer;\n-\twhile (i < buffer.posn) {\n-\t\tswitch (data[i]) {\n-\t\tcase 'P':\n-\t\t\ti++;\n-\t\t\tif (i + 52 <= buffer.posn &&\n-\t\t\t    !prefixcmp(data + i, \" pack-\") &&\n-\t\t\t    !prefixcmp(data + i + 46, \".pack\\n\")) {\n-\t\t\t\tget_sha1_hex(data + i + 6, sha1);\n-\t\t\t\tsetup_index(repo, sha1);\n-\t\t\t\ti += 51;\n-\t\t\t\tbreak;\n-\t\t\t}\n-\t\tdefault:\n-\t\t\twhile (i < buffer.posn && data[i] != '\\n')\n-\t\t\t\ti++;\n-\t\t}\n-\t\ti++;\n-\t}\n-\n-\tfree(buffer.buffer);\n-\trepo->got_indices = 1;\n-\treturn 0;\n-}\n-\n-static int fetch_pack(struct alt_base *repo, unsigned char *sha1)\n-{\n-\tchar *url;\n-\tstruct packed_git *target;\n-\tstruct packed_git **lst;\n-\tFILE *packfile;\n-\tchar *filename;\n-\tchar tmpfile[PATH_MAX];\n-\tint ret;\n-\tlong prev_posn = 0;\n-\tchar range[RANGE_HEADER_SIZE];\n-\tstruct curl_slist *range_header = NULL;\n-\n-\tstruct active_request_slot *slot;\n-\tstruct slot_results results;\n-\n-\tif (fetch_indices(repo))\n-\t\treturn -1;\n-\ttarget = find_sha1_pack(sha1, repo->packs);\n-\tif (!target)\n-\t\treturn -1;\n-\n-\tif (get_verbosely) {\n-\t\tfprintf(stderr, \"Getting pack %s\\n\",\n-\t\t\tsha1_to_hex(target->sha1));\n-\t\tfprintf(stderr, \" which contains %s\\n\",\n-\t\t\tsha1_to_hex(sha1));\n-\t}\n-\n-\turl = xmalloc(strlen(repo->base) + 65);\n-\tsprintf(url, \"%s/objects/pack/pack-%s.pack\",\n-\t\trepo->base, sha1_to_hex(target->sha1));\n-\n-\tfilename = sha1_pack_name(target->sha1);\n-\tsnprintf(tmpfile, sizeof(tmpfile), \"%s.temp\", filename);\n-\tpackfile = fopen(tmpfile, \"a\");\n-\tif (!packfile)\n-\t\treturn error(\"Unable to open local file %s for pack\",\n-\t\t\t     filename);\n-\n-\tslot = get_active_slot();\n-\tslot->results = &results;\n-\tcurl_easy_setopt(slot->curl, CURLOPT_FILE, packfile);\n-\tcurl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite);\n-\tcurl_easy_setopt(slot->curl, CURLOPT_URL, url);\n-\tcurl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_pragma_header);\n-\tslot->local = packfile;\n-\n-\t/* If there is data present from a previous transfer attempt,\n-\t   resume where it left off */\n-\tprev_posn = ftell(packfile);\n-\tif (prev_posn>0) {\n-\t\tif (get_verbosely)\n-\t\t\tfprintf(stderr,\n-\t\t\t\t\"Resuming fetch of pack %s at byte %ld\\n\",\n-\t\t\t\tsha1_to_hex(target->sha1), prev_posn);\n-\t\tsprintf(range, \"Range: bytes=%ld-\", prev_posn);\n-\t\trange_header = curl_slist_append(range_header, range);\n-\t\tcurl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, range_header);\n-\t}\n-\n-\tif (start_active_slot(slot)) {\n-\t\trun_active_slot(slot);\n-\t\tif (results.curl_result != CURLE_OK) {\n-\t\t\tfclose(packfile);\n-\t\t\treturn error(\"Unable to get pack file %s\\n%s\", url,\n-\t\t\t\t     curl_errorstr);\n-\t\t}\n-\t} else {\n-\t\tfclose(packfile);\n-\t\treturn error(\"Unable to start request\");\n-\t}\n-\n-\ttarget->pack_size = ftell(packfile);\n-\tfclose(packfile);\n-\n-\tret = move_temp_to_file(tmpfile, filename);\n-\tif (ret)\n-\t\treturn ret;\n-\n-\tlst = &repo->packs;\n-\twhile (*lst != target)\n-\t\tlst = &((*lst)->next);\n-\t*lst = (*lst)->next;\n-\n-\tif (verify_pack(target, 0))\n-\t\treturn -1;\n-\tinstall_packed_git(target);\n-\n-\treturn 0;\n-}\n-\n-static void abort_object_request(struct object_request *obj_req)\n-{\n-\tif (obj_req->local >= 0) {\n-\t\tclose(obj_req->local);\n-\t\tobj_req->local = -1;\n-\t}\n-\tunlink(obj_req->tmpfile);\n-\tif (obj_req->slot) {\n-\t\trelease_active_slot(obj_req->slot);\n-\t\tobj_req->slot = NULL;\n-\t}\n-\trelease_object_request(obj_req);\n-}\n-\n-static int fetch_object(struct alt_base *repo, unsigned char *sha1)\n-{\n-\tchar *hex = sha1_to_hex(sha1);\n-\tint ret = 0;\n-\tstruct object_request *obj_req = object_queue_head;\n-\n-\twhile (obj_req != NULL && hashcmp(obj_req->sha1, sha1))\n-\t\tobj_req = obj_req->next;\n-\tif (obj_req == NULL)\n-\t\treturn error(\"Couldn't find request for %s in the queue\", hex);\n-\n-\tif (has_sha1_file(obj_req->sha1)) {\n-\t\tabort_object_request(obj_req);\n-\t\treturn 0;\n-\t}\n-\n-#ifdef USE_CURL_MULTI\n-\twhile (obj_req->state == WAITING) {\n-\t\tstep_active_slots();\n-\t}\n-#else\n-\tstart_object_request(obj_req);\n-#endif\n-\n-\twhile (obj_req->state == ACTIVE) {\n-\t\trun_active_slot(obj_req->slot);\n-\t}\n-\tif (obj_req->local != -1) {\n-\t\tclose(obj_req->local); obj_req->local = -1;\n-\t}\n-\n-\tif (obj_req->state == ABORTED) {\n-\t\tret = error(\"Request for %s aborted\", hex);\n-\t} else if (obj_req->curl_result != CURLE_OK &&\n-\t\t   obj_req->http_code != 416) {\n-\t\tif (missing_target(obj_req))\n-\t\t\tret = -1; /* Be silent, it is probably in a pack. */\n-\t\telse\n-\t\t\tret = error(\"%s (curl_result = %d, http_code = %ld, sha1 = %s)\",\n-\t\t\t\t    obj_req->errorstr, obj_req->curl_result,\n-\t\t\t\t    obj_req->http_code, hex);\n-\t} else if (obj_req->zret != Z_STREAM_END) {\n-\t\tcorrupt_object_found++;\n-\t\tret = error(\"File %s (%s) corrupt\", hex, obj_req->url);\n-\t} else if (hashcmp(obj_req->sha1, obj_req->real_sha1)) {\n-\t\tret = error(\"File %s has bad hash\", hex);\n-\t} else if (obj_req->rename < 0) {\n-\t\tret = error(\"unable to write sha1 filename %s\",\n-\t\t\t    obj_req->filename);\n-\t}\n-\n-\trelease_object_request(obj_req);\n-\treturn ret;\n-}\n-\n-int fetch(unsigned char *sha1)\n-{\n-\tstruct alt_base *altbase = alt;\n-\n-\tif (!fetch_object(altbase, sha1))\n-\t\treturn 0;\n-\twhile (altbase) {\n-\t\tif (!fetch_pack(altbase, sha1))\n-\t\t\treturn 0;\n-\t\tfetch_alternates(alt->base);\n-\t\taltbase = altbase->next;\n-\t}\n-\treturn error(\"Unable to find %s under %s\", sha1_to_hex(sha1),\n-\t\t     alt->base);\n-}\n-\n-static inline int needs_quote(int ch)\n-{\n-\tif (((ch >= 'A') && (ch <= 'Z'))\n-\t\t\t|| ((ch >= 'a') && (ch <= 'z'))\n-\t\t\t|| ((ch >= '0') && (ch <= '9'))\n-\t\t\t|| (ch == '/')\n-\t\t\t|| (ch == '-')\n-\t\t\t|| (ch == '.'))\n-\t\treturn 0;\n-\treturn 1;\n-}\n-\n-static inline int hex(int v)\n-{\n-\tif (v < 10) return '0' + v;\n-\telse return 'A' + v - 10;\n-}\n-\n-static char *quote_ref_url(const char *base, const char *ref)\n-{\n-\tconst char *cp;\n-\tchar *dp, *qref;\n-\tint len, baselen, ch;\n-\n-\tbaselen = strlen(base);\n-\tlen = baselen + 7; /* \"/refs/\" + NUL */\n-\tfor (cp = ref; (ch = *cp) != 0; cp++, len++)\n-\t\tif (needs_quote(ch))\n-\t\t\tlen += 2; /* extra two hex plus replacement % */\n-\tqref = xmalloc(len);\n-\tmemcpy(qref, base, baselen);\n-\tmemcpy(qref + baselen, \"/refs/\", 6);\n-\tfor (cp = ref, dp = qref + baselen + 6; (ch = *cp) != 0; cp++) {\n-\t\tif (needs_quote(ch)) {\n-\t\t\t*dp++ = '%';\n-\t\t\t*dp++ = hex((ch >> 4) & 0xF);\n-\t\t\t*dp++ = hex(ch & 0xF);\n-\t\t}\n-\t\telse\n-\t\t\t*dp++ = ch;\n-\t}\n-\t*dp = 0;\n-\n-\treturn qref;\n-}\n-\n-int fetch_ref(char *ref, unsigned char *sha1)\n-{\n-        char *url;\n-        char hex[42];\n-        struct buffer buffer;\n-\tconst char *base = alt->base;\n-\tstruct active_request_slot *slot;\n-\tstruct slot_results results;\n-        buffer.size = 41;\n-        buffer.posn = 0;\n-        buffer.buffer = hex;\n-        hex[41] = '\\0';\n-\n-\turl = quote_ref_url(base, ref);\n-\tslot = get_active_slot();\n-\tslot->results = &results;\n-\tcurl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);\n-\tcurl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);\n-\tcurl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);\n-\tcurl_easy_setopt(slot->curl, CURLOPT_URL, url);\n-\tif (start_active_slot(slot)) {\n-\t\trun_active_slot(slot);\n-\t\tif (results.curl_result != CURLE_OK)\n-\t\t\treturn error(\"Couldn't get %s for %s\\n%s\",\n-\t\t\t\t     url, ref, curl_errorstr);\n-\t} else {\n-\t\treturn error(\"Unable to start request\");\n-\t}\n-\n-        hex[40] = '\\0';\n-        get_sha1_hex(hex, sha1);\n-        return 0;\n-}\n-\n-int main(int argc, const char **argv)\n-{\n-\tint commits;\n-\tconst char **write_ref = NULL;\n-\tchar **commit_id;\n-\tconst char *url;\n-\tchar *s;\n-\tint arg = 1;\n-\tint rc = 0;\n-\n-\tsetup_git_directory();\n-\tgit_config(git_default_config);\n-\n-\twhile (arg < argc && argv[arg][0] == '-') {\n-\t\tif (argv[arg][1] == 't') {\n-\t\t\tget_tree = 1;\n-\t\t} else if (argv[arg][1] == 'c') {\n-\t\t\tget_history = 1;\n-\t\t} else if (argv[arg][1] == 'a') {\n-\t\t\tget_all = 1;\n-\t\t\tget_tree = 1;\n-\t\t\tget_history = 1;\n-\t\t} else if (argv[arg][1] == 'v') {\n-\t\t\tget_verbosely = 1;\n-\t\t} else if (argv[arg][1] == 'w') {\n-\t\t\twrite_ref = &argv[arg + 1];\n-\t\t\targ++;\n-\t\t} else if (!strcmp(argv[arg], \"--recover\")) {\n-\t\t\tget_recover = 1;\n-\t\t} else if (!strcmp(argv[arg], \"--stdin\")) {\n-\t\t\tcommits_on_stdin = 1;\n-\t\t}\n-\t\targ++;\n-\t}\n-\tif (argc < arg + 2 - commits_on_stdin) {\n-\t\tusage(\"git-http-fetch [-c] [-t] [-a] [-v] [--recover] [-w ref] [--stdin] commit-id url\");\n-\t\treturn 1;\n-\t}\n-\tif (commits_on_stdin) {\n-\t\tcommits = pull_targets_stdin(&commit_id, &write_ref);\n-\t} else {\n-\t\tcommit_id = (char **) &argv[arg++];\n-\t\tcommits = 1;\n-\t}\n-\turl = argv[arg];\n-\n-\thttp_init();\n-\n-\tno_pragma_header = curl_slist_append(no_pragma_header, \"Pragma:\");\n-\n-\talt = xmalloc(sizeof(*alt));\n-\talt->base = xmalloc(strlen(url) + 1);\n-\tstrcpy(alt->base, url);\n-\tfor (s = alt->base + strlen(alt->base) - 1; *s == '/'; --s)\n-\t\t*s = 0;\n-\talt->got_indices = 0;\n-\talt->packs = NULL;\n-\talt->next = NULL;\n-\n-#ifdef USE_CURL_MULTI\n-\tadd_fill_function(NULL, fill_active_slot);\n-#endif\n-\n-\tif (pull(commits, commit_id, write_ref, url))\n-\t\trc = 1;\n-\n-\thttp_cleanup();\n-\n-\tcurl_slist_free_all(no_pragma_header);\n-\n-\tif (commits_on_stdin)\n-\t\tpull_targets_free(commits, commit_id, write_ref);\n-\n-\tif (corrupt_object_found) {\n-\t\tfprintf(stderr,\n-\"Some loose object were found to be corrupt, but they might be just\\n\"\n-\"a false '404 Not Found' error message sent with incorrect HTTP\\n\"\n-\"status code.  Suggest running git-fsck.\\n\");\n-\t}\n-\treturn rc;\n-}\ndiff --git a/http-push.c b/http-push.c\nindex c54230b..8e1fdfd 100644\n--- a/http-push.c\n+++ b/http-push.c\n@@ -1,7 +1,6 @@\n #include \"cache.h\"\n #include \"commit.h\"\n #include \"pack.h\"\n-#include \"fetch.h\"\n #include \"tag.h\"\n #include \"blob.h\"\n #include \"http.h\"\ndiff --git a/http-walker.c b/http-walker.c\nnew file mode 100644\nindex 0000000..1069a57\n--- /dev/null\n+++ b/http-walker.c\n@@ -0,0 +1,1035 @@\n+#include \"cache.h\"\n+#include \"commit.h\"\n+#include \"pack.h\"\n+#include \"walker.h\"\n+#include \"http.h\"\n+\n+#define PREV_BUF_SIZE 4096\n+#define RANGE_HEADER_SIZE 30\n+\n+struct alt_base\n+{\n+\tchar *base;\n+\tint got_indices;\n+\tstruct packed_git *packs;\n+\tstruct alt_base *next;\n+};\n+\n+enum object_request_state {\n+\tWAITING,\n+\tABORTED,\n+\tACTIVE,\n+\tCOMPLETE,\n+};\n+\n+struct object_request\n+{\n+\tstruct walker *walker;\n+\tunsigned char sha1[20];\n+\tstruct alt_base *repo;\n+\tchar *url;\n+\tchar filename[PATH_MAX];\n+\tchar tmpfile[PATH_MAX];\n+\tint local;\n+\tenum object_request_state state;\n+\tCURLcode curl_result;\n+\tchar errorstr[CURL_ERROR_SIZE];\n+\tlong http_code;\n+\tunsigned char real_sha1[20];\n+\tSHA_CTX c;\n+\tz_stream stream;\n+\tint zret;\n+\tint rename;\n+\tstruct active_request_slot *slot;\n+\tstruct object_request *next;\n+};\n+\n+struct alternates_request {\n+\tstruct walker *walker;\n+\tconst char *base;\n+\tchar *url;\n+\tstruct buffer *buffer;\n+\tstruct active_request_slot *slot;\n+\tint http_specific;\n+};\n+\n+struct walker_data {\n+\tconst char *url;\n+\tint got_alternates;\n+\tstruct alt_base *alt;\n+\tstruct curl_slist *no_pragma_header;\n+};\n+\n+static struct object_request *object_queue_head;\n+\n+static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb,\n+\t\t\t       void *data)\n+{\n+\tunsigned char expn[4096];\n+\tsize_t size = eltsize * nmemb;\n+\tint posn = 0;\n+\tstruct object_request *obj_req = (struct object_request *)data;\n+\tdo {\n+\t\tssize_t retval = xwrite(obj_req->local,\n+\t\t\t\t       (char *) ptr + posn, size - posn);\n+\t\tif (retval < 0)\n+\t\t\treturn posn;\n+\t\tposn += retval;\n+\t} while (posn < size);\n+\n+\tobj_req->stream.avail_in = size;\n+\tobj_req->stream.next_in = ptr;\n+\tdo {\n+\t\tobj_req->stream.next_out = expn;\n+\t\tobj_req->stream.avail_out = sizeof(expn);\n+\t\tobj_req->zret = inflate(&obj_req->stream, Z_SYNC_FLUSH);\n+\t\tSHA1_Update(&obj_req->c, expn,\n+\t\t\t    sizeof(expn) - obj_req->stream.avail_out);\n+\t} while (obj_req->stream.avail_in && obj_req->zret == Z_OK);\n+\tdata_received++;\n+\treturn size;\n+}\n+\n+static int missing__target(int code, int result)\n+{\n+\treturn\t/* file:// URL -- do we ever use one??? */\n+\t\t(result == CURLE_FILE_COULDNT_READ_FILE) ||\n+\t\t/* http:// and https:// URL */\n+\t\t(code == 404 && result == CURLE_HTTP_RETURNED_ERROR) ||\n+\t\t/* ftp:// URL */\n+\t\t(code == 550 && result == CURLE_FTP_COULDNT_RETR_FILE)\n+\t\t;\n+}\n+\n+#define missing_target(a) missing__target((a)->http_code, (a)->curl_result)\n+\n+static void fetch_alternates(struct walker *walker, const char *base);\n+\n+static void process_object_response(void *callback_data);\n+\n+static void start_object_request(struct walker *walker,\n+\t\t\t\t struct object_request *obj_req)\n+{\n+\tchar *hex = sha1_to_hex(obj_req->sha1);\n+\tchar prevfile[PATH_MAX];\n+\tchar *url;\n+\tchar *posn;\n+\tint prevlocal;\n+\tunsigned char prev_buf[PREV_BUF_SIZE];\n+\tssize_t prev_read = 0;\n+\tlong prev_posn = 0;\n+\tchar range[RANGE_HEADER_SIZE];\n+\tstruct curl_slist *range_header = NULL;\n+\tstruct active_request_slot *slot;\n+\tstruct walker_data *data = walker->data;\n+\n+\tsnprintf(prevfile, sizeof(prevfile), \"%s.prev\", obj_req->filename);\n+\tunlink(prevfile);\n+\trename(obj_req->tmpfile, prevfile);\n+\tunlink(obj_req->tmpfile);\n+\n+\tif (obj_req->local != -1)\n+\t\terror(\"fd leakage in start: %d\", obj_req->local);\n+\tobj_req->local = open(obj_req->tmpfile,\n+\t\t\t      O_WRONLY | O_CREAT | O_EXCL, 0666);\n+\t/* This could have failed due to the \"lazy directory creation\";\n+\t * try to mkdir the last path component.\n+\t */\n+\tif (obj_req->local < 0 && errno == ENOENT) {\n+\t\tchar *dir = strrchr(obj_req->tmpfile, '/');\n+\t\tif (dir) {\n+\t\t\t*dir = 0;\n+\t\t\tmkdir(obj_req->tmpfile, 0777);\n+\t\t\t*dir = '/';\n+\t\t}\n+\t\tobj_req->local = open(obj_req->tmpfile,\n+\t\t\t\t      O_WRONLY | O_CREAT | O_EXCL, 0666);\n+\t}\n+\n+\tif (obj_req->local < 0) {\n+\t\tobj_req->state = ABORTED;\n+\t\terror(\"Couldn't create temporary file %s for %s: %s\",\n+\t\t      obj_req->tmpfile, obj_req->filename, strerror(errno));\n+\t\treturn;\n+\t}\n+\n+\tmemset(&obj_req->stream, 0, sizeof(obj_req->stream));\n+\n+\tinflateInit(&obj_req->stream);\n+\n+\tSHA1_Init(&obj_req->c);\n+\n+\turl = xmalloc(strlen(obj_req->repo->base) + 51);\n+\tobj_req->url = xmalloc(strlen(obj_req->repo->base) + 51);\n+\tstrcpy(url, obj_req->repo->base);\n+\tposn = url + strlen(obj_req->repo->base);\n+\tstrcpy(posn, \"/objects/\");\n+\tposn += 9;\n+\tmemcpy(posn, hex, 2);\n+\tposn += 2;\n+\t*(posn++) = '/';\n+\tstrcpy(posn, hex + 2);\n+\tstrcpy(obj_req->url, url);\n+\n+\t/* If a previous temp file is present, process what was already\n+\t   fetched. */\n+\tprevlocal = open(prevfile, O_RDONLY);\n+\tif (prevlocal != -1) {\n+\t\tdo {\n+\t\t\tprev_read = xread(prevlocal, prev_buf, PREV_BUF_SIZE);\n+\t\t\tif (prev_read>0) {\n+\t\t\t\tif (fwrite_sha1_file(prev_buf,\n+\t\t\t\t\t\t     1,\n+\t\t\t\t\t\t     prev_read,\n+\t\t\t\t\t\t     obj_req) == prev_read) {\n+\t\t\t\t\tprev_posn += prev_read;\n+\t\t\t\t} else {\n+\t\t\t\t\tprev_read = -1;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t} while (prev_read > 0);\n+\t\tclose(prevlocal);\n+\t}\n+\tunlink(prevfile);\n+\n+\t/* Reset inflate/SHA1 if there was an error reading the previous temp\n+\t   file; also rewind to the beginning of the local file. */\n+\tif (prev_read == -1) {\n+\t\tmemset(&obj_req->stream, 0, sizeof(obj_req->stream));\n+\t\tinflateInit(&obj_req->stream);\n+\t\tSHA1_Init(&obj_req->c);\n+\t\tif (prev_posn>0) {\n+\t\t\tprev_posn = 0;\n+\t\t\tlseek(obj_req->local, 0, SEEK_SET);\n+\t\t\tftruncate(obj_req->local, 0);\n+\t\t}\n+\t}\n+\n+\tslot = get_active_slot();\n+\tslot->callback_func = process_object_response;\n+\tslot->callback_data = obj_req;\n+\tobj_req->slot = slot;\n+\n+\tcurl_easy_setopt(slot->curl, CURLOPT_FILE, obj_req);\n+\tcurl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_sha1_file);\n+\tcurl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, obj_req->errorstr);\n+\tcurl_easy_setopt(slot->curl, CURLOPT_URL, url);\n+\tcurl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, data->no_pragma_header);\n+\n+\t/* If we have successfully processed data from a previous fetch\n+\t   attempt, only fetch the data we don't already have. */\n+\tif (prev_posn>0) {\n+\t\tif (walker->get_verbosely)\n+\t\t\tfprintf(stderr,\n+\t\t\t\t\"Resuming fetch of object %s at byte %ld\\n\",\n+\t\t\t\thex, prev_posn);\n+\t\tsprintf(range, \"Range: bytes=%ld-\", prev_posn);\n+\t\trange_header = curl_slist_append(range_header, range);\n+\t\tcurl_easy_setopt(slot->curl,\n+\t\t\t\t CURLOPT_HTTPHEADER, range_header);\n+\t}\n+\n+\t/* Try to get the request started, abort the request on error */\n+\tobj_req->state = ACTIVE;\n+\tif (!start_active_slot(slot)) {\n+\t\tobj_req->state = ABORTED;\n+\t\tobj_req->slot = NULL;\n+\t\tclose(obj_req->local); obj_req->local = -1;\n+\t\tfree(obj_req->url);\n+\t\treturn;\n+\t}\n+}\n+\n+static void finish_object_request(struct object_request *obj_req)\n+{\n+\tstruct stat st;\n+\n+\tfchmod(obj_req->local, 0444);\n+\tclose(obj_req->local); obj_req->local = -1;\n+\n+\tif (obj_req->http_code == 416) {\n+\t\tfprintf(stderr, \"Warning: requested range invalid; we may already have all the data.\\n\");\n+\t} else if (obj_req->curl_result != CURLE_OK) {\n+\t\tif (stat(obj_req->tmpfile, &st) == 0)\n+\t\t\tif (st.st_size == 0)\n+\t\t\t\tunlink(obj_req->tmpfile);\n+\t\treturn;\n+\t}\n+\n+\tinflateEnd(&obj_req->stream);\n+\tSHA1_Final(obj_req->real_sha1, &obj_req->c);\n+\tif (obj_req->zret != Z_STREAM_END) {\n+\t\tunlink(obj_req->tmpfile);\n+\t\treturn;\n+\t}\n+\tif (hashcmp(obj_req->sha1, obj_req->real_sha1)) {\n+\t\tunlink(obj_req->tmpfile);\n+\t\treturn;\n+\t}\n+\tobj_req->rename =\n+\t\tmove_temp_to_file(obj_req->tmpfile, obj_req->filename);\n+\n+\tif (obj_req->rename == 0)\n+\t\twalker_say(obj_req->walker, \"got %s\\n\", sha1_to_hex(obj_req->sha1));\n+}\n+\n+static void process_object_response(void *callback_data)\n+{\n+\tstruct object_request *obj_req =\n+\t\t(struct object_request *)callback_data;\n+\tstruct walker *walker = obj_req->walker;\n+\tstruct walker_data *data = walker->data;\n+\tstruct alt_base *alt = data->alt;\n+\n+\tobj_req->curl_result = obj_req->slot->curl_result;\n+\tobj_req->http_code = obj_req->slot->http_code;\n+\tobj_req->slot = NULL;\n+\tobj_req->state = COMPLETE;\n+\n+\t/* Use alternates if necessary */\n+\tif (missing_target(obj_req)) {\n+\t\tfetch_alternates(walker, alt->base);\n+\t\tif (obj_req->repo->next != NULL) {\n+\t\t\tobj_req->repo =\n+\t\t\t\tobj_req->repo->next;\n+\t\t\tclose(obj_req->local);\n+\t\t\tobj_req->local = -1;\n+\t\t\tstart_object_request(walker, obj_req);\n+\t\t\treturn;\n+\t\t}\n+\t}\n+\n+\tfinish_object_request(obj_req);\n+}\n+\n+static void release_object_request(struct object_request *obj_req)\n+{\n+\tstruct object_request *entry = object_queue_head;\n+\n+\tif (obj_req->local != -1)\n+\t\terror(\"fd leakage in release: %d\", obj_req->local);\n+\tif (obj_req == object_queue_head) {\n+\t\tobject_queue_head = obj_req->next;\n+\t} else {\n+\t\twhile (entry->next != NULL && entry->next != obj_req)\n+\t\t\tentry = entry->next;\n+\t\tif (entry->next == obj_req)\n+\t\t\tentry->next = entry->next->next;\n+\t}\n+\n+\tfree(obj_req->url);\n+\tfree(obj_req);\n+}\n+\n+#ifdef USE_CURL_MULTI\n+static int fill_active_slot(struct walker *walker)\n+{\n+\tstruct object_request *obj_req;\n+\n+\tfor (obj_req = object_queue_head; obj_req; obj_req = obj_req->next) {\n+\t\tif (obj_req->state == WAITING) {\n+\t\t\tif (has_sha1_file(obj_req->sha1))\n+\t\t\t\tobj_req->state = COMPLETE;\n+\t\t\telse {\n+\t\t\t\tstart_object_request(walker, obj_req);\n+\t\t\t\treturn 1;\n+\t\t\t}\n+\t\t}\n+\t}\n+\treturn 0;\n+}\n+#endif\n+\n+static void prefetch(struct walker *walker, unsigned char *sha1)\n+{\n+\tstruct object_request *newreq;\n+\tstruct object_request *tail;\n+\tstruct walker_data *data = walker->data;\n+\tchar *filename = sha1_file_name(sha1);\n+\n+\tnewreq = xmalloc(sizeof(*newreq));\n+\tnewreq->walker = walker;\n+\thashcpy(newreq->sha1, sha1);\n+\tnewreq->repo = data->alt;\n+\tnewreq->url = NULL;\n+\tnewreq->local = -1;\n+\tnewreq->state = WAITING;\n+\tsnprintf(newreq->filename, sizeof(newreq->filename), \"%s\", filename);\n+\tsnprintf(newreq->tmpfile, sizeof(newreq->tmpfile),\n+\t\t \"%s.temp\", filename);\n+\tnewreq->slot = NULL;\n+\tnewreq->next = NULL;\n+\n+\tif (object_queue_head == NULL) {\n+\t\tobject_queue_head = newreq;\n+\t} else {\n+\t\ttail = object_queue_head;\n+\t\twhile (tail->next != NULL) {\n+\t\t\ttail = tail->next;\n+\t\t}\n+\t\ttail->next = newreq;\n+\t}\n+\n+#ifdef USE_CURL_MULTI\n+\tfill_active_slots();\n+\tstep_active_slots();\n+#endif\n+}\n+\n+static int fetch_index(struct walker *walker, struct alt_base *repo, unsigned char *sha1)\n+{\n+\tchar *hex = sha1_to_hex(sha1);\n+\tchar *filename;\n+\tchar *url;\n+\tchar tmpfile[PATH_MAX];\n+\tlong prev_posn = 0;\n+\tchar range[RANGE_HEADER_SIZE];\n+\tstruct curl_slist *range_header = NULL;\n+\tstruct walker_data *data = walker->data;\n+\n+\tFILE *indexfile;\n+\tstruct active_request_slot *slot;\n+\tstruct slot_results results;\n+\n+\tif (has_pack_index(sha1))\n+\t\treturn 0;\n+\n+\tif (walker->get_verbosely)\n+\t\tfprintf(stderr, \"Getting index for pack %s\\n\", hex);\n+\n+\turl = xmalloc(strlen(repo->base) + 64);\n+\tsprintf(url, \"%s/objects/pack/pack-%s.idx\", repo->base, hex);\n+\n+\tfilename = sha1_pack_index_name(sha1);\n+\tsnprintf(tmpfile, sizeof(tmpfile), \"%s.temp\", filename);\n+\tindexfile = fopen(tmpfile, \"a\");\n+\tif (!indexfile)\n+\t\treturn error(\"Unable to open local file %s for pack index\",\n+\t\t\t     filename);\n+\n+\tslot = get_active_slot();\n+\tslot->results = &results;\n+\tcurl_easy_setopt(slot->curl, CURLOPT_FILE, indexfile);\n+\tcurl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite);\n+\tcurl_easy_setopt(slot->curl, CURLOPT_URL, url);\n+\tcurl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, data->no_pragma_header);\n+\tslot->local = indexfile;\n+\n+\t/* If there is data present from a previous transfer attempt,\n+\t   resume where it left off */\n+\tprev_posn = ftell(indexfile);\n+\tif (prev_posn>0) {\n+\t\tif (walker->get_verbosely)\n+\t\t\tfprintf(stderr,\n+\t\t\t\t\"Resuming fetch of index for pack %s at byte %ld\\n\",\n+\t\t\t\thex, prev_posn);\n+\t\tsprintf(range, \"Range: bytes=%ld-\", prev_posn);\n+\t\trange_header = curl_slist_append(range_header, range);\n+\t\tcurl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, range_header);\n+\t}\n+\n+\tif (start_active_slot(slot)) {\n+\t\trun_active_slot(slot);\n+\t\tif (results.curl_result != CURLE_OK) {\n+\t\t\tfclose(indexfile);\n+\t\t\treturn error(\"Unable to get pack index %s\\n%s\", url,\n+\t\t\t\t     curl_errorstr);\n+\t\t}\n+\t} else {\n+\t\tfclose(indexfile);\n+\t\treturn error(\"Unable to start request\");\n+\t}\n+\n+\tfclose(indexfile);\n+\n+\treturn move_temp_to_file(tmpfile, filename);\n+}\n+\n+static int setup_index(struct walker *walker, struct alt_base *repo, unsigned char *sha1)\n+{\n+\tstruct packed_git *new_pack;\n+\tif (has_pack_file(sha1))\n+\t\treturn 0; /* don't list this as something we can get */\n+\n+\tif (fetch_index(walker, repo, sha1))\n+\t\treturn -1;\n+\n+\tnew_pack = parse_pack_index(sha1);\n+\tnew_pack->next = repo->packs;\n+\trepo->packs = new_pack;\n+\treturn 0;\n+}\n+\n+static void process_alternates_response(void *callback_data)\n+{\n+\tstruct alternates_request *alt_req =\n+\t\t(struct alternates_request *)callback_data;\n+\tstruct walker *walker = alt_req->walker;\n+\tstruct walker_data *cdata = walker->data;\n+\tstruct active_request_slot *slot = alt_req->slot;\n+\tstruct alt_base *tail = cdata->alt;\n+\tconst char *base = alt_req->base;\n+\tstatic const char null_byte = '\\0';\n+\tchar *data;\n+\tint i = 0;\n+\n+\tif (alt_req->http_specific) {\n+\t\tif (slot->curl_result != CURLE_OK ||\n+\t\t    !alt_req->buffer->posn) {\n+\n+\t\t\t/* Try reusing the slot to get non-http alternates */\n+\t\t\talt_req->http_specific = 0;\n+\t\t\tsprintf(alt_req->url, \"%s/objects/info/alternates\",\n+\t\t\t\tbase);\n+\t\t\tcurl_easy_setopt(slot->curl, CURLOPT_URL,\n+\t\t\t\t\t alt_req->url);\n+\t\t\tactive_requests++;\n+\t\t\tslot->in_use = 1;\n+\t\t\tif (slot->finished != NULL)\n+\t\t\t\t(*slot->finished) = 0;\n+\t\t\tif (!start_active_slot(slot)) {\n+\t\t\t\tcdata->got_alternates = -1;\n+\t\t\t\tslot->in_use = 0;\n+\t\t\t\tif (slot->finished != NULL)\n+\t\t\t\t\t(*slot->finished) = 1;\n+\t\t\t}\n+\t\t\treturn;\n+\t\t}\n+\t} else if (slot->curl_result != CURLE_OK) {\n+\t\tif (!missing_target(slot)) {\n+\t\t\tcdata->got_alternates = -1;\n+\t\t\treturn;\n+\t\t}\n+\t}\n+\n+\tfwrite_buffer(&null_byte, 1, 1, alt_req->buffer);\n+\talt_req->buffer->posn--;\n+\tdata = alt_req->buffer->buffer;\n+\n+\twhile (i < alt_req->buffer->posn) {\n+\t\tint posn = i;\n+\t\twhile (posn < alt_req->buffer->posn && data[posn] != '\\n')\n+\t\t\tposn++;\n+\t\tif (data[posn] == '\\n') {\n+\t\t\tint okay = 0;\n+\t\t\tint serverlen = 0;\n+\t\t\tstruct alt_base *newalt;\n+\t\t\tchar *target = NULL;\n+\t\t\tif (data[i] == '/') {\n+\t\t\t\t/* This counts\n+\t\t\t\t * http://git.host/pub/scm/linux.git/\n+\t\t\t\t * -----------here^\n+\t\t\t\t * so memcpy(dst, base, serverlen) will\n+\t\t\t\t * copy up to \"...git.host\".\n+\t\t\t\t */\n+\t\t\t\tconst char *colon_ss = strstr(base,\"://\");\n+\t\t\t\tif (colon_ss) {\n+\t\t\t\t\tserverlen = (strchr(colon_ss + 3, '/')\n+\t\t\t\t\t\t     - base);\n+\t\t\t\t\tokay = 1;\n+\t\t\t\t}\n+\t\t\t} else if (!memcmp(data + i, \"../\", 3)) {\n+\t\t\t\t/* Relative URL; chop the corresponding\n+\t\t\t\t * number of subpath from base (and ../\n+\t\t\t\t * from data), and concatenate the result.\n+\t\t\t\t *\n+\t\t\t\t * The code first drops ../ from data, and\n+\t\t\t\t * then drops one ../ from data and one path\n+\t\t\t\t * from base.  IOW, one extra ../ is dropped\n+\t\t\t\t * from data than path is dropped from base.\n+\t\t\t\t *\n+\t\t\t\t * This is not wrong.  The alternate in\n+\t\t\t\t *     http://git.host/pub/scm/linux.git/\n+\t\t\t\t * to borrow from\n+\t\t\t\t *     http://git.host/pub/scm/linus.git/\n+\t\t\t\t * is ../../linus.git/objects/.  You need\n+\t\t\t\t * two ../../ to borrow from your direct\n+\t\t\t\t * neighbour.\n+\t\t\t\t */\n+\t\t\t\ti += 3;\n+\t\t\t\tserverlen = strlen(base);\n+\t\t\t\twhile (i + 2 < posn &&\n+\t\t\t\t       !memcmp(data + i, \"../\", 3)) {\n+\t\t\t\t\tdo {\n+\t\t\t\t\t\tserverlen--;\n+\t\t\t\t\t} while (serverlen &&\n+\t\t\t\t\t\t base[serverlen - 1] != '/');\n+\t\t\t\t\ti += 3;\n+\t\t\t\t}\n+\t\t\t\t/* If the server got removed, give up. */\n+\t\t\t\tokay = strchr(base, ':') - base + 3 <\n+\t\t\t\t\tserverlen;\n+\t\t\t} else if (alt_req->http_specific) {\n+\t\t\t\tchar *colon = strchr(data + i, ':');\n+\t\t\t\tchar *slash = strchr(data + i, '/');\n+\t\t\t\tif (colon && slash && colon < data + posn &&\n+\t\t\t\t    slash < data + posn && colon < slash) {\n+\t\t\t\t\tokay = 1;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\t/* skip \"objects\\n\" at end */\n+\t\t\tif (okay) {\n+\t\t\t\ttarget = xmalloc(serverlen + posn - i - 6);\n+\t\t\t\tmemcpy(target, base, serverlen);\n+\t\t\t\tmemcpy(target + serverlen, data + i,\n+\t\t\t\t       posn - i - 7);\n+\t\t\t\ttarget[serverlen + posn - i - 7] = 0;\n+\t\t\t\tif (walker->get_verbosely)\n+\t\t\t\t\tfprintf(stderr,\n+\t\t\t\t\t\t\"Also look at %s\\n\", target);\n+\t\t\t\tnewalt = xmalloc(sizeof(*newalt));\n+\t\t\t\tnewalt->next = NULL;\n+\t\t\t\tnewalt->base = target;\n+\t\t\t\tnewalt->got_indices = 0;\n+\t\t\t\tnewalt->packs = NULL;\n+\n+\t\t\t\twhile (tail->next != NULL)\n+\t\t\t\t\ttail = tail->next;\n+\t\t\t\ttail->next = newalt;\n+\t\t\t}\n+\t\t}\n+\t\ti = posn + 1;\n+\t}\n+\n+\tcdata->got_alternates = 1;\n+}\n+\n+static void fetch_alternates(struct walker *walker, const char *base)\n+{\n+\tstruct buffer buffer;\n+\tchar *url;\n+\tchar *data;\n+\tstruct active_request_slot *slot;\n+\tstruct alternates_request alt_req;\n+\tstruct walker_data *cdata = walker->data;\n+\n+\t/* If another request has already started fetching alternates,\n+\t   wait for them to arrive and return to processing this request's\n+\t   curl message */\n+#ifdef USE_CURL_MULTI\n+\twhile (cdata->got_alternates == 0) {\n+\t\tstep_active_slots();\n+\t}\n+#endif\n+\n+\t/* Nothing to do if they've already been fetched */\n+\tif (cdata->got_alternates == 1)\n+\t\treturn;\n+\n+\t/* Start the fetch */\n+\tcdata->got_alternates = 0;\n+\n+\tdata = xmalloc(4096);\n+\tbuffer.size = 4096;\n+\tbuffer.posn = 0;\n+\tbuffer.buffer = data;\n+\n+\tif (walker->get_verbosely)\n+\t\tfprintf(stderr, \"Getting alternates list for %s\\n\", base);\n+\n+\turl = xmalloc(strlen(base) + 31);\n+\tsprintf(url, \"%s/objects/info/http-alternates\", base);\n+\n+\t/* Use a callback to process the result, since another request\n+\t   may fail and need to have alternates loaded before continuing */\n+\tslot = get_active_slot();\n+\tslot->callback_func = process_alternates_response;\n+\talt_req.walker = walker;\n+\tslot->callback_data = &alt_req;\n+\n+\tcurl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);\n+\tcurl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);\n+\tcurl_easy_setopt(slot->curl, CURLOPT_URL, url);\n+\n+\talt_req.base = base;\n+\talt_req.url = url;\n+\talt_req.buffer = &buffer;\n+\talt_req.http_specific = 1;\n+\talt_req.slot = slot;\n+\n+\tif (start_active_slot(slot))\n+\t\trun_active_slot(slot);\n+\telse\n+\t\tcdata->got_alternates = -1;\n+\n+\tfree(data);\n+\tfree(url);\n+}\n+\n+static int fetch_indices(struct walker *walker, struct alt_base *repo)\n+{\n+\tunsigned char sha1[20];\n+\tchar *url;\n+\tstruct buffer buffer;\n+\tchar *data;\n+\tint i = 0;\n+\n+\tstruct active_request_slot *slot;\n+\tstruct slot_results results;\n+\n+\tif (repo->got_indices)\n+\t\treturn 0;\n+\n+\tdata = xmalloc(4096);\n+\tbuffer.size = 4096;\n+\tbuffer.posn = 0;\n+\tbuffer.buffer = data;\n+\n+\tif (walker->get_verbosely)\n+\t\tfprintf(stderr, \"Getting pack list for %s\\n\", repo->base);\n+\n+\turl = xmalloc(strlen(repo->base) + 21);\n+\tsprintf(url, \"%s/objects/info/packs\", repo->base);\n+\n+\tslot = get_active_slot();\n+\tslot->results = &results;\n+\tcurl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);\n+\tcurl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);\n+\tcurl_easy_setopt(slot->curl, CURLOPT_URL, url);\n+\tcurl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);\n+\tif (start_active_slot(slot)) {\n+\t\trun_active_slot(slot);\n+\t\tif (results.curl_result != CURLE_OK) {\n+\t\t\tif (missing_target(&results)) {\n+\t\t\t\trepo->got_indices = 1;\n+\t\t\t\tfree(buffer.buffer);\n+\t\t\t\treturn 0;\n+\t\t\t} else {\n+\t\t\t\trepo->got_indices = 0;\n+\t\t\t\tfree(buffer.buffer);\n+\t\t\t\treturn error(\"%s\", curl_errorstr);\n+\t\t\t}\n+\t\t}\n+\t} else {\n+\t\trepo->got_indices = 0;\n+\t\tfree(buffer.buffer);\n+\t\treturn error(\"Unable to start request\");\n+\t}\n+\n+\tdata = buffer.buffer;\n+\twhile (i < buffer.posn) {\n+\t\tswitch (data[i]) {\n+\t\tcase 'P':\n+\t\t\ti++;\n+\t\t\tif (i + 52 <= buffer.posn &&\n+\t\t\t    !prefixcmp(data + i, \" pack-\") &&\n+\t\t\t    !prefixcmp(data + i + 46, \".pack\\n\")) {\n+\t\t\t\tget_sha1_hex(data + i + 6, sha1);\n+\t\t\t\tsetup_index(walker, repo, sha1);\n+\t\t\t\ti += 51;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\tdefault:\n+\t\t\twhile (i < buffer.posn && data[i] != '\\n')\n+\t\t\t\ti++;\n+\t\t}\n+\t\ti++;\n+\t}\n+\n+\tfree(buffer.buffer);\n+\trepo->got_indices = 1;\n+\treturn 0;\n+}\n+\n+static int fetch_pack(struct walker *walker, struct alt_base *repo, unsigned char *sha1)\n+{\n+\tchar *url;\n+\tstruct packed_git *target;\n+\tstruct packed_git **lst;\n+\tFILE *packfile;\n+\tchar *filename;\n+\tchar tmpfile[PATH_MAX];\n+\tint ret;\n+\tlong prev_posn = 0;\n+\tchar range[RANGE_HEADER_SIZE];\n+\tstruct curl_slist *range_header = NULL;\n+\tstruct walker_data *data = walker->data;\n+\n+\tstruct active_request_slot *slot;\n+\tstruct slot_results results;\n+\n+\tif (fetch_indices(walker, repo))\n+\t\treturn -1;\n+\ttarget = find_sha1_pack(sha1, repo->packs);\n+\tif (!target)\n+\t\treturn -1;\n+\n+\tif (walker->get_verbosely) {\n+\t\tfprintf(stderr, \"Getting pack %s\\n\",\n+\t\t\tsha1_to_hex(target->sha1));\n+\t\tfprintf(stderr, \" which contains %s\\n\",\n+\t\t\tsha1_to_hex(sha1));\n+\t}\n+\n+\turl = xmalloc(strlen(repo->base) + 65);\n+\tsprintf(url, \"%s/objects/pack/pack-%s.pack\",\n+\t\trepo->base, sha1_to_hex(target->sha1));\n+\n+\tfilename = sha1_pack_name(target->sha1);\n+\tsnprintf(tmpfile, sizeof(tmpfile), \"%s.temp\", filename);\n+\tpackfile = fopen(tmpfile, \"a\");\n+\tif (!packfile)\n+\t\treturn error(\"Unable to open local file %s for pack\",\n+\t\t\t     filename);\n+\n+\tslot = get_active_slot();\n+\tslot->results = &results;\n+\tcurl_easy_setopt(slot->curl, CURLOPT_FILE, packfile);\n+\tcurl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite);\n+\tcurl_easy_setopt(slot->curl, CURLOPT_URL, url);\n+\tcurl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, data->no_pragma_header);\n+\tslot->local = packfile;\n+\n+\t/* If there is data present from a previous transfer attempt,\n+\t   resume where it left off */\n+\tprev_posn = ftell(packfile);\n+\tif (prev_posn>0) {\n+\t\tif (walker->get_verbosely)\n+\t\t\tfprintf(stderr,\n+\t\t\t\t\"Resuming fetch of pack %s at byte %ld\\n\",\n+\t\t\t\tsha1_to_hex(target->sha1), prev_posn);\n+\t\tsprintf(range, \"Range: bytes=%ld-\", prev_posn);\n+\t\trange_header = curl_slist_append(range_header, range);\n+\t\tcurl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, range_header);\n+\t}\n+\n+\tif (start_active_slot(slot)) {\n+\t\trun_active_slot(slot);\n+\t\tif (results.curl_result != CURLE_OK) {\n+\t\t\tfclose(packfile);\n+\t\t\treturn error(\"Unable to get pack file %s\\n%s\", url,\n+\t\t\t\t     curl_errorstr);\n+\t\t}\n+\t} else {\n+\t\tfclose(packfile);\n+\t\treturn error(\"Unable to start request\");\n+\t}\n+\n+\ttarget->pack_size = ftell(packfile);\n+\tfclose(packfile);\n+\n+\tret = move_temp_to_file(tmpfile, filename);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tlst = &repo->packs;\n+\twhile (*lst != target)\n+\t\tlst = &((*lst)->next);\n+\t*lst = (*lst)->next;\n+\n+\tif (verify_pack(target, 0))\n+\t\treturn -1;\n+\tinstall_packed_git(target);\n+\n+\treturn 0;\n+}\n+\n+static void abort_object_request(struct object_request *obj_req)\n+{\n+\tif (obj_req->local >= 0) {\n+\t\tclose(obj_req->local);\n+\t\tobj_req->local = -1;\n+\t}\n+\tunlink(obj_req->tmpfile);\n+\tif (obj_req->slot) {\n+\t\trelease_active_slot(obj_req->slot);\n+\t\tobj_req->slot = NULL;\n+\t}\n+\trelease_object_request(obj_req);\n+}\n+\n+static int fetch_object(struct walker *walker, struct alt_base *repo, unsigned char *sha1)\n+{\n+\tchar *hex = sha1_to_hex(sha1);\n+\tint ret = 0;\n+\tstruct object_request *obj_req = object_queue_head;\n+\n+\twhile (obj_req != NULL && hashcmp(obj_req->sha1, sha1))\n+\t\tobj_req = obj_req->next;\n+\tif (obj_req == NULL)\n+\t\treturn error(\"Couldn't find request for %s in the queue\", hex);\n+\n+\tif (has_sha1_file(obj_req->sha1)) {\n+\t\tabort_object_request(obj_req);\n+\t\treturn 0;\n+\t}\n+\n+#ifdef USE_CURL_MULTI\n+\twhile (obj_req->state == WAITING) {\n+\t\tstep_active_slots();\n+\t}\n+#else\n+\tstart_object_request(walker, obj_req);\n+#endif\n+\n+\twhile (obj_req->state == ACTIVE) {\n+\t\trun_active_slot(obj_req->slot);\n+\t}\n+\tif (obj_req->local != -1) {\n+\t\tclose(obj_req->local); obj_req->local = -1;\n+\t}\n+\n+\tif (obj_req->state == ABORTED) {\n+\t\tret = error(\"Request for %s aborted\", hex);\n+\t} else if (obj_req->curl_result != CURLE_OK &&\n+\t\t   obj_req->http_code != 416) {\n+\t\tif (missing_target(obj_req))\n+\t\t\tret = -1; /* Be silent, it is probably in a pack. */\n+\t\telse\n+\t\t\tret = error(\"%s (curl_result = %d, http_code = %ld, sha1 = %s)\",\n+\t\t\t\t    obj_req->errorstr, obj_req->curl_result,\n+\t\t\t\t    obj_req->http_code, hex);\n+\t} else if (obj_req->zret != Z_STREAM_END) {\n+\t\twalker->corrupt_object_found++;\n+\t\tret = error(\"File %s (%s) corrupt\", hex, obj_req->url);\n+\t} else if (hashcmp(obj_req->sha1, obj_req->real_sha1)) {\n+\t\tret = error(\"File %s has bad hash\", hex);\n+\t} else if (obj_req->rename < 0) {\n+\t\tret = error(\"unable to write sha1 filename %s\",\n+\t\t\t    obj_req->filename);\n+\t}\n+\n+\trelease_object_request(obj_req);\n+\treturn ret;\n+}\n+\n+static int fetch(struct walker *walker, unsigned char *sha1)\n+{\n+\tstruct walker_data *data = walker->data;\n+\tstruct alt_base *altbase = data->alt;\n+\n+\tif (!fetch_object(walker, altbase, sha1))\n+\t\treturn 0;\n+\twhile (altbase) {\n+\t\tif (!fetch_pack(walker, altbase, sha1))\n+\t\t\treturn 0;\n+\t\tfetch_alternates(walker, data->alt->base);\n+\t\taltbase = altbase->next;\n+\t}\n+\treturn error(\"Unable to find %s under %s\", sha1_to_hex(sha1),\n+\t\t     data->alt->base);\n+}\n+\n+static inline int needs_quote(int ch)\n+{\n+\tif (((ch >= 'A') && (ch <= 'Z'))\n+\t\t\t|| ((ch >= 'a') && (ch <= 'z'))\n+\t\t\t|| ((ch >= '0') && (ch <= '9'))\n+\t\t\t|| (ch == '/')\n+\t\t\t|| (ch == '-')\n+\t\t\t|| (ch == '.'))\n+\t\treturn 0;\n+\treturn 1;\n+}\n+\n+static inline int hex(int v)\n+{\n+\tif (v < 10) return '0' + v;\n+\telse return 'A' + v - 10;\n+}\n+\n+static char *quote_ref_url(const char *base, const char *ref)\n+{\n+\tconst char *cp;\n+\tchar *dp, *qref;\n+\tint len, baselen, ch;\n+\n+\tbaselen = strlen(base);\n+\tlen = baselen + 7; /* \"/refs/\" + NUL */\n+\tfor (cp = ref; (ch = *cp) != 0; cp++, len++)\n+\t\tif (needs_quote(ch))\n+\t\t\tlen += 2; /* extra two hex plus replacement % */\n+\tqref = xmalloc(len);\n+\tmemcpy(qref, base, baselen);\n+\tmemcpy(qref + baselen, \"/refs/\", 6);\n+\tfor (cp = ref, dp = qref + baselen + 6; (ch = *cp) != 0; cp++) {\n+\t\tif (needs_quote(ch)) {\n+\t\t\t*dp++ = '%';\n+\t\t\t*dp++ = hex((ch >> 4) & 0xF);\n+\t\t\t*dp++ = hex(ch & 0xF);\n+\t\t}\n+\t\telse\n+\t\t\t*dp++ = ch;\n+\t}\n+\t*dp = 0;\n+\n+\treturn qref;\n+}\n+\n+static int fetch_ref(struct walker *walker, char *ref, unsigned char *sha1)\n+{\n+        char *url;\n+        char hex[42];\n+        struct buffer buffer;\n+\tstruct walker_data *data = walker->data;\n+\tconst char *base = data->alt->base;\n+\tstruct active_request_slot *slot;\n+\tstruct slot_results results;\n+        buffer.size = 41;\n+        buffer.posn = 0;\n+        buffer.buffer = hex;\n+        hex[41] = '\\0';\n+\n+\turl = quote_ref_url(base, ref);\n+\tslot = get_active_slot();\n+\tslot->results = &results;\n+\tcurl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);\n+\tcurl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);\n+\tcurl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);\n+\tcurl_easy_setopt(slot->curl, CURLOPT_URL, url);\n+\tif (start_active_slot(slot)) {\n+\t\trun_active_slot(slot);\n+\t\tif (results.curl_result != CURLE_OK)\n+\t\t\treturn error(\"Couldn't get %s for %s\\n%s\",\n+\t\t\t\t     url, ref, curl_errorstr);\n+\t} else {\n+\t\treturn error(\"Unable to start request\");\n+\t}\n+\n+        hex[40] = '\\0';\n+        get_sha1_hex(hex, sha1);\n+        return 0;\n+}\n+\n+static void cleanup(struct walker *walker)\n+{\n+\tstruct walker_data *data = walker->data;\n+\thttp_cleanup();\n+\n+\tcurl_slist_free_all(data->no_pragma_header);\n+}\n+\n+struct walker *get_http_walker(const char *url)\n+{\n+\tchar *s;\n+\tstruct walker_data *data = xmalloc(sizeof(struct walker_data));\n+\tstruct walker *walker = xmalloc(sizeof(struct walker));\n+\n+\thttp_init();\n+\n+\tdata->no_pragma_header = curl_slist_append(NULL, \"Pragma:\");\n+\n+\tdata->alt = xmalloc(sizeof(*data->alt));\n+\tdata->alt->base = xmalloc(strlen(url) + 1);\n+\tstrcpy(data->alt->base, url);\n+\tfor (s = data->alt->base + strlen(data->alt->base) - 1; *s == '/'; --s)\n+\t\t*s = 0;\n+\n+\tdata->alt->got_indices = 0;\n+\tdata->alt->packs = NULL;\n+\tdata->alt->next = NULL;\n+\tdata->got_alternates = -1;\n+\n+\twalker->corrupt_object_found = 0;\n+\twalker->fetch = fetch;\n+\twalker->fetch_ref = fetch_ref;\n+\twalker->prefetch = prefetch;\n+\twalker->cleanup = cleanup;\n+\twalker->data = data;\n+\n+#ifdef USE_CURL_MULTI\n+\tadd_fill_function(walker, fill_active_slot);\n+#endif\n+\n+\treturn walker;\n+}\ndiff --git a/walker.c b/walker.c\nnew file mode 100644\nindex 0000000..5c65ea4\n--- /dev/null\n+++ b/walker.c\n@@ -0,0 +1,318 @@\n+#include \"cache.h\"\n+#include \"walker.h\"\n+#include \"commit.h\"\n+#include \"tree.h\"\n+#include \"tree-walk.h\"\n+#include \"tag.h\"\n+#include \"blob.h\"\n+#include \"refs.h\"\n+#include \"strbuf.h\"\n+\n+static unsigned char current_commit_sha1[20];\n+\n+void walker_say(struct walker *walker, const char *fmt, const char *hex)\n+{\n+\tif (walker->get_verbosely)\n+\t\tfprintf(stderr, fmt, hex);\n+}\n+\n+static void report_missing(const struct object *obj)\n+{\n+\tchar missing_hex[41];\n+\tstrcpy(missing_hex, sha1_to_hex(obj->sha1));;\n+\tfprintf(stderr, \"Cannot obtain needed %s %s\\n\",\n+\t\tobj->type ? typename(obj->type): \"object\", missing_hex);\n+\tif (!is_null_sha1(current_commit_sha1))\n+\t\tfprintf(stderr, \"while processing commit %s.\\n\",\n+\t\t\tsha1_to_hex(current_commit_sha1));\n+}\n+\n+static int process(struct walker *walker, struct object *obj);\n+\n+static int process_tree(struct walker *walker, struct tree *tree)\n+{\n+\tstruct tree_desc desc;\n+\tstruct name_entry entry;\n+\n+\tif (parse_tree(tree))\n+\t\treturn -1;\n+\n+\tinit_tree_desc(&desc, tree->buffer, tree->size);\n+\twhile (tree_entry(&desc, &entry)) {\n+\t\tstruct object *obj = NULL;\n+\n+\t\t/* submodule commits are not stored in the superproject */\n+\t\tif (S_ISGITLINK(entry.mode))\n+\t\t\tcontinue;\n+\t\tif (S_ISDIR(entry.mode)) {\n+\t\t\tstruct tree *tree = lookup_tree(entry.sha1);\n+\t\t\tif (tree)\n+\t\t\t\tobj = &tree->object;\n+\t\t}\n+\t\telse {\n+\t\t\tstruct blob *blob = lookup_blob(entry.sha1);\n+\t\t\tif (blob)\n+\t\t\t\tobj = &blob->object;\n+\t\t}\n+\t\tif (!obj || process(walker, obj))\n+\t\t\treturn -1;\n+\t}\n+\tfree(tree->buffer);\n+\ttree->buffer = NULL;\n+\ttree->size = 0;\n+\treturn 0;\n+}\n+\n+#define COMPLETE\t(1U << 0)\n+#define SEEN\t\t(1U << 1)\n+#define TO_SCAN\t\t(1U << 2)\n+\n+static struct commit_list *complete = NULL;\n+\n+static int process_commit(struct walker *walker, struct commit *commit)\n+{\n+\tif (parse_commit(commit))\n+\t\treturn -1;\n+\n+\twhile (complete && complete->item->date >= commit->date) {\n+\t\tpop_most_recent_commit(&complete, COMPLETE);\n+\t}\n+\n+\tif (commit->object.flags & COMPLETE)\n+\t\treturn 0;\n+\n+\thashcpy(current_commit_sha1, commit->object.sha1);\n+\n+\twalker_say(walker, \"walk %s\\n\", sha1_to_hex(commit->object.sha1));\n+\n+\tif (walker->get_tree) {\n+\t\tif (process(walker, &commit->tree->object))\n+\t\t\treturn -1;\n+\t\tif (!walker->get_all)\n+\t\t\twalker->get_tree = 0;\n+\t}\n+\tif (walker->get_history) {\n+\t\tstruct commit_list *parents = commit->parents;\n+\t\tfor (; parents; parents = parents->next) {\n+\t\t\tif (process(walker, &parents->item->object))\n+\t\t\t\treturn -1;\n+\t\t}\n+\t}\n+\treturn 0;\n+}\n+\n+static int process_tag(struct walker *walker, struct tag *tag)\n+{\n+\tif (parse_tag(tag))\n+\t\treturn -1;\n+\treturn process(walker, tag->tagged);\n+}\n+\n+static struct object_list *process_queue = NULL;\n+static struct object_list **process_queue_end = &process_queue;\n+\n+static int process_object(struct walker *walker, struct object *obj)\n+{\n+\tif (obj->type == OBJ_COMMIT) {\n+\t\tif (process_commit(walker, (struct commit *)obj))\n+\t\t\treturn -1;\n+\t\treturn 0;\n+\t}\n+\tif (obj->type == OBJ_TREE) {\n+\t\tif (process_tree(walker, (struct tree *)obj))\n+\t\t\treturn -1;\n+\t\treturn 0;\n+\t}\n+\tif (obj->type == OBJ_BLOB) {\n+\t\treturn 0;\n+\t}\n+\tif (obj->type == OBJ_TAG) {\n+\t\tif (process_tag(walker, (struct tag *)obj))\n+\t\t\treturn -1;\n+\t\treturn 0;\n+\t}\n+\treturn error(\"Unable to determine requirements \"\n+\t\t     \"of type %s for %s\",\n+\t\t     typename(obj->type), sha1_to_hex(obj->sha1));\n+}\n+\n+static int process(struct walker *walker, struct object *obj)\n+{\n+\tif (obj->flags & SEEN)\n+\t\treturn 0;\n+\tobj->flags |= SEEN;\n+\n+\tif (has_sha1_file(obj->sha1)) {\n+\t\t/* We already have it, so we should scan it now. */\n+\t\tobj->flags |= TO_SCAN;\n+\t}\n+\telse {\n+\t\tif (obj->flags & COMPLETE)\n+\t\t\treturn 0;\n+\t\twalker->prefetch(walker, obj->sha1);\n+\t}\n+\n+\tobject_list_insert(obj, process_queue_end);\n+\tprocess_queue_end = &(*process_queue_end)->next;\n+\treturn 0;\n+}\n+\n+static int loop(struct walker *walker)\n+{\n+\tstruct object_list *elem;\n+\n+\twhile (process_queue) {\n+\t\tstruct object *obj = process_queue->item;\n+\t\telem = process_queue;\n+\t\tprocess_queue = elem->next;\n+\t\tfree(elem);\n+\t\tif (!process_queue)\n+\t\t\tprocess_queue_end = &process_queue;\n+\n+\t\t/* If we are not scanning this object, we placed it in\n+\t\t * the queue because we needed to fetch it first.\n+\t\t */\n+\t\tif (! (obj->flags & TO_SCAN)) {\n+\t\t\tif (walker->fetch(walker, obj->sha1)) {\n+\t\t\t\treport_missing(obj);\n+\t\t\t\treturn -1;\n+\t\t\t}\n+\t\t}\n+\t\tif (!obj->type)\n+\t\t\tparse_object(obj->sha1);\n+\t\tif (process_object(walker, obj))\n+\t\t\treturn -1;\n+\t}\n+\treturn 0;\n+}\n+\n+static int interpret_target(struct walker *walker, char *target, unsigned char *sha1)\n+{\n+\tif (!get_sha1_hex(target, sha1))\n+\t\treturn 0;\n+\tif (!check_ref_format(target)) {\n+\t\tif (!walker->fetch_ref(walker, target, sha1)) {\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\treturn -1;\n+}\n+\n+static int mark_complete(const char *path, const unsigned char *sha1, int flag, void *cb_data)\n+{\n+\tstruct commit *commit = lookup_commit_reference_gently(sha1, 1);\n+\tif (commit) {\n+\t\tcommit->object.flags |= COMPLETE;\n+\t\tinsert_by_date(commit, &complete);\n+\t}\n+\treturn 0;\n+}\n+\n+int walker_targets_stdin(char ***target, const char ***write_ref)\n+{\n+\tint targets = 0, targets_alloc = 0;\n+\tstruct strbuf buf;\n+\t*target = NULL; *write_ref = NULL;\n+\tstrbuf_init(&buf);\n+\twhile (1) {\n+\t\tchar *rf_one = NULL;\n+\t\tchar *tg_one;\n+\n+\t\tread_line(&buf, stdin, '\\n');\n+\t\tif (buf.eof)\n+\t\t\tbreak;\n+\t\ttg_one = buf.buf;\n+\t\trf_one = strchr(tg_one, '\\t');\n+\t\tif (rf_one)\n+\t\t\t*rf_one++ = 0;\n+\n+\t\tif (targets >= targets_alloc) {\n+\t\t\ttargets_alloc = targets_alloc ? targets_alloc * 2 : 64;\n+\t\t\t*target = xrealloc(*target, targets_alloc * sizeof(**target));\n+\t\t\t*write_ref = xrealloc(*write_ref, targets_alloc * sizeof(**write_ref));\n+\t\t}\n+\t\t(*target)[targets] = xstrdup(tg_one);\n+\t\t(*write_ref)[targets] = rf_one ? xstrdup(rf_one) : NULL;\n+\t\ttargets++;\n+\t}\n+\treturn targets;\n+}\n+\n+void walker_targets_free(int targets, char **target, const char **write_ref)\n+{\n+\twhile (targets--) {\n+\t\tfree(target[targets]);\n+\t\tif (write_ref && write_ref[targets])\n+\t\t\tfree((char *) write_ref[targets]);\n+\t}\n+}\n+\n+int walker_fetch(struct walker *walker, int targets, char **target,\n+\t\t const char **write_ref, const char *write_ref_log_details)\n+{\n+\tstruct ref_lock **lock = xcalloc(targets, sizeof(struct ref_lock *));\n+\tunsigned char *sha1 = xmalloc(targets * 20);\n+\tchar *msg;\n+\tint ret;\n+\tint i;\n+\n+\tsave_commit_buffer = 0;\n+\ttrack_object_refs = 0;\n+\n+\tfor (i = 0; i < targets; i++) {\n+\t\tif (!write_ref || !write_ref[i])\n+\t\t\tcontinue;\n+\n+\t\tlock[i] = lock_ref_sha1(write_ref[i], NULL);\n+\t\tif (!lock[i]) {\n+\t\t\terror(\"Can't lock ref %s\", write_ref[i]);\n+\t\t\tgoto unlock_and_fail;\n+\t\t}\n+\t}\n+\n+\tif (!walker->get_recover)\n+\t\tfor_each_ref(mark_complete, NULL);\n+\n+\tfor (i = 0; i < targets; i++) {\n+\t\tif (interpret_target(walker, target[i], &sha1[20 * i])) {\n+\t\t\terror(\"Could not interpret %s as something to pull\", target[i]);\n+\t\t\tgoto unlock_and_fail;\n+\t\t}\n+\t\tif (process(walker, lookup_unknown_object(&sha1[20 * i])))\n+\t\t\tgoto unlock_and_fail;\n+\t}\n+\n+\tif (loop(walker))\n+\t\tgoto unlock_and_fail;\n+\n+\tif (write_ref_log_details) {\n+\t\tmsg = xmalloc(strlen(write_ref_log_details) + 12);\n+\t\tsprintf(msg, \"fetch from %s\", write_ref_log_details);\n+\t} else {\n+\t\tmsg = NULL;\n+\t}\n+\tfor (i = 0; i < targets; i++) {\n+\t\tif (!write_ref || !write_ref[i])\n+\t\t\tcontinue;\n+\t\tret = write_ref_sha1(lock[i], &sha1[20 * i], msg ? msg : \"fetch (unknown)\");\n+\t\tlock[i] = NULL;\n+\t\tif (ret)\n+\t\t\tgoto unlock_and_fail;\n+\t}\n+\tfree(msg);\n+\n+\treturn 0;\n+\n+unlock_and_fail:\n+\tfor (i = 0; i < targets; i++)\n+\t\tif (lock[i])\n+\t\t\tunlock_ref(lock[i]);\n+\n+\treturn -1;\n+}\n+\n+void walker_free(struct walker *walker)\n+{\n+\twalker->cleanup(walker);\n+\tfree(walker);\n+}\ndiff --git a/walker.h b/walker.h\nnew file mode 100644\nindex 0000000..ea2c363\n--- /dev/null\n+++ b/walker.h\n@@ -0,0 +1,37 @@\n+#ifndef WALKER_H\n+#define WALKER_H\n+\n+struct walker {\n+\tvoid *data;\n+\tint (*fetch_ref)(struct walker *, char *ref, unsigned char *sha1);\n+\tvoid (*prefetch)(struct walker *, unsigned char *sha1);\n+\tint (*fetch)(struct walker *, unsigned char *sha1);\n+\tvoid (*cleanup)(struct walker *);\n+\tint get_tree;\n+\tint get_history;\n+\tint get_all;\n+\tint get_verbosely;\n+\tint get_recover;\n+\n+\tint corrupt_object_found;\n+};\n+\n+/* Report what we got under get_verbosely */\n+void walker_say(struct walker *walker, const char *, const char *);\n+\n+/* Load pull targets from stdin */\n+int walker_targets_stdin(char ***target, const char ***write_ref);\n+\n+/* Free up loaded targets */\n+void walker_targets_free(int targets, char **target, const char **write_ref);\n+\n+/* If write_ref is set, the ref filename to write the target value to. */\n+/* If write_ref_log_details is set, additional text will appear in the ref log. */\n+int walker_fetch(struct walker *impl, int targets, char **target,\n+\t\t const char **write_ref, const char *write_ref_log_details);\n+\n+void walker_free(struct walker *walker);\n+\n+struct walker *get_http_walker(const char *url);\n+\n+#endif /* WALKER_H */\n-- \n1.5.3.rc1.818.g84b7\n"}]}