Merge branch 'maint'
[gitweb.git] / http-push.c
index 85cd595802ca565f380a9851efd5a3551c959db0..b60fa8d2417c32cf72331e9869d4d3a3208e953f 100644 (file)
 #include "fetch.h"
 #include "tag.h"
 #include "blob.h"
+#include "http.h"
+#include "refs.h"
+#include "revision.h"
+#include "exec_cmd.h"
 
-#include <curl/curl.h>
-#include <curl/easy.h>
-#include "expat.h"
+#include <expat.h>
 
 static const char http_push_usage[] =
-"git-http-push [--complete] [--force] [--verbose] <url> <ref> [<ref>...]\n";
+"git-http-push [--all] [--force] [--verbose] <remote> [<head>...]\n";
 
-#if LIBCURL_VERSION_NUM >= 0x070908
-#define USE_CURL_MULTI
-#define DEFAULT_MAX_REQUESTS 5
-#endif
-
-#if LIBCURL_VERSION_NUM < 0x070704
-#define curl_global_cleanup() do { /* nothing */ } while(0)
-#endif
-#if LIBCURL_VERSION_NUM < 0x070800
-#define curl_global_init(a) do { /* nothing */ } while(0)
-#endif
-
-#if LIBCURL_VERSION_NUM < 0x070c04
-#define NO_CURL_EASY_DUPHANDLE
+#ifndef XML_STATUS_OK
+enum XML_Status {
+  XML_STATUS_OK = 1,
+  XML_STATUS_ERROR = 0
+};
+#define XML_STATUS_OK    1
+#define XML_STATUS_ERROR 0
 #endif
 
+#define PREV_BUF_SIZE 4096
 #define RANGE_HEADER_SIZE 30
 
-/* DAV method names and request body templates */
+/* DAV methods */
 #define DAV_LOCK "LOCK"
 #define DAV_MKCOL "MKCOL"
 #define DAV_MOVE "MOVE"
 #define DAV_PROPFIND "PROPFIND"
 #define DAV_PUT "PUT"
 #define DAV_UNLOCK "UNLOCK"
-#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>"
+#define DAV_DELETE "DELETE"
+
+/* DAV lock flags */
+#define DAV_PROP_LOCKWR (1u << 0)
+#define DAV_PROP_LOCKEX (1u << 1)
+#define DAV_LOCK_OK (1u << 2)
+
+/* DAV XML properties */
+#define DAV_CTX_LOCKENTRY ".multistatus.response.propstat.prop.supportedlock.lockentry"
+#define DAV_CTX_LOCKTYPE_WRITE ".multistatus.response.propstat.prop.supportedlock.lockentry.locktype.write"
+#define DAV_CTX_LOCKTYPE_EXCLUSIVE ".multistatus.response.propstat.prop.supportedlock.lockentry.lockscope.exclusive"
+#define DAV_ACTIVELOCK_OWNER ".prop.lockdiscovery.activelock.owner.href"
+#define DAV_ACTIVELOCK_TIMEOUT ".prop.lockdiscovery.activelock.timeout"
+#define DAV_ACTIVELOCK_TOKEN ".prop.lockdiscovery.activelock.locktoken.href"
+#define DAV_PROPFIND_RESP ".multistatus.response"
+#define DAV_PROPFIND_NAME ".multistatus.response.href"
+#define DAV_PROPFIND_COLLECTION ".multistatus.response.propstat.prop.resourcetype.collection"
+
+/* DAV request body templates */
+#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>"
+#define PROPFIND_ALL_REQUEST "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:allprop/>\n</D:propfind>"
 #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>"
 
-static int active_requests = 0;
-static int data_received;
+#define LOCK_TIME 600
+#define LOCK_REFRESH 30
+
+/* bits #0-4 in revision.h */
+
+#define LOCAL    (1u << 5)
+#define REMOTE   (1u << 6)
+#define FETCHING (1u << 7)
+#define PUSHING  (1u << 8)
+
+/* We allow "recursive" symbolic refs. Only within reason, though */
+#define MAXDEPTH 5
+
 static int pushing = 0;
 static int aborted = 0;
+static signed char remote_dir_exists[256];
 
-#ifdef USE_CURL_MULTI
-static int max_requests = -1;
-static CURLM *curlm;
-#endif
-#ifndef NO_CURL_EASY_DUPHANDLE
-static CURL *curl_default;
-#endif
 static struct curl_slist *no_pragma_header;
 static struct curl_slist *default_headers;
-static char curl_errorstr[CURL_ERROR_SIZE];
 
 static int push_verbosely = 0;
 static int push_all = 0;
 static int force_all = 0;
 
-struct buffer
-{
-        size_t posn;
-        size_t size;
-        void *buffer;
-};
+static struct object_list *objects = NULL;
 
 struct repo
 {
        char *url;
+       int path_len;
+       int has_info_refs;
+       int can_update_info_refs;
+       int has_info_packs;
        struct packed_git *packs;
+       struct remote_lock *locks;
 };
 
 static struct repo *remote = NULL;
 
 enum transfer_state {
-       NEED_CHECK,
-       RUN_HEAD,
+       NEED_FETCH,
+       RUN_FETCH_LOOSE,
+       RUN_FETCH_PACKED,
        NEED_PUSH,
        RUN_MKCOL,
        RUN_PUT,
@@ -88,14 +109,16 @@ enum transfer_state {
 
 struct transfer_request
 {
-       unsigned char sha1[20];
+       struct object *obj;
        char *url;
        char *dest;
-       struct active_lock *lock;
+       struct remote_lock *lock;
        struct curl_slist *headers;
        struct buffer buffer;
        char filename[PATH_MAX];
        char tmpfile[PATH_MAX];
+       int local_fileno;
+       FILE *local_stream;
        enum transfer_state state;
        CURLcode curl_result;
        char errorstr[CURL_ERROR_SIZE];
@@ -105,349 +128,318 @@ struct transfer_request
        z_stream stream;
        int zret;
        int rename;
+       void *userData;
        struct active_request_slot *slot;
        struct transfer_request *next;
 };
 
-struct active_request_slot
-{
-       CURL *curl;
-       FILE *local;
-       int in_use;
-       int done;
-       CURLcode curl_result;
-       long http_code;
-       struct active_request_slot *next;
-};
-
 static struct transfer_request *request_queue_head = NULL;
-static struct active_request_slot *active_queue_head = NULL;
 
-static int curl_ssl_verify = -1;
-static char *ssl_cert = NULL;
-#if LIBCURL_VERSION_NUM >= 0x070902
-static char *ssl_key = NULL;
-#endif
-#if LIBCURL_VERSION_NUM >= 0x070908
-static char *ssl_capath = NULL;
-#endif
-static char *ssl_cainfo = NULL;
-static long curl_low_speed_limit = -1;
-static long curl_low_speed_time = -1;
+struct xml_ctx
+{
+       char *name;
+       int len;
+       char *cdata;
+       void (*userFunc)(struct xml_ctx *ctx, int tag_closed);
+       void *userData;
+};
 
-struct active_lock
+struct remote_lock
 {
-       int ctx_activelock;
-       int ctx_owner;
-       int ctx_owner_href;
-       int ctx_timeout;
-       int ctx_locktoken;
-       int ctx_locktoken_href;
+       char *url;
        char *owner;
+       char *token;
        time_t start_time;
        long timeout;
-       char *token;
-};
-
-struct lockprop
-{
-       int supported_lock;
-       int lock_entry;
-       int lock_scope;
-       int lock_type;
-       int lock_exclusive;
-       int lock_exclusive_write;
+       int refreshing;
+       struct remote_lock *next;
 };
 
-static int http_options(const char *var, const char *value)
-{
-       if (!strcmp("http.sslverify", var)) {
-               if (curl_ssl_verify == -1) {
-                       curl_ssl_verify = git_config_bool(var, value);
-               }
-               return 0;
-       }
-
-       if (!strcmp("http.sslcert", var)) {
-               if (ssl_cert == NULL) {
-                       ssl_cert = xmalloc(strlen(value)+1);
-                       strcpy(ssl_cert, value);
-               }
-               return 0;
-       }
-#if LIBCURL_VERSION_NUM >= 0x070902
-       if (!strcmp("http.sslkey", var)) {
-               if (ssl_key == NULL) {
-                       ssl_key = xmalloc(strlen(value)+1);
-                       strcpy(ssl_key, value);
-               }
-               return 0;
-       }
-#endif
-#if LIBCURL_VERSION_NUM >= 0x070908
-       if (!strcmp("http.sslcapath", var)) {
-               if (ssl_capath == NULL) {
-                       ssl_capath = xmalloc(strlen(value)+1);
-                       strcpy(ssl_capath, value);
-               }
-               return 0;
-       }
-#endif
-       if (!strcmp("http.sslcainfo", var)) {
-               if (ssl_cainfo == NULL) {
-                       ssl_cainfo = xmalloc(strlen(value)+1);
-                       strcpy(ssl_cainfo, value);
-               }
-               return 0;
-       }
+/* Flags that control remote_ls processing */
+#define PROCESS_FILES (1u << 0)
+#define PROCESS_DIRS  (1u << 1)
+#define RECURSIVE     (1u << 2)
 
-#ifdef USE_CURL_MULTI  
-       if (!strcmp("http.maxrequests", var)) {
-               if (max_requests == -1)
-                       max_requests = git_config_int(var, value);
-               return 0;
-       }
-#endif
+/* Flags that remote_ls passes to callback functions */
+#define IS_DIR (1u << 0)
 
-       if (!strcmp("http.lowspeedlimit", var)) {
-               if (curl_low_speed_limit == -1)
-                       curl_low_speed_limit = (long)git_config_int(var, value);
-               return 0;
-       }
-       if (!strcmp("http.lowspeedtime", var)) {
-               if (curl_low_speed_time == -1)
-                       curl_low_speed_time = (long)git_config_int(var, value);
-               return 0;
-       }
+struct remote_ls_ctx
+{
+       char *path;
+       void (*userFunc)(struct remote_ls_ctx *ls);
+       void *userData;
+       int flags;
+       char *dentry_name;
+       int dentry_flags;
+       struct remote_ls_ctx *parent;
+};
 
-       /* Fall back on the default ones */
-       return git_default_config(var, value);
-}
+static void finish_request(struct transfer_request *request);
+static void release_request(struct transfer_request *request);
 
-static size_t fread_buffer(void *ptr, size_t eltsize, size_t nmemb,
-                          struct buffer *buffer)
+static void process_response(void *callback_data)
 {
-       size_t size = eltsize * nmemb;
-       if (size > buffer->size - buffer->posn)
-               size = buffer->size - buffer->posn;
-       memcpy(ptr, buffer->buffer + buffer->posn, size);
-       buffer->posn += size;
-       return size;
+       struct transfer_request *request =
+               (struct transfer_request *)callback_data;
+
+       finish_request(request);
 }
 
-static size_t fwrite_buffer_dynamic(const void *ptr, size_t eltsize,
-                                   size_t nmemb, struct buffer *buffer)
+static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb,
+                              void *data)
 {
+       unsigned char expn[4096];
        size_t size = eltsize * nmemb;
-       if (size > buffer->size - buffer->posn) {
-               buffer->size = buffer->size * 3 / 2;
-               if (buffer->size < buffer->posn + size)
-                       buffer->size = buffer->posn + size;
-               buffer->buffer = xrealloc(buffer->buffer, buffer->size);
-       }
-       memcpy(buffer->buffer + buffer->posn, ptr, size);
-       buffer->posn += size;
+       int posn = 0;
+       struct transfer_request *request = (struct transfer_request *)data;
+       do {
+               ssize_t retval = write(request->local_fileno,
+                                      ptr + posn, size - posn);
+               if (retval < 0)
+                       return posn;
+               posn += retval;
+       } while (posn < size);
+
+       request->stream.avail_in = size;
+       request->stream.next_in = ptr;
+       do {
+               request->stream.next_out = expn;
+               request->stream.avail_out = sizeof(expn);
+               request->zret = inflate(&request->stream, Z_SYNC_FLUSH);
+               SHA1_Update(&request->c, expn,
+                           sizeof(expn) - request->stream.avail_out);
+       } while (request->stream.avail_in && request->zret == Z_OK);
        data_received++;
        return size;
 }
 
-static size_t fwrite_null(const void *ptr, size_t eltsize,
-                         size_t nmemb, struct buffer *buffer)
-{
-       data_received++;
-       return eltsize * nmemb;
-}
-
-#ifdef USE_CURL_MULTI
-static void process_curl_messages(void);
-static void process_request_queue(void);
-#endif
-
-static CURL* get_curl_handle(void)
+static void start_fetch_loose(struct transfer_request *request)
 {
-       CURL* result = curl_easy_init();
-
-       curl_easy_setopt(result, CURLOPT_SSL_VERIFYPEER, curl_ssl_verify);
-#if LIBCURL_VERSION_NUM >= 0x070907
-       curl_easy_setopt(result, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
-#endif
-
-       if (ssl_cert != NULL)
-               curl_easy_setopt(result, CURLOPT_SSLCERT, ssl_cert);
-#if LIBCURL_VERSION_NUM >= 0x070902
-       if (ssl_key != NULL)
-               curl_easy_setopt(result, CURLOPT_SSLKEY, ssl_key);
-#endif
-#if LIBCURL_VERSION_NUM >= 0x070908
-       if (ssl_capath != NULL)
-               curl_easy_setopt(result, CURLOPT_CAPATH, ssl_capath);
-#endif
-       if (ssl_cainfo != NULL)
-               curl_easy_setopt(result, CURLOPT_CAINFO, ssl_cainfo);
-       curl_easy_setopt(result, CURLOPT_FAILONERROR, 1);
+       char *hex = sha1_to_hex(request->obj->sha1);
+       char *filename;
+       char prevfile[PATH_MAX];
+       char *url;
+       char *posn;
+       int prevlocal;
+       unsigned char prev_buf[PREV_BUF_SIZE];
+       ssize_t prev_read = 0;
+       long prev_posn = 0;
+       char range[RANGE_HEADER_SIZE];
+       struct curl_slist *range_header = NULL;
+       struct active_request_slot *slot;
 
-       if (curl_low_speed_limit > 0 && curl_low_speed_time > 0) {
-               curl_easy_setopt(result, CURLOPT_LOW_SPEED_LIMIT,
-                                curl_low_speed_limit);
-               curl_easy_setopt(result, CURLOPT_LOW_SPEED_TIME,
-                                curl_low_speed_time);
+       filename = sha1_file_name(request->obj->sha1);
+       snprintf(request->filename, sizeof(request->filename), "%s", filename);
+       snprintf(request->tmpfile, sizeof(request->tmpfile),
+                "%s.temp", filename);
+
+       snprintf(prevfile, sizeof(prevfile), "%s.prev", request->filename);
+       unlink(prevfile);
+       rename(request->tmpfile, prevfile);
+       unlink(request->tmpfile);
+
+       if (request->local_fileno != -1)
+               error("fd leakage in start: %d", request->local_fileno);
+       request->local_fileno = open(request->tmpfile,
+                                    O_WRONLY | O_CREAT | O_EXCL, 0666);
+       /* This could have failed due to the "lazy directory creation";
+        * try to mkdir the last path component.
+        */
+       if (request->local_fileno < 0 && errno == ENOENT) {
+               char *dir = strrchr(request->tmpfile, '/');
+               if (dir) {
+                       *dir = 0;
+                       mkdir(request->tmpfile, 0777);
+                       *dir = '/';
+               }
+               request->local_fileno = open(request->tmpfile,
+                                            O_WRONLY | O_CREAT | O_EXCL, 0666);
        }
 
-       return result;
-}
+       if (request->local_fileno < 0) {
+               request->state = ABORTED;
+               error("Couldn't create temporary file %s for %s: %s",
+                     request->tmpfile, request->filename, strerror(errno));
+               return;
+       }
 
-static struct active_request_slot *get_active_slot(void)
-{
-       struct active_request_slot *slot = active_queue_head;
-       struct active_request_slot *newslot;
+       memset(&request->stream, 0, sizeof(request->stream));
 
-#ifdef USE_CURL_MULTI
-       int num_transfers;
+       inflateInit(&request->stream);
 
-       /* Wait for a slot to open up if the queue is full */
-       while (active_requests >= max_requests) {
-               curl_multi_perform(curlm, &num_transfers);
-               if (num_transfers < active_requests) {
-                       process_curl_messages();
-               }
-       }
-#endif
+       SHA1_Init(&request->c);
 
-       while (slot != NULL && slot->in_use) {
-               slot = slot->next;
-       }
-       if (slot == NULL) {
-               newslot = xmalloc(sizeof(*newslot));
-               newslot->curl = NULL;
-               newslot->in_use = 0;
-               newslot->next = NULL;
+       url = xmalloc(strlen(remote->url) + 50);
+       request->url = xmalloc(strlen(remote->url) + 50);
+       strcpy(url, remote->url);
+       posn = url + strlen(remote->url);
+       strcpy(posn, "objects/");
+       posn += 8;
+       memcpy(posn, hex, 2);
+       posn += 2;
+       *(posn++) = '/';
+       strcpy(posn, hex + 2);
+       strcpy(request->url, url);
 
-               slot = active_queue_head;
-               if (slot == NULL) {
-                       active_queue_head = newslot;
-               } else {
-                       while (slot->next != NULL) {
-                               slot = slot->next;
+       /* If a previous temp file is present, process what was already
+          fetched. */
+       prevlocal = open(prevfile, O_RDONLY);
+       if (prevlocal != -1) {
+               do {
+                       prev_read = read(prevlocal, prev_buf, PREV_BUF_SIZE);
+                       if (prev_read>0) {
+                               if (fwrite_sha1_file(prev_buf,
+                                                    1,
+                                                    prev_read,
+                                                    request) == prev_read) {
+                                       prev_posn += prev_read;
+                               } else {
+                                       prev_read = -1;
+                               }
                        }
-                       slot->next = newslot;
+               } while (prev_read > 0);
+               close(prevlocal);
+       }
+       unlink(prevfile);
+
+       /* Reset inflate/SHA1 if there was an error reading the previous temp
+          file; also rewind to the beginning of the local file. */
+       if (prev_read == -1) {
+               memset(&request->stream, 0, sizeof(request->stream));
+               inflateInit(&request->stream);
+               SHA1_Init(&request->c);
+               if (prev_posn>0) {
+                       prev_posn = 0;
+                       lseek(request->local_fileno, SEEK_SET, 0);
+                       ftruncate(request->local_fileno, 0);
                }
-               slot = newslot;
        }
 
-       if (slot->curl == NULL) {
-#ifdef NO_CURL_EASY_DUPHANDLE
-               slot->curl = get_curl_handle();
-#else
-               slot->curl = curl_easy_duphandle(curl_default);
-#endif
-       }
+       slot = get_active_slot();
+       slot->callback_func = process_response;
+       slot->callback_data = request;
+       request->slot = slot;
+
+       curl_easy_setopt(slot->curl, CURLOPT_FILE, request);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_sha1_file);
+       curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, request->errorstr);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_pragma_header);
 
-       active_requests++;
-       slot->in_use = 1;
-       slot->done = 0;
-       slot->local = NULL;
-       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, default_headers);
-       curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, curl_errorstr);
+       /* If we have successfully processed data from a previous fetch
+          attempt, only fetch the data we don't already have. */
+       if (prev_posn>0) {
+               if (push_verbosely)
+                       fprintf(stderr,
+                               "Resuming fetch of object %s at byte %ld\n",
+                               hex, prev_posn);
+               sprintf(range, "Range: bytes=%ld-", prev_posn);
+               range_header = curl_slist_append(range_header, range);
+               curl_easy_setopt(slot->curl,
+                                CURLOPT_HTTPHEADER, range_header);
+       }
 
-       return slot;
+       /* Try to get the request started, abort the request on error */
+       request->state = RUN_FETCH_LOOSE;
+       if (!start_active_slot(slot)) {
+               fprintf(stderr, "Unable to start GET request\n");
+               remote->can_update_info_refs = 0;
+               release_request(request);
+       }
 }
 
-static int start_active_slot(struct active_request_slot *slot)
+static void start_fetch_packed(struct transfer_request *request)
 {
-#ifdef USE_CURL_MULTI
-       CURLMcode curlm_result = curl_multi_add_handle(curlm, slot->curl);
+       char *url;
+       struct packed_git *target;
+       FILE *packfile;
+       char *filename;
+       long prev_posn = 0;
+       char range[RANGE_HEADER_SIZE];
+       struct curl_slist *range_header = NULL;
 
-       if (curlm_result != CURLM_OK &&
-           curlm_result != CURLM_CALL_MULTI_PERFORM) {
-               active_requests--;
-               slot->in_use = 0;
-               return 0;
+       struct transfer_request *check_request = request_queue_head;
+       struct active_request_slot *slot;
+
+       target = find_sha1_pack(request->obj->sha1, remote->packs);
+       if (!target) {
+               fprintf(stderr, "Unable to fetch %s, will not be able to update server info refs\n", sha1_to_hex(request->obj->sha1));
+               remote->can_update_info_refs = 0;
+               release_request(request);
+               return;
        }
-#endif
-       return 1;
-}
 
-static void run_active_slot(struct active_request_slot *slot)
-{
-#ifdef USE_CURL_MULTI
-       int num_transfers;
-       long last_pos = 0;
-       long current_pos;
-       fd_set readfds;
-       fd_set writefds;
-       fd_set excfds;
-       int max_fd;
-       struct timeval select_timeout;
-       CURLMcode curlm_result;
-
-       while (!slot->done) {
-               data_received = 0;
-               do {
-                       curlm_result = curl_multi_perform(curlm,
-                                                         &num_transfers);
-               } while (curlm_result == CURLM_CALL_MULTI_PERFORM);
-               if (num_transfers < active_requests) {
-                       process_curl_messages();
-                       process_request_queue();
-               }
+       fprintf(stderr, "Fetching pack %s\n", sha1_to_hex(target->sha1));
+       fprintf(stderr, " which contains %s\n", sha1_to_hex(request->obj->sha1));
 
-               if (!data_received && slot->local != NULL) {
-                       current_pos = ftell(slot->local);
-                       if (current_pos > last_pos)
-                               data_received++;
-                       last_pos = current_pos;
-               }
+       filename = sha1_pack_name(target->sha1);
+       snprintf(request->filename, sizeof(request->filename), "%s", filename);
+       snprintf(request->tmpfile, sizeof(request->tmpfile),
+                "%s.temp", filename);
+
+       url = xmalloc(strlen(remote->url) + 64);
+       sprintf(url, "%sobjects/pack/pack-%s.pack",
+               remote->url, sha1_to_hex(target->sha1));
 
-               if (!slot->done && !data_received) {
-                       max_fd = 0;
-                       FD_ZERO(&readfds);
-                       FD_ZERO(&writefds);
-                       FD_ZERO(&excfds);
-                       select_timeout.tv_sec = 0;
-                       select_timeout.tv_usec = 50000;
-                       select(max_fd, &readfds, &writefds,
-                              &excfds, &select_timeout);
+       /* Make sure there isn't another open request for this pack */
+       while (check_request) {
+               if (check_request->state == RUN_FETCH_PACKED &&
+                   !strcmp(check_request->url, url)) {
+                       free(url);
+                       release_request(request);
+                       return;
                }
+               check_request = check_request->next;
        }
-#else
-       slot->curl_result = curl_easy_perform(slot->curl);
-       active_requests--;
-#endif
-}
-
-static void start_check(struct transfer_request *request)
-{
-       char *hex = sha1_to_hex(request->sha1);
-       struct active_request_slot *slot;
-       char *posn;
 
-       request->url = xmalloc(strlen(remote->url) + 55);
-       strcpy(request->url, remote->url);
-       posn = request->url + strlen(remote->url);
-       strcpy(posn, "objects/");
-       posn += 8;
-       memcpy(posn, hex, 2);
-       posn += 2;
-       *(posn++) = '/';
-       strcpy(posn, hex + 2);
+       packfile = fopen(request->tmpfile, "a");
+       if (!packfile) {
+               fprintf(stderr, "Unable to open local file %s for pack",
+                       filename);
+               remote->can_update_info_refs = 0;
+               free(url);
+               return;
+       }
 
        slot = get_active_slot();
-       curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, request->errorstr);
-       curl_easy_setopt(slot->curl, CURLOPT_URL, request->url);
-       curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 1);
+       slot->callback_func = process_response;
+       slot->callback_data = request;
+       request->slot = slot;
+       request->local_stream = packfile;
+       request->userData = target;
+
+       request->url = url;
+       curl_easy_setopt(slot->curl, CURLOPT_FILE, packfile);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_pragma_header);
+       slot->local = packfile;
 
-       if (start_active_slot(slot)) {
-               request->slot = slot;
-               request->state = RUN_HEAD;
-       } else {
-               request->state = ABORTED;
-               free(request->url);
+       /* If there is data present from a previous transfer attempt,
+          resume where it left off */
+       prev_posn = ftell(packfile);
+       if (prev_posn>0) {
+               if (push_verbosely)
+                       fprintf(stderr,
+                               "Resuming fetch of pack %s at byte %ld\n",
+                               sha1_to_hex(target->sha1), prev_posn);
+               sprintf(range, "Range: bytes=%ld-", prev_posn);
+               range_header = curl_slist_append(range_header, range);
+               curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, range_header);
+       }
+
+       /* Try to get the request started, abort the request on error */
+       request->state = RUN_FETCH_PACKED;
+       if (!start_active_slot(slot)) {
+               fprintf(stderr, "Unable to start GET request\n");
+               remote->can_update_info_refs = 0;
+               release_request(request);
        }
 }
 
 static void start_mkcol(struct transfer_request *request)
 {
-       char *hex = sha1_to_hex(request->sha1);
+       char *hex = sha1_to_hex(request->obj->sha1);
        struct active_request_slot *slot;
        char *posn;
 
@@ -461,6 +453,8 @@ static void start_mkcol(struct transfer_request *request)
        strcpy(posn, "/");
 
        slot = get_active_slot();
+       slot->callback_func = process_response;
+       slot->callback_data = request;
        curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); /* undo PUT setup */
        curl_easy_setopt(slot->curl, CURLOPT_URL, request->url);
        curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, request->errorstr);
@@ -473,12 +467,13 @@ static void start_mkcol(struct transfer_request *request)
        } else {
                request->state = ABORTED;
                free(request->url);
+               request->url = NULL;
        }
 }
 
 static void start_put(struct transfer_request *request)
 {
-       char *hex = sha1_to_hex(request->sha1);
+       char *hex = sha1_to_hex(request->obj->sha1);
        struct active_request_slot *slot;
        char *posn;
        char type[20];
@@ -489,7 +484,7 @@ static void start_put(struct transfer_request *request)
        ssize_t size;
        z_stream stream;
 
-       unpacked = read_sha1_file(request->sha1, type, &len);
+       unpacked = read_sha1_file(request->obj->sha1, type, &len);
        hdrlen = sprintf(hdr, "%s %lu", type, len) + 1;
 
        /* Set it up */
@@ -519,8 +514,6 @@ static void start_put(struct transfer_request *request)
        request->buffer.size = stream.total_out;
        request->buffer.posn = 0;
 
-       if (request->url != NULL)
-               free(request->url);
        request->url = xmalloc(strlen(remote->url) + 
                               strlen(request->lock->token) + 51);
        strcpy(request->url, remote->url);
@@ -538,6 +531,8 @@ static void start_put(struct transfer_request *request)
        strcpy(posn, request->lock->token);
 
        slot = get_active_slot();
+       slot->callback_func = process_response;
+       slot->callback_data = request;
        curl_easy_setopt(slot->curl, CURLOPT_INFILE, &request->buffer);
        curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, request->buffer.size);
        curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
@@ -554,6 +549,7 @@ static void start_put(struct transfer_request *request)
        } else {
                request->state = ABORTED;
                free(request->url);
+               request->url = NULL;
        }
 }
 
@@ -563,6 +559,8 @@ static void start_move(struct transfer_request *request)
        struct curl_slist *dav_headers = NULL;
 
        slot = get_active_slot();
+       slot->callback_func = process_response;
+       slot->callback_data = request;
        curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); /* undo PUT setup */
        curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_MOVE);
        dav_headers = curl_slist_append(dav_headers, request->dest);
@@ -577,35 +575,127 @@ static void start_move(struct transfer_request *request)
        } else {
                request->state = ABORTED;
                free(request->url);
+               request->url = NULL;
+       }
+}
+
+static int refresh_lock(struct remote_lock *lock)
+{
+       struct active_request_slot *slot;
+       struct slot_results results;
+       char *if_header;
+       char timeout_header[25];
+       struct curl_slist *dav_headers = NULL;
+       int rc = 0;
+
+       lock->refreshing = 1;
+
+       if_header = xmalloc(strlen(lock->token) + 25);
+       sprintf(if_header, "If: (<opaquelocktoken:%s>)", lock->token);
+       sprintf(timeout_header, "Timeout: Second-%ld", lock->timeout);
+       dav_headers = curl_slist_append(dav_headers, if_header);
+       dav_headers = curl_slist_append(dav_headers, timeout_header);
+
+       slot = get_active_slot();
+       slot->results = &results;
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, lock->url);
+       curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_LOCK);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
+
+       if (start_active_slot(slot)) {
+               run_active_slot(slot);
+               if (results.curl_result != CURLE_OK) {
+                       fprintf(stderr, "LOCK HTTP error %ld\n",
+                               results.http_code);
+               } else {
+                       lock->start_time = time(NULL);
+                       rc = 1;
+               }
+       }
+
+       lock->refreshing = 0;
+       curl_slist_free_all(dav_headers);
+       free(if_header);
+
+       return rc;
+}
+
+static void check_locks(void)
+{
+       struct remote_lock *lock = remote->locks;
+       time_t current_time = time(NULL);
+       int time_remaining;
+
+       while (lock) {
+               time_remaining = lock->start_time + lock->timeout -
+                       current_time;
+               if (!lock->refreshing && time_remaining < LOCK_REFRESH) {
+                       if (!refresh_lock(lock)) {
+                               fprintf(stderr,
+                                       "Unable to refresh lock for %s\n",
+                                       lock->url);
+                               aborted = 1;
+                               return;
+                       }
+               }
+               lock = lock->next;
+       }
+}
+
+static void release_request(struct transfer_request *request)
+{
+       struct transfer_request *entry = request_queue_head;
+
+       if (request == request_queue_head) {
+               request_queue_head = request->next;
+       } else {
+               while (entry->next != NULL && entry->next != request)
+                       entry = entry->next;
+               if (entry->next == request)
+                       entry->next = entry->next->next;
        }
+
+       if (request->local_fileno != -1)
+               close(request->local_fileno);
+       if (request->local_stream)
+               fclose(request->local_stream);
+       if (request->url != NULL)
+               free(request->url);
+       free(request);
 }
 
 static void finish_request(struct transfer_request *request)
 {
-       request->curl_result =  request->slot->curl_result;
+       struct stat st;
+       struct packed_git *target;
+       struct packed_git **lst;
+
+       request->curl_result = request->slot->curl_result;
        request->http_code = request->slot->http_code;
        request->slot = NULL;
+
+       /* Keep locks active */
+       check_locks();
+
        if (request->headers != NULL)
                curl_slist_free_all(request->headers);
-       if (request->state == RUN_HEAD) {
-               if (request->http_code == 404) {
-                       request->state = NEED_PUSH;
-               } else if (request->curl_result == CURLE_OK) {
-                       request->state = COMPLETE;
-               } else {
-                       fprintf(stderr, "HEAD %s failed, aborting (%d/%ld)\n",
-                               sha1_to_hex(request->sha1),
-                               request->curl_result, request->http_code);
-                       request->state = ABORTED;
-                       aborted = 1;
-               }
-       } else if (request->state == RUN_MKCOL) {
+
+       /* URL is reused for MOVE after PUT */
+       if (request->state != RUN_PUT) {
+               free(request->url);
+               request->url = NULL;
+       }
+
+       if (request->state == RUN_MKCOL) {
                if (request->curl_result == CURLE_OK ||
                    request->http_code == 405) {
+                       remote_dir_exists[request->obj->sha1[0]] = 1;
                        start_put(request);
                } else {
                        fprintf(stderr, "MKCOL %s failed, aborting (%d/%ld)\n",
-                               sha1_to_hex(request->sha1),
+                               sha1_to_hex(request->obj->sha1),
                                request->curl_result, request->http_code);
                        request->state = ABORTED;
                        aborted = 1;
@@ -615,7 +705,7 @@ static void finish_request(struct transfer_request *request)
                        start_move(request);
                } else {
                        fprintf(stderr, "PUT %s failed, aborting (%d/%ld)\n",
-                               sha1_to_hex(request->sha1),
+                               sha1_to_hex(request->obj->sha1),
                                request->curl_result, request->http_code);
                        request->state = ABORTED;
                        aborted = 1;
@@ -623,80 +713,84 @@ static void finish_request(struct transfer_request *request)
        } else if (request->state == RUN_MOVE) {
                if (request->curl_result == CURLE_OK) {
                        if (push_verbosely)
-                               fprintf(stderr,
-                                       "sent %s\n",
-                                       sha1_to_hex(request->sha1));
-                       request->state = COMPLETE;
+                               fprintf(stderr, "    sent %s\n",
+                                       sha1_to_hex(request->obj->sha1));
+                       request->obj->flags |= REMOTE;
+                       release_request(request);
                } else {
                        fprintf(stderr, "MOVE %s failed, aborting (%d/%ld)\n",
-                               sha1_to_hex(request->sha1),
+                               sha1_to_hex(request->obj->sha1),
                                request->curl_result, request->http_code);
                        request->state = ABORTED;
                        aborted = 1;
                }
-       }
-}
-
-static void release_request(struct transfer_request *request)
-{
-       struct transfer_request *entry = request_queue_head;
-
-       if (request == request_queue_head) {
-               request_queue_head = request->next;
-       } else {
-               while (entry->next != NULL && entry->next != request)
-                       entry = entry->next;
-               if (entry->next == request)
-                       entry->next = entry->next->next;
-       }
-
-       free(request->url);
-       free(request);
-}
-
-#ifdef USE_CURL_MULTI
-void process_curl_messages(void)
-{
-       int num_messages;
-       struct active_request_slot *slot;
-       struct transfer_request *request = NULL;
-       CURLMsg *curl_message = curl_multi_info_read(curlm, &num_messages);
-
-       while (curl_message != NULL) {
-               if (curl_message->msg == CURLMSG_DONE) {
-                       slot = active_queue_head;
-                       while (slot != NULL &&
-                              slot->curl != curl_message->easy_handle)
-                               slot = slot->next;
-                       if (slot != NULL) {
-                               curl_multi_remove_handle(curlm, slot->curl);
-                               active_requests--;
-                               slot->done = 1;
-                               slot->in_use = 0;
-                               slot->curl_result = curl_message->data.result;
-                               curl_easy_getinfo(slot->curl,
-                                                 CURLINFO_HTTP_CODE,
-                                                 &slot->http_code);
-                               request = request_queue_head;
-                               while (request != NULL &&
-                                      request->slot != slot)
-                                       request = request->next;
-                               if (request != NULL)
-                                       finish_request(request);
+       } else if (request->state == RUN_FETCH_LOOSE) {
+               fchmod(request->local_fileno, 0444);
+               close(request->local_fileno); request->local_fileno = -1;
+
+               if (request->curl_result != CURLE_OK &&
+                   request->http_code != 416) {
+                       if (stat(request->tmpfile, &st) == 0) {
+                               if (st.st_size == 0)
+                                       unlink(request->tmpfile);
+                       }
+               } else {
+                       if (request->http_code == 416)
+                               fprintf(stderr, "Warning: requested range invalid; we may already have all the data.\n");
+
+                       inflateEnd(&request->stream);
+                       SHA1_Final(request->real_sha1, &request->c);
+                       if (request->zret != Z_STREAM_END) {
+                               unlink(request->tmpfile);
+                       } else if (memcmp(request->obj->sha1, request->real_sha1, 20)) {
+                               unlink(request->tmpfile);
                        } else {
-                               fprintf(stderr, "Received DONE message for unknown request!\n");
+                               request->rename =
+                                       move_temp_to_file(
+                                               request->tmpfile,
+                                               request->filename);
+                               if (request->rename == 0) {
+                                       request->obj->flags |= (LOCAL | REMOTE);
+                               }
                        }
+               }
+
+               /* Try fetching packed if necessary */
+               if (request->obj->flags & LOCAL)
+                       release_request(request);
+               else
+                       start_fetch_packed(request);
+
+       } else if (request->state == RUN_FETCH_PACKED) {
+               if (request->curl_result != CURLE_OK) {
+                       fprintf(stderr, "Unable to get pack file %s\n%s",
+                               request->url, curl_errorstr);
+                       remote->can_update_info_refs = 0;
                } else {
-                       fprintf(stderr, "Unknown CURL message received: %d\n",
-                               (int)curl_message->msg);
+                       fclose(request->local_stream);
+                       request->local_stream = NULL;
+                       if (!move_temp_to_file(request->tmpfile,
+                                              request->filename)) {
+                               target = (struct packed_git *)request->userData;
+                               lst = &remote->packs;
+                               while (*lst != target)
+                                       lst = &((*lst)->next);
+                               *lst = (*lst)->next;
+
+                               if (!verify_pack(target, 0))
+                                       install_packed_git(target);
+                               else
+                                       remote->can_update_info_refs = 0;
+                       }
                }
-               curl_message = curl_multi_info_read(curlm, &num_messages);
+               release_request(request);
        }
 }
 
-void process_request_queue(void)
+void fill_active_slots(void)
 {
        struct transfer_request *request = request_queue_head;
+       struct transfer_request *next;
        struct active_request_slot *slot = active_queue_head;
        int num_transfers;
 
@@ -704,14 +798,18 @@ void process_request_queue(void)
                return;
 
        while (active_requests < max_requests && request != NULL) {
-               if (!pushing && request->state == NEED_CHECK) {
-                       start_check(request);
-                       curl_multi_perform(curlm, &num_transfers);
+               next = request->next;
+               if (request->state == NEED_FETCH) {
+                       start_fetch_loose(request);
                } else if (pushing && request->state == NEED_PUSH) {
-                       start_mkcol(request);
+                       if (remote_dir_exists[request->obj->sha1[0]] == 1) {
+                               start_put(request);
+                       } else {
+                               start_mkcol(request);
+                       }
                        curl_multi_perform(curlm, &num_transfers);
                }
-               request = request->next;
+               request = next;
        }
 
        while (slot != NULL) {
@@ -720,49 +818,80 @@ void process_request_queue(void)
                        slot->curl = NULL;
                }
                slot = slot->next;
-       }                               
+       }
 }
-#endif
 
-void process_waiting_requests(void)
+static void get_remote_object_list(unsigned char parent);
+
+static void add_fetch_request(struct object *obj)
 {
-       struct active_request_slot *slot = active_queue_head;
+       struct transfer_request *request;
 
-       while (slot != NULL)
-               if (slot->in_use) {
-                       run_active_slot(slot);
-                       slot = active_queue_head;
-               } else {
-                       slot = slot->next;
-               }
+       check_locks();
+
+       /*
+        * Don't fetch the object if it's known to exist locally
+        * or is already in the request queue
+        */
+       if (remote_dir_exists[obj->sha1[0]] == -1)
+               get_remote_object_list(obj->sha1[0]);
+       if (obj->flags & (LOCAL | FETCHING))
+               return;
+
+       obj->flags |= FETCHING;
+       request = xmalloc(sizeof(*request));
+       request->obj = obj;
+       request->url = NULL;
+       request->lock = NULL;
+       request->headers = NULL;
+       request->local_fileno = -1;
+       request->local_stream = NULL;
+       request->state = NEED_FETCH;
+       request->next = request_queue_head;
+       request_queue_head = request;
+
+       fill_active_slots();
+       step_active_slots();
 }
 
-void add_request(unsigned char *sha1, struct active_lock *lock)
+static int add_send_request(struct object *obj, struct remote_lock *lock)
 {
        struct transfer_request *request = request_queue_head;
        struct packed_git *target;
-       
-       while (request != NULL && memcmp(request->sha1, sha1, 20))
-               request = request->next;
-       if (request != NULL)
-               return;
 
-       target = find_sha1_pack(sha1, remote->packs);
-       if (target)
-               return;
+       /* Keep locks active */
+       check_locks();
 
+       /*
+        * Don't push the object if it's known to exist on the remote
+        * or is already in the request queue
+        */
+       if (remote_dir_exists[obj->sha1[0]] == -1)
+               get_remote_object_list(obj->sha1[0]);
+       if (obj->flags & (REMOTE | PUSHING))
+               return 0;
+       target = find_sha1_pack(obj->sha1, remote->packs);
+       if (target) {
+               obj->flags |= REMOTE;
+               return 0;
+       }
+
+       obj->flags |= PUSHING;
        request = xmalloc(sizeof(*request));
-       memcpy(request->sha1, sha1, 20);
+       request->obj = obj;
        request->url = NULL;
        request->lock = lock;
        request->headers = NULL;
-       request->state = NEED_CHECK;
+       request->local_fileno = -1;
+       request->local_stream = NULL;
+       request->state = NEED_PUSH;
        request->next = request_queue_head;
        request_queue_head = request;
-#ifdef USE_CURL_MULTI
-       process_request_queue();
-       process_curl_messages();
-#endif
+
+       fill_active_slots();
+       step_active_slots();
+
+       return 1;
 }
 
 static int fetch_index(unsigned char *sha1)
@@ -777,16 +906,18 @@ static int fetch_index(unsigned char *sha1)
 
        FILE *indexfile;
        struct active_request_slot *slot;
+       struct slot_results results;
 
        /* Don't use the index if the pack isn't there */
-       url = xmalloc(strlen(remote->url) + 65);
-       sprintf(url, "%s/objects/pack/pack-%s.pack", remote->url, hex);
+       url = xmalloc(strlen(remote->url) + 64);
+       sprintf(url, "%sobjects/pack/pack-%s.pack", remote->url, hex);
        slot = get_active_slot();
+       slot->results = &results;
        curl_easy_setopt(slot->curl, CURLOPT_URL, url);
        curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 1);
        if (start_active_slot(slot)) {
                run_active_slot(slot);
-               if (slot->curl_result != CURLE_OK) {
+               if (results.curl_result != CURLE_OK) {
                        free(url);
                        return error("Unable to verify pack %s is available",
                                     hex);
@@ -800,9 +931,9 @@ static int fetch_index(unsigned char *sha1)
 
        if (push_verbosely)
                fprintf(stderr, "Getting index for pack %s\n", hex);
-       
-       sprintf(url, "%s/objects/pack/pack-%s.idx", remote->url, hex);
-       
+
+       sprintf(url, "%sobjects/pack/pack-%s.idx", remote->url, hex);
+
        filename = sha1_pack_index_name(sha1);
        snprintf(tmpfile, sizeof(tmpfile), "%s.temp", filename);
        indexfile = fopen(tmpfile, "a");
@@ -811,6 +942,7 @@ static int fetch_index(unsigned char *sha1)
                             filename);
 
        slot = get_active_slot();
+       slot->results = &results;
        curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0);
        curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);
        curl_easy_setopt(slot->curl, CURLOPT_FILE, indexfile);
@@ -834,7 +966,7 @@ static int fetch_index(unsigned char *sha1)
 
        if (start_active_slot(slot)) {
                run_active_slot(slot);
-               if (slot->curl_result != CURLE_OK) {
+               if (results.curl_result != CURLE_OK) {
                        free(url);
                        fclose(indexfile);
                        return error("Unable to get pack index %s\n%s", url,
@@ -842,6 +974,7 @@ static int fetch_index(unsigned char *sha1)
                }
        } else {
                free(url);
+               fclose(indexfile);
                return error("Unable to start request");
        }
 
@@ -864,7 +997,7 @@ static int setup_index(unsigned char *sha1)
        return 0;
 }
 
-static int fetch_indices()
+static int fetch_indices(void)
 {
        unsigned char sha1[20];
        char *url;
@@ -873,31 +1006,31 @@ static int fetch_indices()
        int i = 0;
 
        struct active_request_slot *slot;
+       struct slot_results results;
 
-       data = xmalloc(4096);
-       memset(data, 0, 4096);
+       data = xcalloc(1, 4096);
        buffer.size = 4096;
        buffer.posn = 0;
        buffer.buffer = data;
 
        if (push_verbosely)
                fprintf(stderr, "Getting pack list\n");
-       
-       url = xmalloc(strlen(remote->url) + 21);
-       sprintf(url, "%s/objects/info/packs", remote->url);
+
+       url = xmalloc(strlen(remote->url) + 20);
+       sprintf(url, "%sobjects/info/packs", remote->url);
 
        slot = get_active_slot();
+       slot->results = &results;
        curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
-       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION,
-                        fwrite_buffer_dynamic);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
        curl_easy_setopt(slot->curl, CURLOPT_URL, url);
        curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
        if (start_active_slot(slot)) {
                run_active_slot(slot);
-               if (slot->curl_result != CURLE_OK) {
+               if (results.curl_result != CURLE_OK) {
                        free(buffer.buffer);
                        free(url);
-                       if (slot->http_code == 404)
+                       if (results.http_code == 404)
                                return 0;
                        else
                                return error("%s", curl_errorstr);
@@ -957,14 +1090,13 @@ static char *quote_ref_url(const char *base, const char *ref)
        int len, baselen, ch;
 
        baselen = strlen(base);
-       len = baselen + 12; /* "refs/heads/" + NUL */
+       len = baselen + 1;
        for (cp = ref; (ch = *cp) != 0; cp++, len++)
                if (needs_quote(ch))
                        len += 2; /* extra two hex plus replacement % */
        qref = xmalloc(len);
        memcpy(qref, base, baselen);
-       memcpy(qref + baselen, "refs/heads/", 11);
-       for (cp = ref, dp = qref + baselen + 11; (ch = *cp) != 0; cp++) {
+       for (cp = ref, dp = qref + baselen; (ch = *cp) != 0; cp++) {
                if (needs_quote(ch)) {
                        *dp++ = '%';
                        *dp++ = hex((ch >> 4) & 0xF);
@@ -985,21 +1117,22 @@ int fetch_ref(char *ref, unsigned char *sha1)
         struct buffer buffer;
        char *base = remote->url;
        struct active_request_slot *slot;
+       struct slot_results results;
         buffer.size = 41;
         buffer.posn = 0;
         buffer.buffer = hex;
         hex[41] = '\0';
-        
+
        url = quote_ref_url(base, ref);
        slot = get_active_slot();
+       slot->results = &results;
        curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
-       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION,
-                        fwrite_buffer_dynamic);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
        curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
        curl_easy_setopt(slot->curl, CURLOPT_URL, url);
        if (start_active_slot(slot)) {
                run_active_slot(slot);
-               if (slot->curl_result != CURLE_OK)
+               if (results.curl_result != CURLE_OK)
                        return error("Couldn't get %s for %s\n%s",
                                     url, ref, curl_errorstr);
        } else {
@@ -1011,112 +1144,130 @@ int fetch_ref(char *ref, unsigned char *sha1)
         return 0;
 }
 
-static void
-start_activelock_element(void *userData, const char *name, const char **atts)
+static void one_remote_object(const char *hex)
 {
-       struct active_lock *lock = (struct active_lock *)userData;
-
-       if (lock->ctx_activelock && !strcmp(name, "D:timeout"))
-               lock->ctx_timeout = 1;
-       else if (lock->ctx_owner && strstr(name, "href"))
-               lock->ctx_owner_href = 1;
-       else if (lock->ctx_activelock && strstr(name, "owner"))
-               lock->ctx_owner = 1;
-       else if (lock->ctx_locktoken && !strcmp(name, "D:href"))
-               lock->ctx_locktoken_href = 1;
-       else if (lock->ctx_activelock && !strcmp(name, "D:locktoken"))
-               lock->ctx_locktoken = 1;
-       else if (!strcmp(name, "D:activelock"))
-               lock->ctx_activelock = 1;
+       unsigned char sha1[20];
+       struct object *obj;
+
+       if (get_sha1_hex(hex, sha1) != 0)
+               return;
+
+       obj = lookup_object(sha1);
+       if (!obj)
+               obj = parse_object(sha1);
+
+       /* Ignore remote objects that don't exist locally */
+       if (!obj)
+               return;
+
+       obj->flags |= REMOTE;
+       if (!object_list_contains(objects, obj))
+               add_object(obj, &objects, NULL, "");
 }
 
-static void
-end_activelock_element(void *userData, const char *name)
+static void handle_lockprop_ctx(struct xml_ctx *ctx, int tag_closed)
 {
-       struct active_lock *lock = (struct active_lock *)userData;
-
-       if (lock->ctx_timeout && !strcmp(name, "D:timeout")) {
-               lock->ctx_timeout = 0;
-       } else if (lock->ctx_owner_href && strstr(name, "href")) {
-               lock->ctx_owner_href = 0;
-       } else if (lock->ctx_owner && strstr(name, "owner")) {
-               lock->ctx_owner = 0;
-       } else if (lock->ctx_locktoken_href && !strcmp(name, "D:href")) {
-               lock->ctx_locktoken_href = 0;
-       } else if (lock->ctx_locktoken && !strcmp(name, "D:locktoken")) {
-               lock->ctx_locktoken = 0;
-       } else if (lock->ctx_activelock && !strcmp(name, "D:activelock")) {
-               lock->ctx_activelock = 0;
+       int *lock_flags = (int *)ctx->userData;
+
+       if (tag_closed) {
+               if (!strcmp(ctx->name, DAV_CTX_LOCKENTRY)) {
+                       if ((*lock_flags & DAV_PROP_LOCKEX) &&
+                           (*lock_flags & DAV_PROP_LOCKWR)) {
+                               *lock_flags |= DAV_LOCK_OK;
+                       }
+                       *lock_flags &= DAV_LOCK_OK;
+               } else if (!strcmp(ctx->name, DAV_CTX_LOCKTYPE_WRITE)) {
+                       *lock_flags |= DAV_PROP_LOCKWR;
+               } else if (!strcmp(ctx->name, DAV_CTX_LOCKTYPE_EXCLUSIVE)) {
+                       *lock_flags |= DAV_PROP_LOCKEX;
+               }
        }
 }
 
-static void
-activelock_cdata(void *userData, const XML_Char *s, int len)
+static void handle_new_lock_ctx(struct xml_ctx *ctx, int tag_closed)
 {
-       struct active_lock *lock = (struct active_lock *)userData;
-       char *this = malloc(len+1);
-       strncpy(this, s, len);
-
-       if (lock->ctx_owner_href) {
-               lock->owner = malloc(len+1);
-               strcpy(lock->owner, this);
-       } else if (lock->ctx_locktoken_href) {
-               if (!strncmp(this, "opaquelocktoken:", 16)) {
-                       lock->token = malloc(len-15);
-                       strcpy(lock->token, this+16);
+       struct remote_lock *lock = (struct remote_lock *)ctx->userData;
+
+       if (tag_closed && ctx->cdata) {
+               if (!strcmp(ctx->name, DAV_ACTIVELOCK_OWNER)) {
+                       lock->owner = xmalloc(strlen(ctx->cdata) + 1);
+                       strcpy(lock->owner, ctx->cdata);
+               } else if (!strcmp(ctx->name, DAV_ACTIVELOCK_TIMEOUT)) {
+                       if (!strncmp(ctx->cdata, "Second-", 7))
+                               lock->timeout =
+                                       strtol(ctx->cdata + 7, NULL, 10);
+               } else if (!strcmp(ctx->name, DAV_ACTIVELOCK_TOKEN)) {
+                       if (!strncmp(ctx->cdata, "opaquelocktoken:", 16)) {
+                               lock->token = xmalloc(strlen(ctx->cdata) - 15);
+                               strcpy(lock->token, ctx->cdata + 16);
+                       }
                }
-       } else if (lock->ctx_timeout) {
-               if (!strncmp(this, "Second-", 7))
-                       lock->timeout = strtol(this+7, NULL, 10);
        }
-
-       free(this);
 }
 
+static void one_remote_ref(char *refname);
+
 static void
-start_lockprop_element(void *userData, const char *name, const char **atts)
+xml_start_tag(void *userData, const char *name, const char **atts)
 {
-       struct lockprop *prop = (struct lockprop *)userData;
+       struct xml_ctx *ctx = (struct xml_ctx *)userData;
+       const char *c = index(name, ':');
+       int new_len;
 
-       if (prop->lock_type && !strcmp(name, "D:write")) {
-               if (prop->lock_exclusive) {
-                       prop->lock_exclusive_write = 1;
-               }
-       } else if (prop->lock_scope && !strcmp(name, "D:exclusive")) {
-               prop->lock_exclusive = 1;
-       } else if (prop->lock_entry) {
-               if (!strcmp(name, "D:lockscope")) {
-                       prop->lock_scope = 1;
-               } else if (!strcmp(name, "D:locktype")) {
-                       prop->lock_type = 1;
-               }
-       } else if (prop->supported_lock) {
-               if (!strcmp(name, "D:lockentry")) {
-                       prop->lock_entry = 1;
-               }
-       } else if (!strcmp(name, "D:supportedlock")) {
-               prop->supported_lock = 1;
+       if (c == NULL)
+               c = name;
+       else
+               c++;
+
+       new_len = strlen(ctx->name) + strlen(c) + 2;
+
+       if (new_len > ctx->len) {
+               ctx->name = xrealloc(ctx->name, new_len);
+               ctx->len = new_len;
        }
+       strcat(ctx->name, ".");
+       strcat(ctx->name, c);
+
+       if (ctx->cdata) {
+               free(ctx->cdata);
+               ctx->cdata = NULL;
+       }
+
+       ctx->userFunc(ctx, 0);
 }
 
 static void
-end_lockprop_element(void *userData, const char *name)
+xml_end_tag(void *userData, const char *name)
 {
-       struct lockprop *prop = (struct lockprop *)userData;
+       struct xml_ctx *ctx = (struct xml_ctx *)userData;
+       const char *c = index(name, ':');
+       char *ep;
 
-       if (!strcmp(name, "D:lockentry")) {
-               prop->lock_entry = 0;
-               prop->lock_scope = 0;
-               prop->lock_type = 0;
-               prop->lock_exclusive = 0;
-       } else if (!strcmp(name, "D:supportedlock")) {
-               prop->supported_lock = 0;
-       }
+       ctx->userFunc(ctx, 1);
+
+       if (c == NULL)
+               c = name;
+       else
+               c++;
+
+       ep = ctx->name + strlen(ctx->name) - strlen(c) - 1;
+       *ep = 0;
 }
 
-struct active_lock *lock_remote(char *file, int timeout)
+static void
+xml_cdata(void *userData, const XML_Char *s, int len)
+{
+       struct xml_ctx *ctx = (struct xml_ctx *)userData;
+       if (ctx->cdata)
+               free(ctx->cdata);
+       ctx->cdata = xcalloc(len+1, 1);
+       strncpy(ctx->cdata, s, len);
+}
+
+static struct remote_lock *lock_remote(char *path, long timeout)
 {
        struct active_request_slot *slot;
+       struct slot_results results;
        struct buffer out_buffer;
        struct buffer in_buffer;
        char *out_data;
@@ -1124,27 +1275,29 @@ struct active_lock *lock_remote(char *file, int timeout)
        char *url;
        char *ep;
        char timeout_header[25];
-       struct active_lock *new_lock;
+       struct remote_lock *lock = NULL;
        XML_Parser parser = XML_ParserCreate(NULL);
        enum XML_Status result;
        struct curl_slist *dav_headers = NULL;
+       struct xml_ctx ctx;
 
-       url = xmalloc(strlen(remote->url) + strlen(file) + 1);
-       sprintf(url, "%s%s", remote->url, file);
+       url = xmalloc(strlen(remote->url) + strlen(path) + 1);
+       sprintf(url, "%s%s", remote->url, path);
 
        /* Make sure leading directories exist for the remote ref */
        ep = strchr(url + strlen(remote->url) + 11, '/');
        while (ep) {
                *ep = 0;
                slot = get_active_slot();
+               slot->results = &results;
                curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);
                curl_easy_setopt(slot->curl, CURLOPT_URL, url);
                curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_MKCOL);
                curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
                if (start_active_slot(slot)) {
                        run_active_slot(slot);
-                       if (slot->curl_result != CURLE_OK &&
-                           slot->http_code != 405) {
+                       if (results.curl_result != CURLE_OK &&
+                           results.http_code != 405) {
                                fprintf(stderr,
                                        "Unable to create branch path %s\n",
                                        url);
@@ -1152,7 +1305,7 @@ struct active_lock *lock_remote(char *file, int timeout)
                                return NULL;
                        }
                } else {
-                       fprintf(stderr, "Unable to start request\n");
+                       fprintf(stderr, "Unable to start MKCOL request\n");
                        free(url);
                        return NULL;
                }
@@ -1171,79 +1324,78 @@ struct active_lock *lock_remote(char *file, int timeout)
        in_buffer.posn = 0;
        in_buffer.buffer = in_data;
 
-       new_lock = xmalloc(sizeof(*new_lock));
-       new_lock->owner = NULL;
-       new_lock->token = NULL;
-       new_lock->timeout = -1;
-
-       sprintf(timeout_header, "Timeout: Second-%d", timeout);
+       sprintf(timeout_header, "Timeout: Second-%ld", timeout);
        dav_headers = curl_slist_append(dav_headers, timeout_header);
        dav_headers = curl_slist_append(dav_headers, "Content-Type: text/xml");
 
        slot = get_active_slot();
+       slot->results = &results;
        curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer);
        curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.size);
        curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
        curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer);
-       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION,
-                        fwrite_buffer_dynamic);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
        curl_easy_setopt(slot->curl, CURLOPT_URL, url);
        curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1);
        curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_LOCK);
        curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
 
+       lock = xcalloc(1, sizeof(*lock));
+       lock->timeout = -1;
+
        if (start_active_slot(slot)) {
                run_active_slot(slot);
-               if (slot->curl_result != CURLE_OK) {
-                       fprintf(stderr, "Got HTTP error %ld\n", slot->http_code);
-                       free(new_lock);
-                       free(url);
-                       free(out_data);
-                       free(in_data);
-                       return NULL;
+               if (results.curl_result == CURLE_OK) {
+                       ctx.name = xcalloc(10, 1);
+                       ctx.len = 0;
+                       ctx.cdata = NULL;
+                       ctx.userFunc = handle_new_lock_ctx;
+                       ctx.userData = lock;
+                       XML_SetUserData(parser, &ctx);
+                       XML_SetElementHandler(parser, xml_start_tag,
+                                             xml_end_tag);
+                       XML_SetCharacterDataHandler(parser, xml_cdata);
+                       result = XML_Parse(parser, in_buffer.buffer,
+                                          in_buffer.posn, 1);
+                       free(ctx.name);
+                       if (result != XML_STATUS_OK) {
+                               fprintf(stderr, "XML error: %s\n",
+                                       XML_ErrorString(
+                                               XML_GetErrorCode(parser)));
+                               lock->timeout = -1;
+                       }
                }
        } else {
-               free(new_lock);
-               free(url);
-               free(out_data);
-               free(in_data);
-               fprintf(stderr, "Unable to start request\n");
-               return NULL;
+               fprintf(stderr, "Unable to start LOCK request\n");
        }
 
-       free(url);
+       curl_slist_free_all(dav_headers);
        free(out_data);
-
-       XML_SetUserData(parser, new_lock);
-       XML_SetElementHandler(parser, start_activelock_element,
-                                     end_activelock_element);
-       XML_SetCharacterDataHandler(parser, activelock_cdata);
-       result = XML_Parse(parser, in_buffer.buffer, in_buffer.posn, 1);
        free(in_data);
-       if (result != XML_STATUS_OK) {
-               fprintf(stderr, "%s", XML_ErrorString(
-                               XML_GetErrorCode(parser)));
-               free(new_lock);
-               return NULL;
-       }
 
-       if (new_lock->token == NULL || new_lock->timeout <= 0) {
-               if (new_lock->token != NULL)
-                       free(new_lock->token);
-               if (new_lock->owner != NULL)
-                       free(new_lock->owner);
-               free(new_lock);
-               return NULL;
+       if (lock->token == NULL || lock->timeout <= 0) {
+               if (lock->token != NULL)
+                       free(lock->token);
+               if (lock->owner != NULL)
+                       free(lock->owner);
+               free(url);
+               free(lock);
+               lock = NULL;
+       } else {
+               lock->url = url;
+               lock->start_time = time(NULL);
+               lock->next = remote->locks;
+               remote->locks = lock;
        }
 
-       new_lock->start_time = time(NULL);
-       return new_lock;
+       return lock;
 }
 
-int unlock_remote(char *file, struct active_lock *lock)
+static int unlock_remote(struct remote_lock *lock)
 {
        struct active_request_slot *slot;
-       char *url;
+       struct slot_results results;
+       struct remote_lock *prev = remote->locks;
        char *lock_token_header;
        struct curl_slist *dav_headers = NULL;
        int rc = 0;
@@ -1251,49 +1403,148 @@ int unlock_remote(char *file, struct active_lock *lock)
        lock_token_header = xmalloc(strlen(lock->token) + 31);
        sprintf(lock_token_header, "Lock-Token: <opaquelocktoken:%s>",
                lock->token);
-       url = xmalloc(strlen(remote->url) + strlen(file) + 1);
-       sprintf(url, "%s%s", remote->url, file);
        dav_headers = curl_slist_append(dav_headers, lock_token_header);
 
        slot = get_active_slot();
+       slot->results = &results;
        curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
-       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, lock->url);
        curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_UNLOCK);
        curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
 
        if (start_active_slot(slot)) {
                run_active_slot(slot);
-               if (slot->curl_result == CURLE_OK)
+               if (results.curl_result == CURLE_OK)
                        rc = 1;
                else
-                       fprintf(stderr, "Got HTTP error %ld\n",
-                               slot->http_code);
+                       fprintf(stderr, "UNLOCK HTTP error %ld\n",
+                               results.http_code);
        } else {
-               fprintf(stderr, "Unable to start request\n");
+               fprintf(stderr, "Unable to start UNLOCK request\n");
        }
 
        curl_slist_free_all(dav_headers);
        free(lock_token_header);
-       free(url);
+
+       if (remote->locks == lock) {
+               remote->locks = lock->next;
+       } else {
+               while (prev && prev->next != lock)
+                       prev = prev->next;
+               if (prev)
+                       prev->next = prev->next->next;
+       }
+
+       if (lock->owner != NULL)
+               free(lock->owner);
+       free(lock->url);
+       free(lock->token);
+       free(lock);
 
        return rc;
 }
 
-int check_locking()
+static void remote_ls(const char *path, int flags,
+                     void (*userFunc)(struct remote_ls_ctx *ls),
+                     void *userData);
+
+static void process_ls_object(struct remote_ls_ctx *ls)
 {
+       unsigned int *parent = (unsigned int *)ls->userData;
+       char *path = ls->dentry_name;
+       char *obj_hex;
+
+       if (!strcmp(ls->path, ls->dentry_name) && (ls->flags & IS_DIR)) {
+               remote_dir_exists[*parent] = 1;
+               return;
+       }
+
+       if (strlen(path) != 49)
+               return;
+       path += 8;
+       obj_hex = xmalloc(strlen(path));
+       strncpy(obj_hex, path, 2);
+       strcpy(obj_hex + 2, path + 3);
+       one_remote_object(obj_hex);
+       free(obj_hex);
+}
+
+static void process_ls_ref(struct remote_ls_ctx *ls)
+{
+       if (!strcmp(ls->path, ls->dentry_name) && (ls->dentry_flags & IS_DIR)) {
+               fprintf(stderr, "  %s\n", ls->dentry_name);
+               return;
+       }
+
+       if (!(ls->dentry_flags & IS_DIR))
+               one_remote_ref(ls->dentry_name);
+}
+
+static void handle_remote_ls_ctx(struct xml_ctx *ctx, int tag_closed)
+{
+       struct remote_ls_ctx *ls = (struct remote_ls_ctx *)ctx->userData;
+
+       if (tag_closed) {
+               if (!strcmp(ctx->name, DAV_PROPFIND_RESP) && ls->dentry_name) {
+                       if (ls->dentry_flags & IS_DIR) {
+                               if (ls->flags & PROCESS_DIRS) {
+                                       ls->userFunc(ls);
+                               }
+                               if (strcmp(ls->dentry_name, ls->path) &&
+                                   ls->flags & RECURSIVE) {
+                                       remote_ls(ls->dentry_name,
+                                                 ls->flags,
+                                                 ls->userFunc,
+                                                 ls->userData);
+                               }
+                       } else if (ls->flags & PROCESS_FILES) {
+                               ls->userFunc(ls);
+                       }
+               } else if (!strcmp(ctx->name, DAV_PROPFIND_NAME) && ctx->cdata) {
+                       ls->dentry_name = xmalloc(strlen(ctx->cdata) -
+                                                 remote->path_len + 1);
+                       strcpy(ls->dentry_name, ctx->cdata + remote->path_len);
+               } else if (!strcmp(ctx->name, DAV_PROPFIND_COLLECTION)) {
+                       ls->dentry_flags |= IS_DIR;
+               }
+       } else if (!strcmp(ctx->name, DAV_PROPFIND_RESP)) {
+               if (ls->dentry_name) {
+                       free(ls->dentry_name);
+               }
+               ls->dentry_name = NULL;
+               ls->dentry_flags = 0;
+       }
+}
+
+static void remote_ls(const char *path, int flags,
+                     void (*userFunc)(struct remote_ls_ctx *ls),
+                     void *userData)
+{
+       char *url = xmalloc(strlen(remote->url) + strlen(path) + 1);
        struct active_request_slot *slot;
+       struct slot_results results;
        struct buffer in_buffer;
        struct buffer out_buffer;
        char *in_data;
        char *out_data;
        XML_Parser parser = XML_ParserCreate(NULL);
        enum XML_Status result;
-       struct lockprop supported_lock;
        struct curl_slist *dav_headers = NULL;
+       struct xml_ctx ctx;
+       struct remote_ls_ctx ls;
+
+       ls.flags = flags;
+       ls.path = strdup(path);
+       ls.dentry_name = NULL;
+       ls.dentry_flags = 0;
+       ls.userData = userData;
+       ls.userFunc = userFunc;
+
+       sprintf(url, "%s%s", remote->url, path);
 
-       out_buffer.size = strlen(PROPFIND_REQUEST) + strlen(remote->url) - 2;
+       out_buffer.size = strlen(PROPFIND_ALL_REQUEST);
        out_data = xmalloc(out_buffer.size + 1);
-       snprintf(out_data, out_buffer.size + 1, PROPFIND_REQUEST, remote->url);
+       snprintf(out_data, out_buffer.size + 1, PROPFIND_ALL_REQUEST);
        out_buffer.posn = 0;
        out_buffer.buffer = out_data;
 
@@ -1302,141 +1553,248 @@ int check_locking()
        in_buffer.posn = 0;
        in_buffer.buffer = in_data;
 
-       dav_headers = curl_slist_append(dav_headers, "Depth: 0");
+       dav_headers = curl_slist_append(dav_headers, "Depth: 1");
        dav_headers = curl_slist_append(dav_headers, "Content-Type: text/xml");
-       
+
        slot = get_active_slot();
+       slot->results = &results;
        curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer);
        curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.size);
        curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
        curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer);
-       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION,
-                        fwrite_buffer_dynamic);
-       curl_easy_setopt(slot->curl, CURLOPT_URL, remote->url);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
        curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1);
        curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PROPFIND);
        curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
 
        if (start_active_slot(slot)) {
                run_active_slot(slot);
-               free(out_data);
-               if (slot->curl_result != CURLE_OK) {
-                       free(in_buffer.buffer);
-                       return -1;
+               if (results.curl_result == CURLE_OK) {
+                       ctx.name = xcalloc(10, 1);
+                       ctx.len = 0;
+                       ctx.cdata = NULL;
+                       ctx.userFunc = handle_remote_ls_ctx;
+                       ctx.userData = &ls;
+                       XML_SetUserData(parser, &ctx);
+                       XML_SetElementHandler(parser, xml_start_tag,
+                                             xml_end_tag);
+                       XML_SetCharacterDataHandler(parser, xml_cdata);
+                       result = XML_Parse(parser, in_buffer.buffer,
+                                          in_buffer.posn, 1);
+                       free(ctx.name);
+
+                       if (result != XML_STATUS_OK) {
+                               fprintf(stderr, "XML error: %s\n",
+                                       XML_ErrorString(
+                                               XML_GetErrorCode(parser)));
+                       }
                }
-
-               XML_SetUserData(parser, &supported_lock);
-               XML_SetElementHandler(parser, start_lockprop_element,
-                                     end_lockprop_element);
-               result = XML_Parse(parser, in_buffer.buffer, in_buffer.posn, 1);
-               free(in_buffer.buffer);
-               if (result != XML_STATUS_OK)
-                       return error("%s", XML_ErrorString(
-                                            XML_GetErrorCode(parser)));
        } else {
-               free(out_data);
-               free(in_buffer.buffer);
-               return error("Unable to start request");
+               fprintf(stderr, "Unable to start PROPFIND request\n");
        }
 
-       if (supported_lock.lock_exclusive_write)
-               return 0;
-       else
-               return 1;
+       free(ls.path);
+       free(url);
+       free(out_data);
+       free(in_buffer.buffer);
+       curl_slist_free_all(dav_headers);
 }
 
-int is_ancestor(unsigned char *sha1, struct commit *commit)
+static void get_remote_object_list(unsigned char parent)
 {
-       struct commit_list *parents;
+       char path[] = "objects/XX/";
+       static const char hex[] = "0123456789abcdef";
+       unsigned int val = parent;
+
+       path[8] = hex[val >> 4];
+       path[9] = hex[val & 0xf];
+       remote_dir_exists[val] = 0;
+       remote_ls(path, (PROCESS_FILES | PROCESS_DIRS),
+                 process_ls_object, &val);
+}
 
-       if (parse_commit(commit))
-               return 0;
-       parents = commit->parents;
-       for (; parents; parents = parents->next) {
-               if (!memcmp(sha1, parents->item->object.sha1, 20)) {
-                       return 1;
-               } else if (parents->item->object.type == commit_type) {
-                       if (is_ancestor(
-                                   sha1,
-                                   (struct commit *)&parents->item->object
-                                   ))
-                               return 1;
+static int locking_available(void)
+{
+       struct active_request_slot *slot;
+       struct slot_results results;
+       struct buffer in_buffer;
+       struct buffer out_buffer;
+       char *in_data;
+       char *out_data;
+       XML_Parser parser = XML_ParserCreate(NULL);
+       enum XML_Status result;
+       struct curl_slist *dav_headers = NULL;
+       struct xml_ctx ctx;
+       int lock_flags = 0;
+
+       out_buffer.size =
+               strlen(PROPFIND_SUPPORTEDLOCK_REQUEST) +
+               strlen(remote->url) - 2;
+       out_data = xmalloc(out_buffer.size + 1);
+       snprintf(out_data, out_buffer.size + 1,
+                PROPFIND_SUPPORTEDLOCK_REQUEST, remote->url);
+       out_buffer.posn = 0;
+       out_buffer.buffer = out_data;
+
+       in_buffer.size = 4096;
+       in_data = xmalloc(in_buffer.size);
+       in_buffer.posn = 0;
+       in_buffer.buffer = in_data;
+
+       dav_headers = curl_slist_append(dav_headers, "Depth: 0");
+       dav_headers = curl_slist_append(dav_headers, "Content-Type: text/xml");
+
+       slot = get_active_slot();
+       slot->results = &results;
+       curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.size);
+       curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, remote->url);
+       curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1);
+       curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PROPFIND);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
+
+       if (start_active_slot(slot)) {
+               run_active_slot(slot);
+               if (results.curl_result == CURLE_OK) {
+                       ctx.name = xcalloc(10, 1);
+                       ctx.len = 0;
+                       ctx.cdata = NULL;
+                       ctx.userFunc = handle_lockprop_ctx;
+                       ctx.userData = &lock_flags;
+                       XML_SetUserData(parser, &ctx);
+                       XML_SetElementHandler(parser, xml_start_tag,
+                                             xml_end_tag);
+                       result = XML_Parse(parser, in_buffer.buffer,
+                                          in_buffer.posn, 1);
+                       free(ctx.name);
+
+                       if (result != XML_STATUS_OK) {
+                               fprintf(stderr, "XML error: %s\n",
+                                       XML_ErrorString(
+                                               XML_GetErrorCode(parser)));
+                               lock_flags = 0;
+                       }
                }
+       } else {
+               fprintf(stderr, "Unable to start PROPFIND request\n");
        }
-       return 0;
+
+       free(out_data);
+       free(in_buffer.buffer);
+       curl_slist_free_all(dav_headers);
+
+       return lock_flags;
 }
 
-void get_delta(unsigned char *sha1, struct object *obj,
-              struct active_lock *lock)
+static struct object_list **process_blob(struct blob *blob,
+                                        struct object_list **p,
+                                        struct name_path *path,
+                                        const char *name)
 {
-       struct commit *commit;
-       struct commit_list *parents;
-       struct tree *tree;
+       struct object *obj = &blob->object;
+
+       obj->flags |= LOCAL;
+
+       if (obj->flags & (UNINTERESTING | SEEN))
+               return p;
+
+       obj->flags |= SEEN;
+       return add_object(obj, p, path, name);
+}
+
+static struct object_list **process_tree(struct tree *tree,
+                                        struct object_list **p,
+                                        struct name_path *path,
+                                        const char *name)
+{
+       struct object *obj = &tree->object;
        struct tree_entry_list *entry;
+       struct name_path me;
+
+       obj->flags |= LOCAL;
+
+       if (obj->flags & (UNINTERESTING | SEEN))
+               return p;
+       if (parse_tree(tree) < 0)
+               die("bad tree object %s", sha1_to_hex(obj->sha1));
+
+       obj->flags |= SEEN;
+       p = add_object(obj, p, NULL, name);
+       me.up = path;
+       me.elem = name;
+       me.elem_len = strlen(name);
+       entry = tree->entries;
+       tree->entries = NULL;
+       while (entry) {
+               struct tree_entry_list *next = entry->next;
+               if (entry->directory)
+                       p = process_tree(entry->item.tree, p, &me, entry->name);
+               else
+                       p = process_blob(entry->item.blob, p, &me, entry->name);
+               free(entry);
+               entry = next;
+       }
+       return p;
+}
 
-       if (sha1 && !memcmp(sha1, obj->sha1, 20))
-               return;
+static int get_delta(struct rev_info *revs, struct remote_lock *lock)
+{
+       struct commit *commit;
+       struct object_list **p = &objects, *pending;
+       int count = 0;
+
+       while ((commit = get_revision(revs)) != NULL) {
+               p = process_tree(commit->tree, p, NULL, "");
+               commit->object.flags |= LOCAL;
+               if (!(commit->object.flags & UNINTERESTING))
+                       count += add_send_request(&commit->object, lock);
+       }
 
-       if (aborted)
-               return;
+       for (pending = revs->pending_objects; pending; pending = pending->next) {
+               struct object *obj = pending->item;
+               const char *name = pending->name;
 
-       if (obj->type == commit_type) {
-               if (push_verbosely)
-                       fprintf(stderr, "walk %s\n", sha1_to_hex(obj->sha1));
-               add_request(obj->sha1, lock);
-               commit = (struct commit *)obj;
-               if (parse_commit(commit)) {
-                       fprintf(stderr, "Error parsing commit %s\n",
-                               sha1_to_hex(obj->sha1));
-                       aborted = 1;
-                       return;
+               if (obj->flags & (UNINTERESTING | SEEN))
+                       continue;
+               if (obj->type == tag_type) {
+                       obj->flags |= SEEN;
+                       p = add_object(obj, p, NULL, name);
+                       continue;
                }
-               parents = commit->parents;
-               for (; parents; parents = parents->next)
-                       if (sha1 == NULL ||
-                           memcmp(sha1, parents->item->object.sha1, 20))
-                               get_delta(sha1, &parents->item->object,
-                                         lock);
-               get_delta(sha1, &commit->tree->object, lock);
-       } else if (obj->type == tree_type) {
-               if (push_verbosely)
-                       fprintf(stderr, "walk %s\n", sha1_to_hex(obj->sha1));
-               add_request(obj->sha1, lock);
-               tree = (struct tree *)obj;
-               if (parse_tree(tree)) {
-                       fprintf(stderr, "Error parsing tree %s\n",
-                               sha1_to_hex(obj->sha1));
-                       aborted = 1;
-                       return;
+               if (obj->type == tree_type) {
+                       p = process_tree((struct tree *)obj, p, NULL, name);
+                       continue;
                }
-               entry = tree->entries;
-               tree->entries = NULL;
-               while (entry) {
-                       struct tree_entry_list *next = entry->next;
-                       get_delta(sha1, entry->item.any, lock);
-                       free(entry->name);
-                       free(entry);
-                       entry = next;
+               if (obj->type == blob_type) {
+                       p = process_blob((struct blob *)obj, p, NULL, name);
+                       continue;
                }
-       } else if (obj->type == blob_type || obj->type == tag_type) {
-               add_request(obj->sha1, lock);
+               die("unknown pending object %s (%s)", sha1_to_hex(obj->sha1), name);
        }
+
+       while (objects) {
+               if (!(objects->item->flags & UNINTERESTING))
+                       count += add_send_request(objects->item, lock);
+               objects = objects->next;
+       }
+
+       return count;
 }
 
-int update_remote(char *remote_path, unsigned char *sha1,
-                 struct active_lock *lock)
+static int update_remote(unsigned char *sha1, struct remote_lock *lock)
 {
        struct active_request_slot *slot;
-       char *url;
+       struct slot_results results;
        char *out_data;
        char *if_header;
        struct buffer out_buffer;
        struct curl_slist *dav_headers = NULL;
        int i;
 
-       url = xmalloc(strlen(remote->url) + strlen(remote_path) + 1);
-       sprintf(url, "%s%s", remote->url, remote_path);
-
        if_header = xmalloc(strlen(lock->token) + 25);
        sprintf(if_header, "If: (<opaquelocktoken:%s>)", lock->token);
        dav_headers = curl_slist_append(dav_headers, if_header);
@@ -1452,6 +1810,7 @@ int update_remote(char *remote_path, unsigned char *sha1,
        out_buffer.buffer = out_data;
 
        slot = get_active_slot();
+       slot->results = &results;
        curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer);
        curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.size);
        curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
@@ -1460,24 +1819,22 @@ int update_remote(char *remote_path, unsigned char *sha1,
        curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
        curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1);
        curl_easy_setopt(slot->curl, CURLOPT_PUT, 1);
-       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, lock->url);
 
        if (start_active_slot(slot)) {
                run_active_slot(slot);
                free(out_data);
                free(if_header);
-               free(url);
-               if (slot->curl_result != CURLE_OK) {
+               if (results.curl_result != CURLE_OK) {
                        fprintf(stderr,
                                "PUT error: curl result=%d, HTTP code=%ld\n",
-                               slot->curl_result, slot->http_code);
+                               results.curl_result, results.http_code);
                        /* We should attempt recovery? */
                        return 0;
                }
        } else {
                free(out_data);
                free(if_header);
-               free(url);
                fprintf(stderr, "Unable to start PUT request\n");
                return 0;
        }
@@ -1485,41 +1842,489 @@ int update_remote(char *remote_path, unsigned char *sha1,
        return 1;
 }
 
-int main(int argc, char **argv)
+static struct ref *local_refs, **local_tail;
+static struct ref *remote_refs, **remote_tail;
+
+static int one_local_ref(const char *refname, const unsigned char *sha1)
+{
+       struct ref *ref;
+       int len = strlen(refname) + 1;
+       ref = xcalloc(1, sizeof(*ref) + len);
+       memcpy(ref->new_sha1, sha1, 20);
+       memcpy(ref->name, refname, len);
+       *local_tail = ref;
+       local_tail = &ref->next;
+       return 0;
+}
+
+static void one_remote_ref(char *refname)
+{
+       struct ref *ref;
+       unsigned char remote_sha1[20];
+       struct object *obj;
+       int len = strlen(refname) + 1;
+
+       if (fetch_ref(refname, remote_sha1) != 0) {
+               fprintf(stderr,
+                       "Unable to fetch ref %s from %s\n",
+                       refname, remote->url);
+               return;
+       }
+
+       /*
+        * Fetch a copy of the object if it doesn't exist locally - it
+        * may be required for updating server info later.
+        */
+       if (remote->can_update_info_refs && !has_sha1_file(remote_sha1)) {
+               obj = lookup_unknown_object(remote_sha1);
+               if (obj) {
+                       fprintf(stderr, "  fetch %s for %s\n",
+                               sha1_to_hex(remote_sha1), refname);
+                       add_fetch_request(obj);
+               }
+       }
+
+       ref = xcalloc(1, sizeof(*ref) + len);
+       memcpy(ref->old_sha1, remote_sha1, 20);
+       memcpy(ref->name, refname, len);
+       *remote_tail = ref;
+       remote_tail = &ref->next;
+}
+
+static void get_local_heads(void)
+{
+       local_tail = &local_refs;
+       for_each_ref(one_local_ref);
+}
+
+static void get_dav_remote_heads(void)
+{
+       remote_tail = &remote_refs;
+       remote_ls("refs/", (PROCESS_FILES | PROCESS_DIRS | RECURSIVE), process_ls_ref, NULL);
+}
+
+static int is_zero_sha1(const unsigned char *sha1)
+{
+       int i;
+
+       for (i = 0; i < 20; i++) {
+               if (*sha1++)
+                       return 0;
+       }
+       return 1;
+}
+
+static void unmark_and_free(struct commit_list *list, unsigned int mark)
+{
+       while (list) {
+               struct commit_list *temp = list;
+               temp->item->object.flags &= ~mark;
+               list = temp->next;
+               free(temp);
+       }
+}
+
+static int ref_newer(const unsigned char *new_sha1,
+                    const unsigned char *old_sha1)
+{
+       struct object *o;
+       struct commit *old, *new;
+       struct commit_list *list, *used;
+       int found = 0;
+
+       /* Both new and old must be commit-ish and new is descendant of
+        * old.  Otherwise we require --force.
+        */
+       o = deref_tag(parse_object(old_sha1), NULL, 0);
+       if (!o || o->type != commit_type)
+               return 0;
+       old = (struct commit *) o;
+
+       o = deref_tag(parse_object(new_sha1), NULL, 0);
+       if (!o || o->type != commit_type)
+               return 0;
+       new = (struct commit *) o;
+
+       if (parse_commit(new) < 0)
+               return 0;
+
+       used = list = NULL;
+       commit_list_insert(new, &list);
+       while (list) {
+               new = pop_most_recent_commit(&list, TMP_MARK);
+               commit_list_insert(new, &used);
+               if (new == old) {
+                       found = 1;
+                       break;
+               }
+       }
+       unmark_and_free(list, TMP_MARK);
+       unmark_and_free(used, TMP_MARK);
+       return found;
+}
+
+static void mark_edge_parents_uninteresting(struct commit *commit)
+{
+       struct commit_list *parents;
+
+       for (parents = commit->parents; parents; parents = parents->next) {
+               struct commit *parent = parents->item;
+               if (!(parent->object.flags & UNINTERESTING))
+                       continue;
+               mark_tree_uninteresting(parent->tree);
+       }
+}
+
+static void mark_edges_uninteresting(struct commit_list *list)
+{
+       for ( ; list; list = list->next) {
+               struct commit *commit = list->item;
+
+               if (commit->object.flags & UNINTERESTING) {
+                       mark_tree_uninteresting(commit->tree);
+                       continue;
+               }
+               mark_edge_parents_uninteresting(commit);
+       }
+}
+
+static void add_remote_info_ref(struct remote_ls_ctx *ls)
+{
+       struct buffer *buf = (struct buffer *)ls->userData;
+       unsigned char remote_sha1[20];
+       struct object *o;
+       int len;
+       char *ref_info;
+
+       if (fetch_ref(ls->dentry_name, remote_sha1) != 0) {
+               fprintf(stderr,
+                       "Unable to fetch ref %s from %s\n",
+                       ls->dentry_name, remote->url);
+               aborted = 1;
+               return;
+       }
+
+       o = parse_object(remote_sha1);
+       if (!o) {
+               fprintf(stderr,
+                       "Unable to parse object %s for remote ref %s\n",
+                       sha1_to_hex(remote_sha1), ls->dentry_name);
+               aborted = 1;
+               return;
+       }
+
+       len = strlen(ls->dentry_name) + 42;
+       ref_info = xcalloc(len + 1, 1);
+       sprintf(ref_info, "%s   %s\n",
+               sha1_to_hex(remote_sha1), ls->dentry_name);
+       fwrite_buffer(ref_info, 1, len, buf);
+       free(ref_info);
+
+       if (o->type == tag_type) {
+               o = deref_tag(o, ls->dentry_name, 0);
+               if (o) {
+                       len = strlen(ls->dentry_name) + 45;
+                       ref_info = xcalloc(len + 1, 1);
+                       sprintf(ref_info, "%s   %s^{}\n",
+                               sha1_to_hex(o->sha1), ls->dentry_name);
+                       fwrite_buffer(ref_info, 1, len, buf);
+                       free(ref_info);
+               }
+       }
+}
+
+static void update_remote_info_refs(struct remote_lock *lock)
 {
+       struct buffer buffer;
        struct active_request_slot *slot;
-       struct active_request_slot *next_slot;
+       struct slot_results results;
+       char *if_header;
+       struct curl_slist *dav_headers = NULL;
+
+       buffer.buffer = xcalloc(1, 4096);
+       buffer.size = 4096;
+       buffer.posn = 0;
+       remote_ls("refs/", (PROCESS_FILES | RECURSIVE),
+                 add_remote_info_ref, &buffer);
+       if (!aborted) {
+               if_header = xmalloc(strlen(lock->token) + 25);
+               sprintf(if_header, "If: (<opaquelocktoken:%s>)", lock->token);
+               dav_headers = curl_slist_append(dav_headers, if_header);
+
+               slot = get_active_slot();
+               slot->results = &results;
+               curl_easy_setopt(slot->curl, CURLOPT_INFILE, &buffer);
+               curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, buffer.posn);
+               curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
+               curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
+               curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PUT);
+               curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
+               curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1);
+               curl_easy_setopt(slot->curl, CURLOPT_PUT, 1);
+               curl_easy_setopt(slot->curl, CURLOPT_URL, lock->url);
+
+               buffer.posn = 0;
+
+               if (start_active_slot(slot)) {
+                       run_active_slot(slot);
+                       if (results.curl_result != CURLE_OK) {
+                               fprintf(stderr,
+                                       "PUT error: curl result=%d, HTTP code=%ld\n",
+                                       results.curl_result, results.http_code);
+                       }
+               }
+               free(if_header);
+       }
+       free(buffer.buffer);
+}
+
+static int remote_exists(const char *path)
+{
+       char *url = xmalloc(strlen(remote->url) + strlen(path) + 1);
+       struct active_request_slot *slot;
+       struct slot_results results;
+
+       sprintf(url, "%s%s", remote->url, path);
+
+        slot = get_active_slot();
+       slot->results = &results;
+        curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+        curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 1);
+
+        if (start_active_slot(slot)) {
+               run_active_slot(slot);
+               if (results.http_code == 404)
+                       return 0;
+               else if (results.curl_result == CURLE_OK)
+                       return 1;
+               else
+                       fprintf(stderr, "HEAD HTTP error %ld\n", results.http_code);
+       } else {
+               fprintf(stderr, "Unable to start HEAD request\n");
+       }
+
+       return -1;
+}
+
+static void fetch_symref(char *path, char **symref, unsigned char *sha1)
+{
+       char *url;
+       struct buffer buffer;
+       struct active_request_slot *slot;
+       struct slot_results results;
+
+       url = xmalloc(strlen(remote->url) + strlen(path) + 1);
+       sprintf(url, "%s%s", remote->url, path);
+
+       buffer.size = 4096;
+       buffer.posn = 0;
+       buffer.buffer = xmalloc(buffer.size);
+
+       slot = get_active_slot();
+       slot->results = &results;
+       curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+       if (start_active_slot(slot)) {
+               run_active_slot(slot);
+               if (results.curl_result != CURLE_OK) {
+                       die("Couldn't get %s for remote symref\n%s",
+                           url, curl_errorstr);
+               }
+       } else {
+               die("Unable to start remote symref request");
+       }
+       free(url);
+
+       if (*symref != NULL)
+               free(*symref);
+       *symref = NULL;
+       memset(sha1, 0, 20);
+
+       if (buffer.posn == 0)
+               return;
+
+       /* If it's a symref, set the refname; otherwise try for a sha1 */
+       if (!strncmp((char *)buffer.buffer, "ref: ", 5)) {
+               *symref = xcalloc(buffer.posn - 5, 1);
+               strncpy(*symref, (char *)buffer.buffer + 5, buffer.posn - 6);
+       } else {
+               get_sha1_hex(buffer.buffer, sha1);
+       }
+
+       free(buffer.buffer);
+}
+
+static int verify_merge_base(unsigned char *head_sha1, unsigned char *branch_sha1)
+{
+       int pipe_fd[2];
+       pid_t merge_base_pid;
+       char line[PATH_MAX + 20];
+       unsigned char merge_sha1[20];
+       int verified = 0;
+
+       if (pipe(pipe_fd) < 0)
+               die("Verify merge base: pipe failed");
+
+       merge_base_pid = fork();
+       if (!merge_base_pid) {
+               static const char *args[] = {
+                       "merge-base",
+                       "-a",
+                       NULL,
+                       NULL,
+                       NULL
+               };
+               args[2] = strdup(sha1_to_hex(head_sha1));
+               args[3] = sha1_to_hex(branch_sha1);
+
+               dup2(pipe_fd[1], 1);
+               close(pipe_fd[0]);
+               close(pipe_fd[1]);
+               execv_git_cmd(args);
+               die("merge-base setup failed");
+       }
+       if (merge_base_pid < 0)
+               die("merge-base fork failed");
+
+       dup2(pipe_fd[0], 0);
+       close(pipe_fd[0]);
+       close(pipe_fd[1]);
+       while (fgets(line, sizeof(line), stdin) != NULL) {
+               if (get_sha1_hex(line, merge_sha1))
+                       die("expected sha1, got garbage:\n %s", line);
+               if (!memcmp(branch_sha1, merge_sha1, 20)) {
+                       verified = 1;
+                       break;
+               }
+       }
+
+       return verified;
+}
+
+static int delete_remote_branch(char *pattern, int force)
+{
+       struct ref *refs = remote_refs;
+       struct ref *remote_ref = NULL;
+       unsigned char head_sha1[20];
+       char *symref = NULL;
+       int match;
+       int patlen = strlen(pattern);
+       int i;
+       struct active_request_slot *slot;
+       struct slot_results results;
+       char *url;
+
+       /* Find the remote branch(es) matching the specified branch name */
+       for (match = 0; refs; refs = refs->next) {
+               char *name = refs->name;
+               int namelen = strlen(name);
+               if (namelen < patlen ||
+                   memcmp(name + namelen - patlen, pattern, patlen))
+                       continue;
+               if (namelen != patlen && name[namelen - patlen - 1] != '/')
+                       continue;
+               match++;
+               remote_ref = refs;
+       }
+       if (match == 0)
+               return error("No remote branch matches %s", pattern);
+       if (match != 1)
+               return error("More than one remote branch matches %s",
+                            pattern);
+
+       /*
+        * Remote HEAD must be a symref (not exactly foolproof; a remote
+        * symlink to a symref will look like a symref)
+        */
+       fetch_symref("HEAD", &symref, head_sha1);
+       if (!symref)
+               return error("Remote HEAD is not a symref");
+
+       /* Remote branch must not be the remote HEAD */
+       for (i=0; symref && i<MAXDEPTH; i++) {
+               if (!strcmp(remote_ref->name, symref))
+                       return error("Remote branch %s is the current HEAD",
+                                    remote_ref->name);
+               fetch_symref(symref, &symref, head_sha1);
+       }
+
+       /* Run extra sanity checks if delete is not forced */
+       if (!force) {
+               /* Remote HEAD must resolve to a known object */
+               if (symref)
+                       return error("Remote HEAD symrefs too deep");
+               if (is_zero_sha1(head_sha1))
+                       return error("Unable to resolve remote HEAD");
+               if (!has_sha1_file(head_sha1))
+                       return error("Remote HEAD resolves to object %s\nwhich does not exist locally, perhaps you need to fetch?", sha1_to_hex(head_sha1));
+
+               /* Remote branch must resolve to a known object */
+               if (is_zero_sha1(remote_ref->old_sha1))
+                       return error("Unable to resolve remote branch %s",
+                                    remote_ref->name);
+               if (!has_sha1_file(remote_ref->old_sha1))
+                       return error("Remote branch %s resolves to object %s\nwhich does not exist locally, perhaps you need to fetch?", remote_ref->name, sha1_to_hex(remote_ref->old_sha1));
+
+               /* Remote branch must be an ancestor of remote HEAD */
+               if (!verify_merge_base(head_sha1, remote_ref->old_sha1)) {
+                       return error("The branch '%s' is not a strict subset of your current HEAD.\nIf you are sure you want to delete it, run:\n\t'git http-push -D %s %s'", remote_ref->name, remote->url, pattern);
+               }
+       }
+
+       /* Send delete request */
+       fprintf(stderr, "Removing remote branch '%s'\n", remote_ref->name);
+       url = xmalloc(strlen(remote->url) + strlen(remote_ref->name) + 1);
+       sprintf(url, "%s%s", remote->url, remote_ref->name);
+       slot = get_active_slot();
+       slot->results = &results;
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+       curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_DELETE);
+       if (start_active_slot(slot)) {
+               run_active_slot(slot);
+               free(url);
+               if (results.curl_result != CURLE_OK)
+                       return error("DELETE request failed (%d/%ld)\n",
+                                    results.curl_result, results.http_code);
+       } else {
+               free(url);
+               return error("Unable to start DELETE request");
+       }
+
+       return 0;
+}
+
+int main(int argc, char **argv)
+{
        struct transfer_request *request;
        struct transfer_request *next_request;
        int nr_refspec = 0;
        char **refspec = NULL;
-       int do_remote_update;
-       int new_branch;
-       int force_this;
-       char *local_ref;
-       unsigned char local_sha1[20];
-       struct object *local_object = NULL;
-       char *remote_ref = NULL;
-       unsigned char remote_sha1[20];
-       struct active_lock *remote_lock;
-       char *remote_path = NULL;
-       char *low_speed_limit;
-       char *low_speed_time;
+       struct remote_lock *ref_lock = NULL;
+       struct remote_lock *info_ref_lock = NULL;
+       struct rev_info revs;
+       int delete_branch = 0;
+       int force_delete = 0;
+       int objects_to_send;
        int rc = 0;
        int i;
+       int new_refs;
+       struct ref *ref;
 
+       setup_git_directory();
        setup_ident();
 
-       remote = xmalloc(sizeof(*remote));
-       remote->url = NULL;
-       remote->packs = NULL;
+       remote = xcalloc(sizeof(*remote), 1);
 
        argv++;
        for (i = 1; i < argc; i++, argv++) {
                char *arg = *argv;
 
                if (*arg == '-') {
-                       if (!strcmp(arg, "--complete")) {
+                       if (!strcmp(arg, "--all")) {
                                push_all = 1;
                                continue;
                        }
@@ -1531,10 +2336,24 @@ int main(int argc, char **argv)
                                push_verbosely = 1;
                                continue;
                        }
-                       usage(http_push_usage);
+                       if (!strcmp(arg, "-d")) {
+                               delete_branch = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "-D")) {
+                               delete_branch = 1;
+                               force_delete = 1;
+                               continue;
+                       }
                }
                if (!remote->url) {
+                       char *path = strstr(arg, "//");
                        remote->url = arg;
+                       if (path) {
+                               path = index(path+2, '/');
+                               if (path)
+                                       remote->path_len = strlen(path);
+                       }
                        continue;
                }
                refspec = argv;
@@ -1542,50 +2361,15 @@ int main(int argc, char **argv)
                break;
        }
 
-       curl_global_init(CURL_GLOBAL_ALL);
+       if (!remote->url)
+               usage(http_push_usage);
 
-#ifdef USE_CURL_MULTI
-       {
-               char *http_max_requests = getenv("GIT_HTTP_MAX_REQUESTS");
-               if (http_max_requests != NULL)
-                       max_requests = atoi(http_max_requests);
-       }
+       if (delete_branch && nr_refspec != 1)
+               die("You must specify only one branch name when deleting a remote branch");
 
-       curlm = curl_multi_init();
-       if (curlm == NULL) {
-               fprintf(stderr, "Error creating curl multi handle.\n");
-               return 1;
-       }
-#endif
-
-       if (getenv("GIT_SSL_NO_VERIFY"))
-               curl_ssl_verify = 0;
-
-       ssl_cert = getenv("GIT_SSL_CERT");
-#if LIBCURL_VERSION_NUM >= 0x070902
-       ssl_key = getenv("GIT_SSL_KEY");
-#endif
-#if LIBCURL_VERSION_NUM >= 0x070908
-       ssl_capath = getenv("GIT_SSL_CAPATH");
-#endif
-       ssl_cainfo = getenv("GIT_SSL_CAINFO");
-
-       low_speed_limit = getenv("GIT_HTTP_LOW_SPEED_LIMIT");
-       if (low_speed_limit != NULL)
-               curl_low_speed_limit = strtol(low_speed_limit, NULL, 10);
-       low_speed_time = getenv("GIT_HTTP_LOW_SPEED_TIME");
-       if (low_speed_time != NULL)
-               curl_low_speed_time = strtol(low_speed_time, NULL, 10);
-
-       git_config(http_options);
+       memset(remote_dir_exists, -1, 256);
 
-       if (curl_ssl_verify == -1)
-               curl_ssl_verify = 1;
-
-#ifdef USE_CURL_MULTI
-       if (max_requests < 1)
-               max_requests = DEFAULT_MAX_REQUESTS;
-#endif
+       http_init();
 
        no_pragma_header = curl_slist_append(no_pragma_header, "Pragma:");
        default_headers = curl_slist_append(default_headers, "Range:");
@@ -1594,137 +2378,172 @@ int main(int argc, char **argv)
        default_headers = curl_slist_append(default_headers,
                                            "Pragma: no-cache");
 
-#ifndef NO_CURL_EASY_DUPHANDLE
-       curl_default = get_curl_handle();
-#endif
-
        /* Verify DAV compliance/lock support */
-       if (check_locking() != 0) {
+       if (!locking_available()) {
                fprintf(stderr, "Error: no DAV locking support on remote repo %s\n", remote->url);
                rc = 1;
                goto cleanup;
        }
 
-       /* Process each refspec */
-       for (i = 0; i < nr_refspec; i++) {
-               char *ep;
-               force_this = 0;
-               do_remote_update = 0;
-               new_branch = 0;
-               local_ref = refspec[i];
-               if (*local_ref == '+') {
-                       force_this = 1;
-                       local_ref++;
+       /* Check whether the remote has server info files */
+       remote->can_update_info_refs = 0;
+       remote->has_info_refs = remote_exists("info/refs");
+       remote->has_info_packs = remote_exists("objects/info/packs");
+       if (remote->has_info_refs) {
+               info_ref_lock = lock_remote("info/refs", LOCK_TIME);
+               if (info_ref_lock)
+                       remote->can_update_info_refs = 1;
+       }
+       if (remote->has_info_packs)
+               fetch_indices();
+
+       /* Get a list of all local and remote heads to validate refspecs */
+       get_local_heads();
+       fprintf(stderr, "Fetching remote heads...\n");
+       get_dav_remote_heads();
+
+       /* Remove a remote branch if -d or -D was specified */
+       if (delete_branch) {
+               if (delete_remote_branch(refspec[0], force_delete) == -1)
+                       fprintf(stderr, "Unable to delete remote branch %s\n",
+                               refspec[0]);
+               goto cleanup;
+       }
+
+       /* match them up */
+       if (!remote_tail)
+               remote_tail = &remote_refs;
+       if (match_refs(local_refs, remote_refs, &remote_tail,
+                      nr_refspec, refspec, push_all))
+               return -1;
+       if (!remote_refs) {
+               fprintf(stderr, "No refs in common and none specified; doing nothing.\n");
+               return 0;
+       }
+
+       new_refs = 0;
+       for (ref = remote_refs; ref; ref = ref->next) {
+               char old_hex[60], *new_hex;
+               const char *commit_argv[4];
+               int commit_argc;
+               char *new_sha1_hex, *old_sha1_hex;
+
+               if (!ref->peer_ref)
+                       continue;
+               if (!memcmp(ref->old_sha1, ref->peer_ref->new_sha1, 20)) {
+                       if (push_verbosely || 1)
+                               fprintf(stderr, "'%s': up-to-date\n", ref->name);
+                       continue;
                }
-               ep = strchr(local_ref, ':');
-               if (ep) {
-                       remote_ref = ep + 1;
-                       *ep = 0;
+
+               if (!force_all &&
+                   !is_zero_sha1(ref->old_sha1) &&
+                   !ref->force) {
+                       if (!has_sha1_file(ref->old_sha1) ||
+                           !ref_newer(ref->peer_ref->new_sha1,
+                                      ref->old_sha1)) {
+                               /* We do not have the remote ref, or
+                                * we know that the remote ref is not
+                                * an ancestor of what we are trying to
+                                * push.  Either way this can be losing
+                                * commits at the remote end and likely
+                                * we were not up to date to begin with.
+                                */
+                               error("remote '%s' is not a strict "
+                                     "subset of local ref '%s'. "
+                                     "maybe you are not up-to-date and "
+                                     "need to pull first?",
+                                     ref->name,
+                                     ref->peer_ref->name);
+                               rc = -2;
+                               continue;
+                       }
                }
-               else
-                       remote_ref = local_ref;
+               memcpy(ref->new_sha1, ref->peer_ref->new_sha1, 20);
+               if (is_zero_sha1(ref->new_sha1)) {
+                       error("cannot happen anymore");
+                       rc = -3;
+                       continue;
+               }
+               new_refs++;
+               strcpy(old_hex, sha1_to_hex(ref->old_sha1));
+               new_hex = sha1_to_hex(ref->new_sha1);
+
+               fprintf(stderr, "updating '%s'", ref->name);
+               if (strcmp(ref->name, ref->peer_ref->name))
+                       fprintf(stderr, " using '%s'", ref->peer_ref->name);
+               fprintf(stderr, "\n  from %s\n  to   %s\n", old_hex, new_hex);
+
 
                /* Lock remote branch ref */
-               if (remote_path)
-                       free(remote_path);
-               remote_path = xmalloc(strlen(remote_ref) + 12);
-               sprintf(remote_path, "refs/heads/%s", remote_ref);
-               remote_lock = lock_remote(remote_path, 3600);
-               if (remote_lock == NULL) {
+               ref_lock = lock_remote(ref->name, LOCK_TIME);
+               if (ref_lock == NULL) {
                        fprintf(stderr, "Unable to lock remote branch %s\n",
-                               remote_ref);
+                               ref->name);
                        rc = 1;
                        continue;
                }
 
-               /* Resolve local and remote refs */
-               if (fetch_ref(remote_ref, remote_sha1) != 0) {
-                       fprintf(stderr,
-                               "Remote branch %s does not exist on %s\n",
-                               remote_ref, remote->url);
-                       new_branch = 1;
-               }
-               if (get_sha1(local_ref, local_sha1) != 0) {
-                       fprintf(stderr, "Error resolving local branch %s\n",
-                               local_ref);
-                       rc = 1;
-                       goto unlock;
+               /* Set up revision info for this refspec */
+               commit_argc = 3;
+               new_sha1_hex = strdup(sha1_to_hex(ref->new_sha1));
+               old_sha1_hex = NULL;
+               commit_argv[1] = "--objects";
+               commit_argv[2] = new_sha1_hex;
+               if (!push_all && !is_zero_sha1(ref->old_sha1)) {
+                       old_sha1_hex = xmalloc(42);
+                       sprintf(old_sha1_hex, "^%s",
+                               sha1_to_hex(ref->old_sha1));
+                       commit_argv[3] = old_sha1_hex;
+                       commit_argc++;
                }
-       
-               /* Find relationship between local and remote */
-               local_object = parse_object(local_sha1);
-               if (!local_object) {
-                       fprintf(stderr, "Unable to parse local object %s\n",
-                               sha1_to_hex(local_sha1));
-                       rc = 1;
-                       goto unlock;
-               } else if (new_branch) {
-                       do_remote_update = 1;
-               } else {
-                       if (!memcmp(local_sha1, remote_sha1, 20)) {
-                               fprintf(stderr,
-                                       "* %s: same as branch '%s' of %s\n",
-                                       local_ref, remote_ref, remote->url);
-                       } else if (is_ancestor(remote_sha1,
-                                              (struct commit *)local_object)) {
-                               fprintf(stderr,
-                                       "Remote %s will fast-forward to local %s\n",
-                                       remote_ref, local_ref);
-                               do_remote_update = 1;
-                       } else if (force_all || force_this) {
-                               fprintf(stderr,
-                                       "* %s on %s does not fast forward to local branch '%s', overwriting\n",
-                                       remote_ref, remote->url, local_ref);
-                               do_remote_update = 1;
-                       } else {
-                               fprintf(stderr,
-                                       "* %s on %s does not fast forward to local branch '%s'\n",
-                                       remote_ref, remote->url, local_ref);
-                               rc = 1;
-                               goto unlock;
-                       }
+               setup_revisions(commit_argc, commit_argv, &revs, NULL);
+               free(new_sha1_hex);
+               if (old_sha1_hex) {
+                       free(old_sha1_hex);
+                       commit_argv[1] = NULL;
                }
 
-               /* Generate and check list of required objects */
+               /* Generate a list of objects that need to be pushed */
                pushing = 0;
-               if (do_remote_update || push_all)
-                       fetch_indices();
-               get_delta(push_all ? NULL : remote_sha1,
-                         local_object, remote_lock);
-               process_waiting_requests();
+               prepare_revision_walk(&revs);
+               mark_edges_uninteresting(revs.commits);
+               objects_to_send = get_delta(&revs, ref_lock);
+               finish_all_active_slots();
 
                /* Push missing objects to remote, this would be a
                   convenient time to pack them first if appropriate. */
                pushing = 1;
-               process_request_queue();
-               process_waiting_requests();
+               if (objects_to_send)
+                       fprintf(stderr, "    sending %d objects\n",
+                               objects_to_send);
+               fill_active_slots();
+               finish_all_active_slots();
 
                /* Update the remote branch if all went well */
-               if (do_remote_update) {
-                       if (!aborted && update_remote(remote_path,
-                                                     local_sha1,
-                                                     remote_lock)) {
-                               fprintf(stderr, "%s remote branch %s\n",
-                                       new_branch ? "Created" : "Updated",
-                                       remote_ref);
-                       } else {
-                               fprintf(stderr,
-                                       "Unable to %s remote branch %s\n",
-                                       new_branch ? "create" : "update",
-                                       remote_ref);
-                               rc = 1;
-                               goto unlock;
-                       }
+               if (aborted || !update_remote(ref->new_sha1, ref_lock)) {
+                       rc = 1;
+                       goto unlock;
                }
 
        unlock:
-               unlock_remote(remote_path, remote_lock);
-               free(remote_path);
-               if (remote_lock->owner != NULL)
-                       free(remote_lock->owner);
-               free(remote_lock->token);
-               free(remote_lock);
+               if (!rc)
+                       fprintf(stderr, "    done\n");
+               unlock_remote(ref_lock);
+               check_locks();
+       }
+
+       /* Update remote server info if appropriate */
+       if (remote->has_info_refs && new_refs) {
+               if (info_ref_lock && remote->can_update_info_refs) {
+                       fprintf(stderr, "Updating remote server info\n");
+                       update_remote_info_refs(info_ref_lock);
+               } else {
+                       fprintf(stderr, "Unable to update server info\n");
+               }
        }
+       if (info_ref_lock)
+               unlock_remote(info_ref_lock);
 
  cleanup:
        free(remote);
@@ -1732,14 +2551,7 @@ int main(int argc, char **argv)
        curl_slist_free_all(no_pragma_header);
        curl_slist_free_all(default_headers);
 
-       slot = active_queue_head;
-       while (slot != NULL) {
-               next_slot = slot->next;
-               if (slot->curl != NULL)
-                       curl_easy_cleanup(slot->curl);
-               free(slot);
-               slot = next_slot;
-       }
+       http_cleanup();
 
        request = request_queue_head;
        while (request != NULL) {
@@ -1748,12 +2560,5 @@ int main(int argc, char **argv)
                request = next_request;
        }
 
-#ifndef NO_CURL_EASY_DUPHANDLE
-       curl_easy_cleanup(curl_default);
-#endif
-#ifdef USE_CURL_MULTI
-       curl_multi_cleanup(curlm);
-#endif
-       curl_global_cleanup();
        return rc;
 }