{"thread":{"id":"3596","subject":"[PATCH] Update http-push functionality","startedAt":"2006-03-08T00:13:20Z","lastAt":"2006-03-08T00:13:20Z","messageCount":1,"participants":["Nick Hengeveld"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"17316","messageId":"20060308001320.GA3997@reactrix.com","threadId":"3596","inReplyTo":null,"subject":"[PATCH] Update http-push functionality","fromName":"Nick Hengeveld","fromEmail":"nickh@reactrix.com","sentAt":"2006-03-08T00:13:20Z","receivedAt":"2006-03-08T00:13:20Z","isPatch":true,"sender":{"key":"nickh@reactrix.com","avatar":null},"body":"This brings http-push functionality more in line with the ssh/git version,\nby borrowing bits from send-pack and rev-list to process refspecs and\nrevision history in more standard ways.  Also, the status of remote objects\nis determined using PROPFIND requests for the object directory rather than\nHEAD requests for each object - while it may be less efficient for small\nnumbers of objects, this approach is able to get the status of all remote\nloose objects in a maximum of 256 requests.\n\n---\n\nI've tested this by running \"git push --all --tags remote\" into a newly\ninitialized repo, and by rewinding one or multiple overlapping heads and\npushing combinations of them.\n\nThis also lays a bit of groundwork for updating remote info/refs.\n\nGetting my head around the underlying object/refspec/revision stuff made\nit hurt, and I've got to say that you guys rule.\n\n Makefile    |    2 \n http-push.c | 1060 ++++++++++++++++++++++++++++++++++++++++-------------------\n 2 files changed, 727 insertions(+), 335 deletions(-)\n\n9d2903e7bf846eeefd3f108207d7c212139ff97c\ndiff --git a/Makefile b/Makefile\nindex b6d8804..1c555da 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -526,7 +526,7 @@ git-http-fetch$X: fetch.o http.o http-fe\n \t$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \\\n \t\t$(LIBS) $(CURL_LIBCURL)\n \n-git-http-push$X: http.o http-push.o $(LIB_FILE)\n+git-http-push$X: revision.o http.o http-push.o $(LIB_FILE)\n \t$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \\\n \t\t$(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)\n \ndiff --git a/http-push.c b/http-push.c\nindex fe92560..970cbfe 100644\n--- a/http-push.c\n+++ b/http-push.c\n@@ -5,6 +5,8 @@\n #include \"tag.h\"\n #include \"blob.h\"\n #include \"http.h\"\n+#include \"refs.h\"\n+#include \"revision.h\"\n \n #include <expat.h>\n \n@@ -42,14 +44,24 @@ enum XML_Status {\n #define DAV_ACTIVELOCK_OWNER \".prop.lockdiscovery.activelock.owner.href\"\n #define DAV_ACTIVELOCK_TIMEOUT \".prop.lockdiscovery.activelock.timeout\"\n #define DAV_ACTIVELOCK_TOKEN \".prop.lockdiscovery.activelock.locktoken.href\"\n+#define DAV_PROPFIND_RESP \".multistatus.response\"\n+#define DAV_PROPFIND_NAME \".multistatus.response.href\"\n+#define DAV_PROPFIND_COLLECTION \".multistatus.response.propstat.prop.resourcetype.collection\"\n \n /* DAV request body templates */\n-#define PROPFIND_REQUEST \"<?xml version=\\\"1.0\\\" encoding=\\\"utf-8\\\" ?>\\n<D:propfind xmlns:D=\\\"DAV:\\\">\\n<D:prop xmlns:R=\\\"%s\\\">\\n<D:supportedlock/>\\n</D:prop>\\n</D:propfind>\"\n+#define PROPFIND_SUPPORTEDLOCK_REQUEST \"<?xml version=\\\"1.0\\\" encoding=\\\"utf-8\\\" ?>\\n<D:propfind xmlns:D=\\\"DAV:\\\">\\n<D:prop xmlns:R=\\\"%s\\\">\\n<D:supportedlock/>\\n</D:prop>\\n</D:propfind>\"\n+#define PROPFIND_ALL_REQUEST \"<?xml version=\\\"1.0\\\" encoding=\\\"utf-8\\\" ?>\\n<D:propfind xmlns:D=\\\"DAV:\\\">\\n<D:allprop/>\\n</D:propfind>\"\n #define LOCK_REQUEST \"<?xml version=\\\"1.0\\\" encoding=\\\"utf-8\\\" ?>\\n<D:lockinfo xmlns:D=\\\"DAV:\\\">\\n<D:lockscope><D:exclusive/></D:lockscope>\\n<D:locktype><D:write/></D:locktype>\\n<D:owner>\\n<D:href>mailto:%s</D:href>\\n</D:owner>\\n</D:lockinfo>\"\n \n #define LOCK_TIME 600\n #define LOCK_REFRESH 30\n \n+/* bits #0-4 in revision.h */\n+\n+#define LOCAL   (1u << 5)\n+#define REMOTE  (1u << 6)\n+#define PUSHING (1u << 7)\n+\n static int pushing = 0;\n static int aborted = 0;\n static char remote_dir_exists[256];\n@@ -61,17 +73,19 @@ static int push_verbosely = 0;\n static int push_all = 0;\n static int force_all = 0;\n \n+static struct object_list *objects = NULL;\n+\n struct repo\n {\n \tchar *url;\n+\tint path_len;\n \tstruct packed_git *packs;\n };\n \n static struct repo *remote = NULL;\n+static struct remote_lock *remote_locks = NULL;\n \n enum transfer_state {\n-\tNEED_CHECK,\n-\tRUN_HEAD,\n \tNEED_PUSH,\n \tRUN_MKCOL,\n \tRUN_PUT,\n@@ -82,10 +96,10 @@ enum transfer_state {\n \n struct transfer_request\n {\n-\tunsigned char sha1[20];\n+\tstruct object *obj;\n \tchar *url;\n \tchar *dest;\n-\tstruct active_lock *lock;\n+\tstruct remote_lock *lock;\n \tstruct curl_slist *headers;\n \tstruct buffer buffer;\n \tchar filename[PATH_MAX];\n@@ -114,14 +128,23 @@ struct xml_ctx\n \tvoid *userData;\n };\n \n-struct active_lock\n+struct remote_lock\n {\n \tchar *url;\n \tchar *owner;\n \tchar *token;\n \ttime_t start_time;\n \tlong timeout;\n+\tint active;\n \tint refreshing;\n+\tstruct remote_lock *next;\n+};\n+\n+struct remote_dentry\n+{\n+\tchar *base;\n+\tchar *name;\n+\tint is_dir;\n };\n \n static void finish_request(struct transfer_request *request);\n@@ -134,42 +157,9 @@ static void process_response(void *callb\n \tfinish_request(request);\n }\n \n-static void start_check(struct transfer_request *request)\n-{\n-\tchar *hex = sha1_to_hex(request->sha1);\n-\tstruct active_request_slot *slot;\n-\tchar *posn;\n-\n-\trequest->url = xmalloc(strlen(remote->url) + 55);\n-\tstrcpy(request->url, remote->url);\n-\tposn = request->url + strlen(remote->url);\n-\tstrcpy(posn, \"objects/\");\n-\tposn += 8;\n-\tmemcpy(posn, hex, 2);\n-\tposn += 2;\n-\t*(posn++) = '/';\n-\tstrcpy(posn, hex + 2);\n-\n-\tslot = get_active_slot();\n-\tslot->callback_func = process_response;\n-\tslot->callback_data = request;\n-\tcurl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, request->errorstr);\n-\tcurl_easy_setopt(slot->curl, CURLOPT_URL, request->url);\n-\tcurl_easy_setopt(slot->curl, CURLOPT_NOBODY, 1);\n-\n-\tif (start_active_slot(slot)) {\n-\t\trequest->slot = slot;\n-\t\trequest->state = RUN_HEAD;\n-\t} else {\n-\t\trequest->state = ABORTED;\n-\t\tfree(request->url);\n-\t\trequest->url = NULL;\n-\t}\n-}\n-\n static void start_mkcol(struct transfer_request *request)\n {\n-\tchar *hex = sha1_to_hex(request->sha1);\n+\tchar *hex = sha1_to_hex(request->obj->sha1);\n \tstruct active_request_slot *slot;\n \tchar *posn;\n \n@@ -203,7 +193,7 @@ static void start_mkcol(struct transfer_\n \n static void start_put(struct transfer_request *request)\n {\n-\tchar *hex = sha1_to_hex(request->sha1);\n+\tchar *hex = sha1_to_hex(request->obj->sha1);\n \tstruct active_request_slot *slot;\n \tchar *posn;\n \tchar type[20];\n@@ -214,7 +204,7 @@ static void start_put(struct transfer_re\n \tssize_t size;\n \tz_stream stream;\n \n-\tunpacked = read_sha1_file(request->sha1, type, &len);\n+\tunpacked = read_sha1_file(request->obj->sha1, type, &len);\n \thdrlen = sprintf(hdr, \"%s %lu\", type, len) + 1;\n \n \t/* Set it up */\n@@ -309,64 +299,90 @@ static void start_move(struct transfer_r\n \t}\n }\n \n-static int refresh_lock(struct active_lock *lock)\n+static int refresh_lock(struct remote_lock *check_lock)\n {\n \tstruct active_request_slot *slot;\n \tchar *if_header;\n \tchar timeout_header[25];\n \tstruct curl_slist *dav_headers = NULL;\n-\tint rc = 0;\n+\tstruct remote_lock *lock;\n+\tint time_remaining;\n+\ttime_t current_time;\n \n-\tlock->refreshing = 1;\n+\t/* Refresh all active locks if they're close to expiring */\n+\tfor (lock = remote_locks; lock; lock = lock->next) {\n+\t\tif (!lock->active)\n+\t\t\tcontinue;\n \n-\tif_header = xmalloc(strlen(lock->token) + 25);\n-\tsprintf(if_header, \"If: (<opaquelocktoken:%s>)\", lock->token);\n-\tsprintf(timeout_header, \"Timeout: Second-%ld\", lock->timeout);\n-\tdav_headers = curl_slist_append(dav_headers, if_header);\n-\tdav_headers = curl_slist_append(dav_headers, timeout_header);\n+\t\tcurrent_time = time(NULL);\n+\t\ttime_remaining = lock->start_time + lock->timeout\n+\t\t\t- current_time;\n+\t\tif (time_remaining > LOCK_REFRESH)\n+\t\t\tcontinue;\n \n-\tslot = get_active_slot();\n-\tcurl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);\n-\tcurl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);\n-\tcurl_easy_setopt(slot->curl, CURLOPT_URL, lock->url);\n-\tcurl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_LOCK);\n-\tcurl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);\n+\t\tlock->refreshing = 1;\n \n-\tif (start_active_slot(slot)) {\n-\t\trun_active_slot(slot);\n-\t\tif (slot->curl_result != CURLE_OK) {\n-\t\t\tfprintf(stderr, \"Got HTTP error %ld\\n\", slot->http_code);\n-\t\t} else {\n-\t\t\tlock->start_time = time(NULL);\n-\t\t\trc = 1;\n+\t\tif_header = xmalloc(strlen(lock->token) + 25);\n+\t\tsprintf(if_header, \"If: (<opaquelocktoken:%s>)\", lock->token);\n+\t\tsprintf(timeout_header, \"Timeout: Second-%ld\", lock->timeout);\n+\t\tdav_headers = curl_slist_append(dav_headers, if_header);\n+\t\tdav_headers = curl_slist_append(dav_headers, timeout_header);\n+\n+\t\tslot = get_active_slot();\n+\t\tcurl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);\n+\t\tcurl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);\n+\t\tcurl_easy_setopt(slot->curl, CURLOPT_URL, lock->url);\n+\t\tcurl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_LOCK);\n+\t\tcurl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);\n+\n+\t\tif (start_active_slot(slot)) {\n+\t\t\trun_active_slot(slot);\n+\t\t\tif (slot->curl_result != CURLE_OK) {\n+\t\t\t\tfprintf(stderr, \"Got HTTP error %ld\\n\", slot->http_code);\n+\t\t\t\tlock->active = 0;\n+\t\t\t} else {\n+\t\t\t\tlock->active = 1;\n+\t\t\t\tlock->start_time = time(NULL);\n+\t\t\t}\n \t\t}\n+\n+\t\tlock->refreshing = 0;\n+\t\tcurl_slist_free_all(dav_headers);\n+\t\tfree(if_header);\n \t}\n \n-\tlock->refreshing = 0;\n-\tcurl_slist_free_all(dav_headers);\n-\tfree(if_header);\n+\tif (check_lock)\n+\t\treturn check_lock->active;\n+\telse\n+\t\treturn 0;\n+}\n \n-\treturn rc;\n+static void release_request(struct transfer_request *request)\n+{\n+\tstruct transfer_request *entry = request_queue_head;\n+\n+\tif (request == request_queue_head) {\n+\t\trequest_queue_head = request->next;\n+\t} else {\n+\t\twhile (entry->next != NULL && entry->next != request)\n+\t\t\tentry = entry->next;\n+\t\tif (entry->next == request)\n+\t\t\tentry->next = entry->next->next;\n+\t}\n+\n+\tif (request->url != NULL)\n+\t\tfree(request->url);\n+\tfree(request);\n }\n \n static void finish_request(struct transfer_request *request)\n {\n-\ttime_t current_time = time(NULL);\n-\tint time_remaining;\n-\n \trequest->curl_result =\trequest->slot->curl_result;\n \trequest->http_code = request->slot->http_code;\n \trequest->slot = NULL;\n \n-\t/* Refresh the lock if it is close to timing out */\n-\ttime_remaining = request->lock->start_time + request->lock->timeout\n-\t\t- current_time;\n-\tif (time_remaining < LOCK_REFRESH && !request->lock->refreshing) {\n-\t\tif (!refresh_lock(request->lock)) {\n-\t\t\tfprintf(stderr, \"Unable to refresh remote lock\\n\");\n-\t\t\taborted = 1;\n-\t\t}\n-\t}\n+\t/* Keep locks active */\n+\trefresh_lock(request->lock);\n \n \tif (request->headers != NULL)\n \t\tcurl_slist_free_all(request->headers);\n@@ -377,27 +393,14 @@ static void finish_request(struct transf\n \t\trequest->url = NULL;\n \t}\t\t\n \n-\tif (request->state == RUN_HEAD) {\n-\t\tif (request->http_code == 404) {\n-\t\t\trequest->state = NEED_PUSH;\n-\t\t} else if (request->curl_result == CURLE_OK) {\n-\t\t\tremote_dir_exists[request->sha1[0]] = 1;\n-\t\t\trequest->state = COMPLETE;\n-\t\t} else {\n-\t\t\tfprintf(stderr, \"HEAD %s failed, aborting (%d/%ld)\\n\",\n-\t\t\t\tsha1_to_hex(request->sha1),\n-\t\t\t\trequest->curl_result, request->http_code);\n-\t\t\trequest->state = ABORTED;\n-\t\t\taborted = 1;\n-\t\t}\n-\t} else if (request->state == RUN_MKCOL) {\n+\tif (request->state == RUN_MKCOL) {\n \t\tif (request->curl_result == CURLE_OK ||\n \t\t    request->http_code == 405) {\n-\t\t\tremote_dir_exists[request->sha1[0]] = 1;\n+\t\t\tremote_dir_exists[request->obj->sha1[0]] = 1;\n \t\t\tstart_put(request);\n \t\t} else {\n \t\t\tfprintf(stderr, \"MKCOL %s failed, aborting (%d/%ld)\\n\",\n-\t\t\t\tsha1_to_hex(request->sha1),\n+\t\t\t\tsha1_to_hex(request->obj->sha1),\n \t\t\t\trequest->curl_result, request->http_code);\n \t\t\trequest->state = ABORTED;\n \t\t\taborted = 1;\n@@ -407,21 +410,21 @@ static void finish_request(struct transf\n \t\t\tstart_move(request);\n \t\t} else {\n \t\t\tfprintf(stderr,\t\"PUT %s failed, aborting (%d/%ld)\\n\",\n-\t\t\t\tsha1_to_hex(request->sha1),\n+\t\t\t\tsha1_to_hex(request->obj->sha1),\n \t\t\t\trequest->curl_result, request->http_code);\n \t\t\trequest->state = ABORTED;\n \t\t\taborted = 1;\n \t\t}\n \t} else if (request->state == RUN_MOVE) {\n \t\tif (request->curl_result == CURLE_OK) {\n-\t\t\tif (push_verbosely)\n-\t\t\t\tfprintf(stderr,\n-\t\t\t\t\t\"sent %s\\n\",\n-\t\t\t\t\tsha1_to_hex(request->sha1));\n+\t\t\tfprintf(stderr, \"    sent %s\\n\",\n+\t\t\t\tsha1_to_hex(request->obj->sha1));\n \t\t\trequest->state = COMPLETE;\n+\t\t\trequest->obj->flags |= REMOTE;\n+\t\t\trelease_request(request);\n \t\t} else {\n \t\t\tfprintf(stderr, \"MOVE %s failed, aborting (%d/%ld)\\n\",\n-\t\t\t\tsha1_to_hex(request->sha1),\n+\t\t\t\tsha1_to_hex(request->obj->sha1),\n \t\t\t\trequest->curl_result, request->http_code);\n \t\t\trequest->state = ABORTED;\n \t\t\taborted = 1;\n@@ -429,24 +432,6 @@ static void finish_request(struct transf\n \t}\n }\n \n-static void release_request(struct transfer_request *request)\n-{\n-\tstruct transfer_request *entry = request_queue_head;\n-\n-\tif (request == request_queue_head) {\n-\t\trequest_queue_head = request->next;\n-\t} else {\n-\t\twhile (entry->next != NULL && entry->next != request)\n-\t\t\tentry = entry->next;\n-\t\tif (entry->next == request)\n-\t\t\tentry->next = entry->next->next;\n-\t}\n-\n-\tif (request->url != NULL)\n-\t\tfree(request->url);\n-\tfree(request);\n-}\n-\n void fill_active_slots(void)\n {\n \tstruct transfer_request *request = request_queue_head;\n@@ -457,14 +442,12 @@ void fill_active_slots(void)\n \t\treturn;\n \n \twhile (active_requests < max_requests && request != NULL) {\n-\t\tif (!pushing && request->state == NEED_CHECK) {\n-\t\t\tstart_check(request);\n-\t\t\tcurl_multi_perform(curlm, &num_transfers);\n-\t\t} else if (pushing && request->state == NEED_PUSH) {\n-\t\t\tif (remote_dir_exists[request->sha1[0]])\n+\t\tif (pushing && request->state == NEED_PUSH) {\n+\t\t\tif (remote_dir_exists[request->obj->sha1[0]] == 1) {\n \t\t\t\tstart_put(request);\n-\t\t\telse\n+\t\t\t} else {\n \t\t\t\tstart_mkcol(request);\n+\t\t\t}\n \t\t\tcurl_multi_perform(curlm, &num_transfers);\n \t\t}\n \t\trequest = request->next;\n@@ -479,26 +462,34 @@ void fill_active_slots(void)\n \t}\t\t\t\t\n }\n \n-static void add_request(unsigned char *sha1, struct active_lock *lock)\n+static void get_remote_object_list(unsigned char parent);\n+\n+static void add_request(struct object *obj, struct remote_lock *lock)\n {\n \tstruct transfer_request *request = request_queue_head;\n \tstruct packed_git *target;\n-\t\n-\twhile (request != NULL && memcmp(request->sha1, sha1, 20))\n-\t\trequest = request->next;\n-\tif (request != NULL)\n-\t\treturn;\n \n-\ttarget = find_sha1_pack(sha1, remote->packs);\n-\tif (target)\n+\t/*\n+\t * Don't push the object if it's known to exist on the remote\n+\t * or is already in the request queue\n+\t */\n+\tif (remote_dir_exists[obj->sha1[0]] == -1)\n+\t\tget_remote_object_list(obj->sha1[0]);\n+\tif (obj->flags & (REMOTE | PUSHING))\n \t\treturn;\n+\ttarget = find_sha1_pack(obj->sha1, remote->packs);\n+\tif (target) {\n+\t\tobj->flags |= REMOTE;\n+\t\treturn;\n+\t}\n \n+\tobj->flags |= PUSHING;\n \trequest = xmalloc(sizeof(*request));\n-\tmemcpy(request->sha1, sha1, 20);\n+\trequest->obj = obj;\n \trequest->url = NULL;\n \trequest->lock = lock;\n \trequest->headers = NULL;\n-\trequest->state = NEED_CHECK;\n+\trequest->state = NEED_PUSH;\n \trequest->next = request_queue_head;\n \trequest_queue_head = request;\n \n@@ -698,14 +689,13 @@ static char *quote_ref_url(const char *b\n \tint len, baselen, ch;\n \n \tbaselen = strlen(base);\n-\tlen = baselen + 12; /* \"refs/heads/\" + NUL */\n+\tlen = baselen + 1;\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/heads/\", 11);\n-\tfor (cp = ref, dp = qref + baselen + 11; (ch = *cp) != 0; cp++) {\n+\tfor (cp = ref, dp = qref + baselen; (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@@ -751,6 +741,27 @@ int fetch_ref(char *ref, unsigned char *\n         return 0;\n }\n \n+static void one_remote_object(const char *hex)\n+{\n+\tunsigned char sha1[20];\n+\tstruct object *obj;\n+\n+\tif (get_sha1_hex(hex, sha1) != 0)\n+\t\treturn;\n+\n+\tobj = lookup_object(sha1);\n+\tif (!obj)\n+\t\tobj = parse_object(sha1);\n+\n+\t/* Ignore remote objects that don't exist locally */\n+\tif (!obj)\n+\t\treturn;\n+\n+\tobj->flags |= REMOTE;\n+\tif (!object_list_contains(objects, obj))\n+\t\tadd_object(obj, &objects, NULL, \"\");\n+}\n+\n static void handle_lockprop_ctx(struct xml_ctx *ctx, int tag_closed)\n {\n \tint *lock_flags = (int *)ctx->userData;\n@@ -772,7 +783,7 @@ static void handle_lockprop_ctx(struct x\n \n static void handle_new_lock_ctx(struct xml_ctx *ctx, int tag_closed)\n {\n-\tstruct active_lock *lock = (struct active_lock *)ctx->userData;\n+\tstruct remote_lock *lock = (struct remote_lock *)ctx->userData;\n \n \tif (tag_closed && ctx->cdata) {\n \t\tif (!strcmp(ctx->name, DAV_ACTIVELOCK_OWNER)) {\n@@ -791,6 +802,57 @@ static void handle_new_lock_ctx(struct x\n \t}\n }\n \n+static void one_remote_ref(char *refname);\n+static void crawl_remote_refs(char *path);\n+\n+static void handle_crawl_ref_ctx(struct xml_ctx *ctx, int tag_closed)\n+{\n+\tstruct remote_dentry *dentry = (struct remote_dentry *)ctx->userData;\n+\n+\n+\tif (tag_closed) {\n+\t\tif (!strcmp(ctx->name, DAV_PROPFIND_RESP) && dentry->name) {\n+\t\t\tif (dentry->is_dir) {\n+\t\t\t\tif (strcmp(dentry->name, dentry->base)) {\n+\t\t\t\t\tcrawl_remote_refs(dentry->name);\n+\t\t\t\t}\n+\t\t\t} else {\n+\t\t\t\tone_remote_ref(dentry->name);\n+\t\t\t}\n+\t\t} else if (!strcmp(ctx->name, DAV_PROPFIND_NAME) && ctx->cdata) {\n+\t\t\tdentry->name = xmalloc(strlen(ctx->cdata) -\n+\t\t\t\t\t       remote->path_len + 1);\n+\t\t\tstrcpy(dentry->name,\n+\t\t\t       ctx->cdata + remote->path_len);\n+\t\t} else if (!strcmp(ctx->name, DAV_PROPFIND_COLLECTION)) {\n+\t\t\tdentry->is_dir = 1;\n+\t\t}\n+\t} else if (!strcmp(ctx->name, DAV_PROPFIND_RESP)) {\n+\t\tdentry->name = NULL;\n+\t\tdentry->is_dir = 0;\n+\t}\n+}\n+\n+static void handle_remote_object_list_ctx(struct xml_ctx *ctx, int tag_closed)\n+{\n+\tchar *path;\n+\tchar *obj_hex;\n+\n+\tif (tag_closed) {\n+\t\tif (!strcmp(ctx->name, DAV_PROPFIND_NAME) && ctx->cdata) {\n+\t\t\tpath = ctx->cdata + remote->path_len;\n+\t\t\tif (strlen(path) != 50)\n+\t\t\t\treturn;\n+\t\t\tpath += 9;\n+\t\t\tobj_hex = xmalloc(strlen(path));\n+\t\t\tstrncpy(obj_hex, path, 2);\n+\t\t\tstrcpy(obj_hex + 2, path + 3);\n+\t\t\tone_remote_object(obj_hex);\n+\t\t\tfree(obj_hex);\n+\t\t}\n+\t}\n+}\n+\n static void\n xml_start_tag(void *userData, const char *name, const char **atts)\n {\n@@ -848,7 +910,7 @@ xml_cdata(void *userData, const XML_Char\n \tstrncpy(ctx->cdata, s, len);\n }\n \n-static struct active_lock *lock_remote(char *file, long timeout)\n+static struct remote_lock *lock_remote(char *path, long timeout)\n {\n \tstruct active_request_slot *slot;\n \tstruct buffer out_buffer;\n@@ -858,14 +920,26 @@ static struct active_lock *lock_remote(c\n \tchar *url;\n \tchar *ep;\n \tchar timeout_header[25];\n-\tstruct active_lock *new_lock = NULL;\n+\tstruct remote_lock *lock = remote_locks;\n \tXML_Parser parser = XML_ParserCreate(NULL);\n \tenum XML_Status result;\n \tstruct curl_slist *dav_headers = NULL;\n \tstruct xml_ctx ctx;\n \n-\turl = xmalloc(strlen(remote->url) + strlen(file) + 1);\n-\tsprintf(url, \"%s%s\", remote->url, file);\n+\turl = xmalloc(strlen(remote->url) + strlen(path) + 1);\n+\tsprintf(url, \"%s%s\", remote->url, path);\n+\n+\t/* Make sure the url is not already locked */\n+\twhile (lock && strcmp(lock->url, url)) {\n+\t\tlock = lock->next;\n+\t}\n+\tif (lock) {\n+\t\tfree(url);\n+\t\tif (refresh_lock(lock))\n+\t\t\treturn lock;\n+\t\telse\n+\t\t\treturn NULL;\n+\t}\n \n \t/* Make sure leading directories exist for the remote ref */\n \tep = strchr(url + strlen(remote->url) + 11, '/');\n@@ -921,11 +995,11 @@ static struct active_lock *lock_remote(c\n \tcurl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_LOCK);\n \tcurl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);\n \n-\tnew_lock = xcalloc(1, sizeof(*new_lock));\n-\tnew_lock->owner = NULL;\n-\tnew_lock->token = NULL;\n-\tnew_lock->timeout = -1;\n-\tnew_lock->refreshing = 0;\n+\tlock = xcalloc(1, sizeof(*lock));\n+\tlock->owner = NULL;\n+\tlock->token = NULL;\n+\tlock->timeout = -1;\n+\tlock->refreshing = 0;\n \n \tif (start_active_slot(slot)) {\n \t\trun_active_slot(slot);\n@@ -934,7 +1008,7 @@ static struct active_lock *lock_remote(c\n \t\t\tctx.len = 0;\n \t\t\tctx.cdata = NULL;\n \t\t\tctx.userFunc = handle_new_lock_ctx;\n-\t\t\tctx.userData = new_lock;\n+\t\t\tctx.userData = lock;\n \t\t\tXML_SetUserData(parser, &ctx);\n \t\t\tXML_SetElementHandler(parser, xml_start_tag,\n \t\t\t\t\t      xml_end_tag);\n@@ -946,7 +1020,7 @@ static struct active_lock *lock_remote(c\n \t\t\t\tfprintf(stderr, \"XML error: %s\\n\",\n \t\t\t\t\tXML_ErrorString(\n \t\t\t\t\t\tXML_GetErrorCode(parser)));\n-\t\t\t\tnew_lock->timeout = -1;\n+\t\t\t\tlock->timeout = -1;\n \t\t\t}\n \t\t}\n \t} else {\n@@ -957,23 +1031,26 @@ static struct active_lock *lock_remote(c\n \tfree(out_data);\n \tfree(in_data);\n \n-\tif (new_lock->token == NULL || new_lock->timeout <= 0) {\n-\t\tif (new_lock->token != NULL)\n-\t\t\tfree(new_lock->token);\n-\t\tif (new_lock->owner != NULL)\n-\t\t\tfree(new_lock->owner);\n+\tif (lock->token == NULL || lock->timeout <= 0) {\n+\t\tif (lock->token != NULL)\n+\t\t\tfree(lock->token);\n+\t\tif (lock->owner != NULL)\n+\t\t\tfree(lock->owner);\n \t\tfree(url);\n-\t\tfree(new_lock);\n-\t\tnew_lock = NULL;\n+\t\tfree(lock);\n+\t\tlock = NULL;\n \t} else {\n-\t\tnew_lock->url = url;\n-\t\tnew_lock->start_time = time(NULL);\n+\t\tlock->url = url;\n+\t\tlock->active = 1;\n+\t\tlock->start_time = time(NULL);\n+\t\tlock->next = remote_locks;\n+\t\tremote_locks = lock;\n \t}\n \n-\treturn new_lock;\n+\treturn lock;\n }\n \n-static int unlock_remote(struct active_lock *lock)\n+static int unlock_remote(struct remote_lock *lock)\n {\n \tstruct active_request_slot *slot;\n \tchar *lock_token_header;\n@@ -1005,15 +1082,171 @@ static int unlock_remote(struct active_l\n \tcurl_slist_free_all(dav_headers);\n \tfree(lock_token_header);\n \n-\tif (lock->owner != NULL)\n-\t\tfree(lock->owner);\n-\tfree(lock->url);\n-\tfree(lock->token);\n-\tfree(lock);\n+\tlock->active = 0;\n \n \treturn rc;\n }\n \n+static void crawl_remote_refs(char *path)\n+{\n+\tchar *url;\n+\tstruct active_request_slot *slot;\n+\tstruct buffer in_buffer;\n+\tstruct buffer out_buffer;\n+\tchar *in_data;\n+\tchar *out_data;\n+\tXML_Parser parser = XML_ParserCreate(NULL);\n+\tenum XML_Status result;\n+\tstruct curl_slist *dav_headers = NULL;\n+\tstruct xml_ctx ctx;\n+\tstruct remote_dentry dentry;\n+\n+\tfprintf(stderr, \"  %s\\n\", path);\n+\n+\tdentry.base = path;\n+\tdentry.name = NULL;\n+\tdentry.is_dir = 0;\n+\n+\turl = xmalloc(strlen(remote->url) + strlen(path) + 1);\n+\tsprintf(url, \"%s%s\", remote->url, path);\n+\n+\tout_buffer.size = strlen(PROPFIND_ALL_REQUEST);\n+\tout_data = xmalloc(out_buffer.size + 1);\n+\tsnprintf(out_data, out_buffer.size + 1, PROPFIND_ALL_REQUEST);\n+\tout_buffer.posn = 0;\n+\tout_buffer.buffer = out_data;\n+\n+\tin_buffer.size = 4096;\n+\tin_data = xmalloc(in_buffer.size);\n+\tin_buffer.posn = 0;\n+\tin_buffer.buffer = in_data;\n+\n+\tdav_headers = curl_slist_append(dav_headers, \"Depth: 1\");\n+\tdav_headers = curl_slist_append(dav_headers, \"Content-Type: text/xml\");\n+\t\n+\tslot = get_active_slot();\n+\tcurl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer);\n+\tcurl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.size);\n+\tcurl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);\n+\tcurl_easy_setopt(slot->curl, CURLOPT_FILE, &in_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_UPLOAD, 1);\n+\tcurl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PROPFIND);\n+\tcurl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);\n+\n+\tif (start_active_slot(slot)) {\n+\t\trun_active_slot(slot);\n+\t\tif (slot->curl_result == CURLE_OK) {\n+\t\t\tctx.name = xcalloc(10, 1);\n+\t\t\tctx.len = 0;\n+\t\t\tctx.cdata = NULL;\n+\t\t\tctx.userFunc = handle_crawl_ref_ctx;\n+\t\t\tctx.userData = &dentry;\n+\t\t\tXML_SetUserData(parser, &ctx);\n+\t\t\tXML_SetElementHandler(parser, xml_start_tag,\n+\t\t\t\t\t      xml_end_tag);\n+\t\t\tXML_SetCharacterDataHandler(parser, xml_cdata);\n+\t\t\tresult = XML_Parse(parser, in_buffer.buffer,\n+\t\t\t\t\t   in_buffer.posn, 1);\n+\t\t\tfree(ctx.name);\n+\n+\t\t\tif (result != XML_STATUS_OK) {\n+\t\t\t\tfprintf(stderr, \"XML error: %s\\n\",\n+\t\t\t\t\tXML_ErrorString(\n+\t\t\t\t\t\tXML_GetErrorCode(parser)));\n+\t\t\t}\n+\t\t}\n+\t} else {\n+\t\tfprintf(stderr, \"Unable to start request\\n\");\n+\t}\n+\n+\tfree(url);\n+\tfree(out_data);\n+\tfree(in_buffer.buffer);\n+\tcurl_slist_free_all(dav_headers);\n+}\n+\n+static void get_remote_object_list(unsigned char parent)\n+{\n+\tchar *url;\n+\tstruct active_request_slot *slot;\n+\tstruct buffer in_buffer;\n+\tstruct buffer out_buffer;\n+\tchar *in_data;\n+\tchar *out_data;\n+\tXML_Parser parser = XML_ParserCreate(NULL);\n+\tenum XML_Status result;\n+\tstruct curl_slist *dav_headers = NULL;\n+\tstruct xml_ctx ctx;\n+\tchar path[] = \"/objects/XX/\";\n+\tstatic const char hex[] = \"0123456789abcdef\";\n+\tunsigned int val = parent;\n+\n+\tpath[9] = hex[val >> 4];\n+\tpath[10] = hex[val & 0xf];\n+\turl = xmalloc(strlen(remote->url) + strlen(path) + 1);\n+\tsprintf(url, \"%s%s\", remote->url, path);\n+\n+\tout_buffer.size = strlen(PROPFIND_ALL_REQUEST);\n+\tout_data = xmalloc(out_buffer.size + 1);\n+\tsnprintf(out_data, out_buffer.size + 1, PROPFIND_ALL_REQUEST);\n+\tout_buffer.posn = 0;\n+\tout_buffer.buffer = out_data;\n+\n+\tin_buffer.size = 4096;\n+\tin_data = xmalloc(in_buffer.size);\n+\tin_buffer.posn = 0;\n+\tin_buffer.buffer = in_data;\n+\n+\tdav_headers = curl_slist_append(dav_headers, \"Depth: 1\");\n+\tdav_headers = curl_slist_append(dav_headers, \"Content-Type: text/xml\");\n+\t\n+\tslot = get_active_slot();\n+\tcurl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer);\n+\tcurl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.size);\n+\tcurl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);\n+\tcurl_easy_setopt(slot->curl, CURLOPT_FILE, &in_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_UPLOAD, 1);\n+\tcurl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PROPFIND);\n+\tcurl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);\n+\n+\tif (start_active_slot(slot)) {\n+\t\trun_active_slot(slot);\n+\t\tif (slot->curl_result == CURLE_OK) {\n+\t\t\tremote_dir_exists[parent] = 1;\n+\t\t\tctx.name = xcalloc(10, 1);\n+\t\t\tctx.len = 0;\n+\t\t\tctx.cdata = NULL;\n+\t\t\tctx.userFunc = handle_remote_object_list_ctx;\n+\t\t\tXML_SetUserData(parser, &ctx);\n+\t\t\tXML_SetElementHandler(parser, xml_start_tag,\n+\t\t\t\t\t      xml_end_tag);\n+\t\t\tXML_SetCharacterDataHandler(parser, xml_cdata);\n+\t\t\tresult = XML_Parse(parser, in_buffer.buffer,\n+\t\t\t\t\t   in_buffer.posn, 1);\n+\t\t\tfree(ctx.name);\n+\n+\t\t\tif (result != XML_STATUS_OK) {\n+\t\t\t\tfprintf(stderr, \"XML error: %s\\n\",\n+\t\t\t\t\tXML_ErrorString(\n+\t\t\t\t\t\tXML_GetErrorCode(parser)));\n+\t\t\t}\n+\t\t} else {\n+\t\t\tremote_dir_exists[parent] = 0;\n+\t\t}\n+\t} else {\n+\t\tfprintf(stderr, \"Unable to start request\\n\");\n+\t}\n+\n+\tfree(url);\n+\tfree(out_data);\n+\tfree(in_buffer.buffer);\n+\tcurl_slist_free_all(dav_headers);\t\n+}\n+\n static int locking_available(void)\n {\n \tstruct active_request_slot *slot;\n@@ -1027,9 +1260,12 @@ static int locking_available(void)\n \tstruct xml_ctx ctx;\n \tint lock_flags = 0;\n \n-\tout_buffer.size = strlen(PROPFIND_REQUEST) + strlen(remote->url) - 2;\n+\tout_buffer.size =\n+\t\tstrlen(PROPFIND_SUPPORTEDLOCK_REQUEST) + \n+\t\tstrlen(remote->url) - 2;\n \tout_data = xmalloc(out_buffer.size + 1);\n-\tsnprintf(out_data, out_buffer.size + 1, PROPFIND_REQUEST, remote->url);\n+\tsnprintf(out_data, out_buffer.size + 1,\n+\t\t PROPFIND_SUPPORTEDLOCK_REQUEST, remote->url);\n \tout_buffer.posn = 0;\n \tout_buffer.buffer = out_data;\n \n@@ -1085,85 +1321,99 @@ static int locking_available(void)\n \treturn lock_flags;\n }\n \n-static int is_ancestor(unsigned char *sha1, struct commit *commit)\n+static struct object_list **process_blob(struct blob *blob,\n+\t\t\t\t\t struct object_list **p,\n+\t\t\t\t\t struct name_path *path,\n+\t\t\t\t\t const char *name)\n {\n-\tstruct commit_list *parents;\n+\tstruct object *obj = &blob->object;\n \n-\tif (parse_commit(commit))\n-\t\treturn 0;\n-\tparents = commit->parents;\n-\tfor (; parents; parents = parents->next) {\n-\t\tif (!memcmp(sha1, parents->item->object.sha1, 20)) {\n-\t\t\treturn 1;\n-\t\t} else if (parents->item->object.type == commit_type) {\n-\t\t\tif (is_ancestor(\n-\t\t\t\t    sha1,\n-\t\t\t\t    (struct commit *)&parents->item->object\n-\t\t\t\t    ))\n-\t\t\t\treturn 1;\n-\t\t}\n+\tobj->flags |= LOCAL;\n+\n+\tif (obj->flags & (UNINTERESTING | SEEN))\n+\t\treturn p;\n+\n+\tobj->flags |= SEEN;\n+\treturn add_object(obj, p, path, name);\n+}\n+\n+static struct object_list **process_tree(struct tree *tree,\n+\t\t\t\t\t struct object_list **p,\n+\t\t\t\t\t struct name_path *path,\n+\t\t\t\t\t const char *name)\n+{\n+\tstruct object *obj = &tree->object;\n+\tstruct tree_entry_list *entry;\n+\tstruct name_path me;\n+\n+\tobj->flags |= LOCAL;\n+\n+\tif (obj->flags & (UNINTERESTING | SEEN))\n+\t\treturn p;\n+\tif (parse_tree(tree) < 0)\n+\t\tdie(\"bad tree object %s\", sha1_to_hex(obj->sha1));\n+\n+\tobj->flags |= SEEN;\n+\tp = add_object(obj, p, NULL, name);\n+\tme.up = path;\n+\tme.elem = name;\n+\tme.elem_len = strlen(name);\n+\tentry = tree->entries;\n+\ttree->entries = NULL;\n+\twhile (entry) {\n+\t\tstruct tree_entry_list *next = entry->next;\n+\t\tif (entry->directory)\n+\t\t\tp = process_tree(entry->item.tree, p, &me, entry->name);\n+\t\telse\n+\t\t\tp = process_blob(entry->item.blob, p, &me, entry->name);\n+\t\tfree(entry);\n+\t\tentry = next;\n \t}\n-\treturn 0;\n+\treturn p;\n }\n \n-static void get_delta(unsigned char *sha1, struct object *obj,\n-\t\t      struct active_lock *lock)\n+static void get_delta(struct rev_info *revs, struct remote_lock *lock)\n {\n \tstruct commit *commit;\n-\tstruct commit_list *parents;\n-\tstruct tree *tree;\n-\tstruct tree_entry_list *entry;\n+\tstruct object_list **p = &objects, *pending;\n \n-\tif (sha1 && !memcmp(sha1, obj->sha1, 20))\n-\t\treturn;\n+\twhile ((commit = get_revision(revs)) != NULL) {\n+\t\tp = process_tree(commit->tree, p, NULL, \"\");\n+\t\tcommit->object.flags |= LOCAL;\n+\t\tif (!(commit->object.flags & UNINTERESTING))\n+\t\t\tadd_request(&commit->object, lock);\n+\t}\n \n-\tif (aborted)\n-\t\treturn;\n+\tfor (pending = revs->pending_objects; pending; pending = pending->next) {\n+\t\tstruct object *obj = pending->item;\n+\t\tconst char *name = pending->name;\n \n-\tif (obj->type == commit_type) {\n-\t\tif (push_verbosely)\n-\t\t\tfprintf(stderr, \"walk %s\\n\", sha1_to_hex(obj->sha1));\n-\t\tadd_request(obj->sha1, lock);\n-\t\tcommit = (struct commit *)obj;\n-\t\tif (parse_commit(commit)) {\n-\t\t\tfprintf(stderr, \"Error parsing commit %s\\n\",\n-\t\t\t\tsha1_to_hex(obj->sha1));\n-\t\t\taborted = 1;\n-\t\t\treturn;\n+\t\tif (obj->flags & (UNINTERESTING | SEEN))\n+\t\t\tcontinue;\n+\t\tif (obj->type == tag_type) {\n+\t\t\tobj->flags |= SEEN;\n+\t\t\tp = add_object(obj, p, NULL, name);\n+\t\t\tcontinue;\n \t\t}\n-\t\tparents = commit->parents;\n-\t\tfor (; parents; parents = parents->next)\n-\t\t\tif (sha1 == NULL ||\n-\t\t\t    memcmp(sha1, parents->item->object.sha1, 20))\n-\t\t\t\tget_delta(sha1, &parents->item->object,\n-\t\t\t\t\t  lock);\n-\t\tget_delta(sha1, &commit->tree->object, lock);\n-\t} else if (obj->type == tree_type) {\n-\t\tif (push_verbosely)\n-\t\t\tfprintf(stderr, \"walk %s\\n\", sha1_to_hex(obj->sha1));\n-\t\tadd_request(obj->sha1, lock);\n-\t\ttree = (struct tree *)obj;\n-\t\tif (parse_tree(tree)) {\n-\t\t\tfprintf(stderr, \"Error parsing tree %s\\n\",\n-\t\t\t\tsha1_to_hex(obj->sha1));\n-\t\t\taborted = 1;\n-\t\t\treturn;\n+\t\tif (obj->type == tree_type) {\n+\t\t\tp = process_tree((struct tree *)obj, p, NULL, name);\n+\t\t\tcontinue;\n \t\t}\n-\t\tentry = tree->entries;\n-\t\ttree->entries = NULL;\n-\t\twhile (entry) {\n-\t\t\tstruct tree_entry_list *next = entry->next;\n-\t\t\tget_delta(sha1, entry->item.any, lock);\n-\t\t\tfree(entry->name);\n-\t\t\tfree(entry);\n-\t\t\tentry = next;\n+\t\tif (obj->type == blob_type) {\n+\t\t\tp = process_blob((struct blob *)obj, p, NULL, name);\n+\t\t\tcontinue;\n \t\t}\n-\t} else if (obj->type == blob_type || obj->type == tag_type) {\n-\t\tadd_request(obj->sha1, lock);\n+\t\tdie(\"unknown pending object %s (%s)\", sha1_to_hex(obj->sha1), name);\n+\t}\n+\n+\twhile (objects) {\n+\t\tif (!(objects->item->flags & UNINTERESTING))\n+\t\t\tadd_request(objects->item, lock);\n+\t\tobjects = objects->next;\n \t}\n }\n \n-static int update_remote(unsigned char *sha1, struct active_lock *lock)\n+static int update_remote(unsigned char *sha1, struct remote_lock *lock)\n {\n \tstruct active_request_slot *slot;\n \tchar *out_data;\n@@ -1218,22 +1468,146 @@ static int update_remote(unsigned char *\n \treturn 1;\n }\n \n+static struct ref *local_refs, **local_tail;\n+static struct ref *remote_refs, **remote_tail;\n+\n+static int one_local_ref(const char *refname, const unsigned char *sha1)\n+{\n+\tstruct ref *ref;\n+\tint len = strlen(refname) + 1;\n+\tref = xcalloc(1, sizeof(*ref) + len);\n+\tmemcpy(ref->new_sha1, sha1, 20);\n+\tmemcpy(ref->name, refname, len);\n+\t*local_tail = ref;\n+\tlocal_tail = &ref->next;\n+\treturn 0;\n+}\n+\n+static void one_remote_ref(char *refname)\n+{\n+\tstruct ref *ref;\n+\tunsigned char remote_sha1[20];\n+\n+\tif (fetch_ref(refname, remote_sha1) != 0) {\n+\t\tfprintf(stderr,\n+\t\t\t\"Unable to fetch ref %s from %s\\n\",\n+\t\t\trefname, remote->url);\n+\t\treturn;\n+\t}\n+\n+\tint len = strlen(refname) + 1;\n+\tref = xcalloc(1, sizeof(*ref) + len);\n+\tmemcpy(ref->old_sha1, remote_sha1, 20);\n+\tmemcpy(ref->name, refname, len);\n+\t*remote_tail = ref;\n+\tremote_tail = &ref->next;\n+}\n+\n+static void get_local_heads(void)\n+{\n+\tlocal_tail = &local_refs;\n+\tfor_each_ref(one_local_ref);\n+}\n+\n+static void get_dav_remote_heads(void)\n+{\n+\tremote_tail = &remote_refs;\n+\tcrawl_remote_refs(\"refs/\");\n+}\n+\n+static int is_zero_sha1(const unsigned char *sha1)\n+{\n+\tint i;\n+\n+\tfor (i = 0; i < 20; i++) {\n+\t\tif (*sha1++)\n+\t\t\treturn 0;\n+\t}\n+\treturn 1;\n+}\n+\n+static void unmark_and_free(struct commit_list *list, unsigned int mark)\n+{\n+\twhile (list) {\n+\t\tstruct commit_list *temp = list;\n+\t\ttemp->item->object.flags &= ~mark;\n+\t\tlist = temp->next;\n+\t\tfree(temp);\n+\t}\n+}\n+\n+static int ref_newer(const unsigned char *new_sha1,\n+\t\t     const unsigned char *old_sha1)\n+{\n+\tstruct object *o;\n+\tstruct commit *old, *new;\n+\tstruct commit_list *list, *used;\n+\tint found = 0;\n+\n+\t/* Both new and old must be commit-ish and new is descendant of\n+\t * old.  Otherwise we require --force.\n+\t */\n+\to = deref_tag(parse_object(old_sha1), NULL, 0);\n+\tif (!o || o->type != commit_type)\n+\t\treturn 0;\n+\told = (struct commit *) o;\n+\n+\to = deref_tag(parse_object(new_sha1), NULL, 0);\n+\tif (!o || o->type != commit_type)\n+\t\treturn 0;\n+\tnew = (struct commit *) o;\n+\n+\tif (parse_commit(new) < 0)\n+\t\treturn 0;\n+\n+\tused = list = NULL;\n+\tcommit_list_insert(new, &list);\n+\twhile (list) {\n+\t\tnew = pop_most_recent_commit(&list, TMP_MARK);\n+\t\tcommit_list_insert(new, &used);\n+\t\tif (new == old) {\n+\t\t\tfound = 1;\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\tunmark_and_free(list, TMP_MARK);\n+\tunmark_and_free(used, TMP_MARK);\n+\treturn found;\n+}\n+\n+static void mark_edge_parents_uninteresting(struct commit *commit)\n+{\n+\tstruct commit_list *parents;\n+\n+\tfor (parents = commit->parents; parents; parents = parents->next) {\n+\t\tstruct commit *parent = parents->item;\n+\t\tif (!(parent->object.flags & UNINTERESTING))\n+\t\t\tcontinue;\n+\t\tmark_tree_uninteresting(parent->tree);\n+\t}\n+}\n+\n+static void mark_edges_uninteresting(struct commit_list *list)\n+{\n+\tfor ( ; list; list = list->next) {\n+\t\tstruct commit *commit = list->item;\n+\n+\t\tif (commit->object.flags & UNINTERESTING) {\n+\t\t\tmark_tree_uninteresting(commit->tree);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tmark_edge_parents_uninteresting(commit);\n+\t}\n+}\n+\n int main(int argc, char **argv)\n {\n \tstruct transfer_request *request;\n \tstruct transfer_request *next_request;\n \tint nr_refspec = 0;\n \tchar **refspec = NULL;\n-\tint do_remote_update;\n-\tint new_branch;\n-\tint force_this;\n-\tchar *local_ref;\n-\tunsigned char local_sha1[20];\n-\tstruct object *local_object = NULL;\n-\tchar *remote_ref = NULL;\n-\tunsigned char remote_sha1[20];\n-\tstruct active_lock *remote_lock;\n-\tchar *remote_path = NULL;\n+\tstruct remote_lock *ref_lock;\n+\tstruct rev_info revs;\n \tint rc = 0;\n \tint i;\n \n@@ -1242,6 +1616,7 @@ int main(int argc, char **argv)\n \n \tremote = xmalloc(sizeof(*remote));\n \tremote->url = NULL;\n+\tremote->path_len = 0;\n \tremote->packs = NULL;\n \n \targv++;\n@@ -1249,7 +1624,7 @@ int main(int argc, char **argv)\n \t\tchar *arg = *argv;\n \n \t\tif (*arg == '-') {\n-\t\t\tif (!strcmp(arg, \"--complete\")) {\n+\t\t\tif (!strcmp(arg, \"--all\")) {\n \t\t\t\tpush_all = 1;\n \t\t\t\tcontinue;\n \t\t\t}\n@@ -1265,6 +1640,12 @@ int main(int argc, char **argv)\n \t\t}\n \t\tif (!remote->url) {\n \t\t\tremote->url = arg;\n+\t\t\tchar *path = strstr(arg, \"//\");\n+\t\t\tif (path) {\n+\t\t\t\tpath = index(path+2, '/');\n+\t\t\t\tif (path)\n+\t\t\t\t\tremote->path_len = strlen(path);\n+\t\t\t}\n \t\t\tcontinue;\n \t\t}\n \t\trefspec = argv;\n@@ -1275,7 +1656,7 @@ int main(int argc, char **argv)\n \tif (!remote->url)\n \t\tusage(http_push_usage);\n \n-\tmemset(remote_dir_exists, 0, 256);\n+\tmemset(remote_dir_exists, -1, 256);\n \n \thttp_init();\n \n@@ -1293,92 +1674,113 @@ int main(int argc, char **argv)\n \t\tgoto cleanup;\n \t}\n \n-\t/* Process each refspec */\n-\tfor (i = 0; i < nr_refspec; i++) {\n-\t\tchar *ep;\n-\t\tforce_this = 0;\n-\t\tdo_remote_update = 0;\n-\t\tnew_branch = 0;\n-\t\tlocal_ref = refspec[i];\n-\t\tif (*local_ref == '+') {\n-\t\t\tforce_this = 1;\n-\t\t\tlocal_ref++;\n-\t\t}\n-\t\tep = strchr(local_ref, ':');\n-\t\tif (ep) {\n-\t\t\tremote_ref = ep + 1;\n-\t\t\t*ep = 0;\n+\t/* Get a list of all local and remote heads to validate refspecs */\n+\tget_local_heads();\n+\tfprintf(stderr, \"Fetching remote heads...\\n\");\n+\tget_dav_remote_heads();\n+\n+\t/* match them up */\n+\tif (!remote_tail)\n+\t\tremote_tail = &remote_refs;\n+\tif (match_refs(local_refs, remote_refs, &remote_tail,\n+\t\t       nr_refspec, refspec, push_all))\n+\t\treturn -1;\n+\tif (!remote_refs) {\n+\t\tfprintf(stderr, \"No refs in common and none specified; doing nothing.\\n\");\n+\t\treturn 0;\n+\t}\n+\n+\tint ret = 0;\n+\tint new_refs = 0;\n+\tstruct ref *ref;\n+\tfor (ref = remote_refs; ref; ref = ref->next) {\n+\t\tchar old_hex[60], *new_hex;\n+\t\tif (!ref->peer_ref)\n+\t\t\tcontinue;\n+\t\tif (!memcmp(ref->old_sha1, ref->peer_ref->new_sha1, 20)) {\n+\t\t\tif (push_verbosely || 1)\n+\t\t\t\tfprintf(stderr, \"'%s': up-to-date\\n\", ref->name);\n+\t\t\tcontinue;\n \t\t}\n-\t\telse\n-\t\t\tremote_ref = local_ref;\n+\n+\t\tif (!force_all &&\n+\t\t    !is_zero_sha1(ref->old_sha1) &&\n+\t\t    !ref->force) {\n+\t\t\tif (!has_sha1_file(ref->old_sha1) ||\n+\t\t\t    !ref_newer(ref->peer_ref->new_sha1,\n+\t\t\t\t       ref->old_sha1)) {\n+\t\t\t\t/* We do not have the remote ref, or\n+\t\t\t\t * we know that the remote ref is not\n+\t\t\t\t * an ancestor of what we are trying to\n+\t\t\t\t * push.  Either way this can be losing\n+\t\t\t\t * commits at the remote end and likely\n+\t\t\t\t * we were not up to date to begin with.\n+\t\t\t\t */\n+\t\t\t\terror(\"remote '%s' is not a strict \"\n+\t\t\t\t      \"subset of local ref '%s'. \"\n+\t\t\t\t      \"maybe you are not up-to-date and \"\n+\t\t\t\t      \"need to pull first?\",\n+\t\t\t\t      ref->name,\n+\t\t\t\t      ref->peer_ref->name);\n+\t\t\t\tret = -2;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t}\n+\t\tmemcpy(ref->new_sha1, ref->peer_ref->new_sha1, 20);\n+\t\tif (is_zero_sha1(ref->new_sha1)) {\n+\t\t\terror(\"cannot happen anymore\");\n+\t\t\tret = -3;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tnew_refs++;\n+\t\tstrcpy(old_hex, sha1_to_hex(ref->old_sha1));\n+\t\tnew_hex = sha1_to_hex(ref->new_sha1);\n+\n+\t\tfprintf(stderr, \"updating '%s'\", ref->name);\n+\t\tif (strcmp(ref->name, ref->peer_ref->name))\n+\t\t\tfprintf(stderr, \" using '%s'\", ref->peer_ref->name);\n+\t\tfprintf(stderr, \"\\n  from %s\\n  to   %s\\n\", old_hex, new_hex);\n+\n \n \t\t/* Lock remote branch ref */\n-\t\tif (remote_path)\n-\t\t\tfree(remote_path);\n-\t\tremote_path = xmalloc(strlen(remote_ref) + 12);\n-\t\tsprintf(remote_path, \"refs/heads/%s\", remote_ref);\n-\t\tremote_lock = lock_remote(remote_path, LOCK_TIME);\n-\t\tif (remote_lock == NULL) {\n+\t\tref_lock = lock_remote(ref->name, LOCK_TIME);\n+\t\tif (ref_lock == NULL) {\n \t\t\tfprintf(stderr, \"Unable to lock remote branch %s\\n\",\n-\t\t\t\tremote_ref);\n+\t\t\t\tref->name);\n \t\t\trc = 1;\n \t\t\tcontinue;\n \t\t}\n \n-\t\t/* Resolve local and remote refs */\n-\t\tif (fetch_ref(remote_ref, remote_sha1) != 0) {\n-\t\t\tfprintf(stderr,\n-\t\t\t\t\"Remote branch %s does not exist on %s\\n\",\n-\t\t\t\tremote_ref, remote->url);\n-\t\t\tnew_branch = 1;\n-\t\t}\n-\t\tif (get_sha1(local_ref, local_sha1) != 0) {\n-\t\t\tfprintf(stderr, \"Error resolving local branch %s\\n\",\n-\t\t\t\tlocal_ref);\n-\t\t\trc = 1;\n-\t\t\tgoto unlock;\n-\t\t}\n-\t\n-\t\t/* Find relationship between local and remote */\n-\t\tlocal_object = parse_object(local_sha1);\n-\t\tif (!local_object) {\n-\t\t\tfprintf(stderr, \"Unable to parse local object %s\\n\",\n-\t\t\t\tsha1_to_hex(local_sha1));\n-\t\t\trc = 1;\n-\t\t\tgoto unlock;\n-\t\t} else if (new_branch) {\n-\t\t\tdo_remote_update = 1;\n-\t\t} else {\n-\t\t\tif (!memcmp(local_sha1, remote_sha1, 20)) {\n-\t\t\t\tfprintf(stderr,\n-\t\t\t\t\t\"* %s: same as branch '%s' of %s\\n\",\n-\t\t\t\t\tlocal_ref, remote_ref, remote->url);\n-\t\t\t} else if (is_ancestor(remote_sha1,\n-\t\t\t\t\t       (struct commit *)local_object)) {\n-\t\t\t\tfprintf(stderr,\n-\t\t\t\t\t\"Remote %s will fast-forward to local %s\\n\",\n-\t\t\t\t\tremote_ref, local_ref);\n-\t\t\t\tdo_remote_update = 1;\n-\t\t\t} else if (force_all || force_this) {\n-\t\t\t\tfprintf(stderr,\n-\t\t\t\t\t\"* %s on %s does not fast forward to local branch '%s', overwriting\\n\",\n-\t\t\t\t\tremote_ref, remote->url, local_ref);\n-\t\t\t\tdo_remote_update = 1;\n-\t\t\t} else {\n-\t\t\t\tfprintf(stderr,\n-\t\t\t\t\t\"* %s on %s does not fast forward to local branch '%s'\\n\",\n-\t\t\t\t\tremote_ref, remote->url, local_ref);\n-\t\t\t\trc = 1;\n-\t\t\t\tgoto unlock;\n-\t\t\t}\n+\t\t/* Set up revision info for this refspec */\n+\t\tconst char *commit_argv[3];\n+\t\tint commit_argc = 2;\n+\t\tchar *new_sha1_hex = strdup(sha1_to_hex(ref->new_sha1));\n+\t\tchar *old_sha1_hex = NULL;\n+\t\tcommit_argv[1] = new_sha1_hex;\n+\t\tif (!push_all && !is_zero_sha1(ref->old_sha1)) {\n+\t\t\told_sha1_hex = xmalloc(42);\n+\t\t\tsprintf(old_sha1_hex, \"^%s\",\n+\t\t\t\tsha1_to_hex(ref->old_sha1));\n+\t\t\tcommit_argv[2] = old_sha1_hex;\n+\t\t\tcommit_argc++;\n+\t\t}\n+\t\trevs.commits = NULL;\n+\t\tsetup_revisions(commit_argc, commit_argv, &revs, NULL);\n+\t\trevs.tag_objects = 1;\n+\t\trevs.tree_objects = 1;\n+\t\trevs.blob_objects = 1;\n+\t\tfree(new_sha1_hex);\n+\t\tif (old_sha1_hex) {\n+\t\t\tfree(old_sha1_hex);\n+\t\t\tcommit_argv[1] = NULL;\n \t\t}\n \n-\t\t/* Generate and check list of required objects */\n+\t\t/* Generate a list of objects that need to be pushed */\n \t\tpushing = 0;\n-\t\tif (do_remote_update || push_all)\n-\t\t\tfetch_indices();\n-\t\tget_delta(push_all ? NULL : remote_sha1,\n-\t\t\t  local_object, remote_lock);\n+\t\tprepare_revision_walk(&revs);\n+\t\tmark_edges_uninteresting(revs.commits);\n+\t\tfetch_indices();\n+\t\tget_delta(&revs, ref_lock);\n \t\tfinish_all_active_slots();\n \n \t\t/* Push missing objects to remote, this would be a\n@@ -1388,25 +1790,15 @@ int main(int argc, char **argv)\n \t\tfinish_all_active_slots();\n \n \t\t/* Update the remote branch if all went well */\n-\t\tif (do_remote_update) {\n-\t\t\tif (!aborted && update_remote(local_sha1,\n-\t\t\t\t\t\t      remote_lock)) {\n-\t\t\t\tfprintf(stderr, \"%s remote branch %s\\n\",\n-\t\t\t\t\tnew_branch ? \"Created\" : \"Updated\",\n-\t\t\t\t\tremote_ref);\n-\t\t\t} else {\n-\t\t\t\tfprintf(stderr,\n-\t\t\t\t\t\"Unable to %s remote branch %s\\n\",\n-\t\t\t\t\tnew_branch ? \"create\" : \"update\",\n-\t\t\t\t\tremote_ref);\n-\t\t\t\trc = 1;\n-\t\t\t\tgoto unlock;\n-\t\t\t}\n+\t\tif (aborted || !update_remote(ref->new_sha1, ref_lock)) {\n+\t\t\trc = 1;\n+\t\t\tgoto unlock;\n \t\t}\n \n \tunlock:\n-\t\tunlock_remote(remote_lock);\n-\t\tfree(remote_path);\n+\t\tif (!rc)\n+\t\t\tfprintf(stderr, \"    done\\n\");\n+\t\tunlock_remote(ref_lock);\n \t}\n \n  cleanup:\n-- \n1.2.4.gb477-dirty\n"}]}