for-each-reflog: not having $GIT_DIR/logs directory is not an error.
[gitweb.git] / http-push.c
index f24cfd2b46a65bde53610eecb6998fa615b62363..b128c0146c6f1d8ca69d802d15d6bc778e5780a6 100644 (file)
@@ -6,12 +6,14 @@
 #include "blob.h"
 #include "http.h"
 #include "refs.h"
+#include "diff.h"
 #include "revision.h"
+#include "exec_cmd.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";
 
 #ifndef XML_STATUS_OK
 enum XML_Status {
@@ -22,6 +24,7 @@ enum XML_Status {
 #define XML_STATUS_ERROR 0
 #endif
 
+#define PREV_BUF_SIZE 4096
 #define RANGE_HEADER_SIZE 30
 
 /* DAV methods */
@@ -31,6 +34,7 @@ enum XML_Status {
 #define DAV_PROPFIND "PROPFIND"
 #define DAV_PUT "PUT"
 #define DAV_UNLOCK "UNLOCK"
+#define DAV_DELETE "DELETE"
 
 /* DAV lock flags */
 #define DAV_PROP_LOCKWR (1u << 0)
@@ -56,36 +60,46 @@ enum XML_Status {
 #define LOCK_TIME 600
 #define LOCK_REFRESH 30
 
-/* bits #0-4 in revision.h */
+/* bits #0-15 in revision.h */
 
-#define LOCAL   (1u << 5)
-#define REMOTE  (1u << 6)
-#define PUSHING (1u << 7)
+#define LOCAL    (1u<<16)
+#define REMOTE   (1u<<17)
+#define FETCHING (1u<<18)
+#define PUSHING  (1u<<19)
 
-static int pushing = 0;
-static int aborted = 0;
-static char remote_dir_exists[256];
+/* We allow "recursive" symbolic refs. Only within reason, though */
+#define MAXDEPTH 5
+
+static int pushing;
+static int aborted;
+static signed char remote_dir_exists[256];
 
 static struct curl_slist *no_pragma_header;
 static struct curl_slist *default_headers;
 
-static int push_verbosely = 0;
-static int push_all = 0;
-static int force_all = 0;
+static int push_verbosely;
+static int push_all;
+static int force_all;
 
-static struct object_list *objects = NULL;
+static struct object_list *objects;
 
 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;
-static struct remote_lock *remote_locks = NULL;
+static struct repo *remote;
 
 enum transfer_state {
+       NEED_FETCH,
+       RUN_FETCH_LOOSE,
+       RUN_FETCH_PACKED,
        NEED_PUSH,
        RUN_MKCOL,
        RUN_PUT,
@@ -104,6 +118,8 @@ struct transfer_request
        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];
@@ -113,11 +129,12 @@ struct transfer_request
        z_stream stream;
        int zret;
        int rename;
+       void *userData;
        struct active_request_slot *slot;
        struct transfer_request *next;
 };
 
-static struct transfer_request *request_queue_head = NULL;
+static struct transfer_request *request_queue_head;
 
 struct xml_ctx
 {
@@ -135,7 +152,6 @@ struct remote_lock
        char *token;
        time_t start_time;
        long timeout;
-       int active;
        int refreshing;
        struct remote_lock *next;
 };
@@ -160,6 +176,7 @@ struct remote_ls_ctx
 };
 
 static void finish_request(struct transfer_request *request);
+static void release_request(struct transfer_request *request);
 
 static void process_response(void *callback_data)
 {
@@ -169,6 +186,170 @@ static void process_response(void *callback_data)
        finish_request(request);
 }
 
+#ifdef USE_CURL_MULTI
+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;
+       int posn = 0;
+       struct transfer_request *request = (struct transfer_request *)data;
+       do {
+               ssize_t retval = xwrite(request->local_fileno,
+                                      (char *) 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 void start_fetch_loose(struct transfer_request *request)
+{
+       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;
+
+       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);
+       }
+
+       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;
+       }
+
+       memset(&request->stream, 0, sizeof(request->stream));
+
+       inflateInit(&request->stream);
+
+       SHA1_Init(&request->c);
+
+       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);
+
+       /* If a previous temp file is present, process what was already
+          fetched. */
+       prevlocal = open(prevfile, O_RDONLY);
+       if (prevlocal != -1) {
+               do {
+                       prev_read = xread(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;
+                               }
+                       }
+               } 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 = 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);
+
+       /* 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);
+       }
+
+       /* 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 void start_mkcol(struct transfer_request *request)
 {
        char *hex = sha1_to_hex(request->obj->sha1);
@@ -202,6 +383,96 @@ static void start_mkcol(struct transfer_request *request)
                request->url = NULL;
        }
 }
+#endif
+
+static void start_fetch_packed(struct transfer_request *request)
+{
+       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;
+
+       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;
+       }
+
+       fprintf(stderr, "Fetching pack %s\n", sha1_to_hex(target->sha1));
+       fprintf(stderr, " which contains %s\n", sha1_to_hex(request->obj->sha1));
+
+       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));
+
+       /* 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;
+       }
+
+       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();
+       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 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_put(struct transfer_request *request)
 {
@@ -221,7 +492,7 @@ static void start_put(struct transfer_request *request)
 
        /* Set it up */
        memset(&stream, 0, sizeof(stream));
-       deflateInit(&stream, Z_BEST_COMPRESSION);
+       deflateInit(&stream, zlib_compression_level);
        size = deflateBound(&stream, len + hdrlen);
        request->buffer.buffer = xmalloc(size);
 
@@ -259,7 +530,7 @@ static void start_put(struct transfer_request *request)
        request->dest = xmalloc(strlen(request->url) + 14);
        sprintf(request->dest, "Destination: %s", request->url);
        posn += 38;
-       *(posn++) = '.';
+       *(posn++) = '_';
        strcpy(posn, request->lock->token);
 
        slot = get_active_slot();
@@ -311,64 +582,69 @@ static void start_move(struct transfer_request *request)
        }
 }
 
-static int refresh_lock(struct remote_lock *check_lock)
+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;
-       struct remote_lock *lock;
-       int time_remaining;
-       time_t current_time;
+       int rc = 0;
 
-       /* Refresh all active locks if they're close to expiring */
-       for (lock = remote_locks; lock; lock = lock->next) {
-               if (!lock->active)
-                       continue;
+       lock->refreshing = 1;
 
-               current_time = time(NULL);
-               time_remaining = lock->start_time + lock->timeout
-                       - current_time;
-               if (time_remaining > LOCK_REFRESH)
-                       continue;
+       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);
 
-               lock->refreshing = 1;
+       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_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);
+       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;
+               }
+       }
 
-               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);
+       lock->refreshing = 0;
+       curl_slist_free_all(dav_headers);
+       free(if_header);
 
-               if (start_active_slot(slot)) {
-                       run_active_slot(slot);
-                       if (results.curl_result != CURLE_OK) {
-                               fprintf(stderr, "Got HTTP error %ld\n", results.http_code);
-                               lock->active = 0;
-                       } else {
-                               lock->active = 1;
-                               lock->start_time = time(NULL);
+       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->refreshing = 0;
-               curl_slist_free_all(dav_headers);
-               free(if_header);
+               lock = lock->next;
        }
-
-       if (check_lock)
-               return check_lock->active;
-       else
-               return 0;
 }
 
 static void release_request(struct transfer_request *request)
@@ -384,6 +660,10 @@ static void release_request(struct transfer_request *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);
@@ -391,12 +671,16 @@ static void release_request(struct transfer_request *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 */
-       refresh_lock(request->lock);
+       check_locks();
 
        if (request->headers != NULL)
                curl_slist_free_all(request->headers);
@@ -431,9 +715,9 @@ static void finish_request(struct transfer_request *request)
                }
        } else if (request->state == RUN_MOVE) {
                if (request->curl_result == CURLE_OK) {
-                       fprintf(stderr, "    sent %s\n",
-                               sha1_to_hex(request->obj->sha1));
-                       request->state = COMPLETE;
+                       if (push_verbosely)
+                               fprintf(stderr, "    sent %s\n",
+                                       sha1_to_hex(request->obj->sha1));
                        request->obj->flags |= REMOTE;
                        release_request(request);
                } else {
@@ -443,12 +727,77 @@ static void finish_request(struct transfer_request *request)
                        request->state = ABORTED;
                        aborted = 1;
                }
+       } 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 (hashcmp(request->obj->sha1, request->real_sha1)) {
+                               unlink(request->tmpfile);
+                       } else {
+                               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 {
+                       off_t pack_size = ftell(request->local_stream);
+
+                       fclose(request->local_stream);
+                       request->local_stream = NULL;
+                       if (!move_temp_to_file(request->tmpfile,
+                                              request->filename)) {
+                               target = (struct packed_git *)request->userData;
+                               target->pack_size = pack_size;
+                               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;
+                       }
+               }
+               release_request(request);
        }
 }
 
+#ifdef USE_CURL_MULTI
 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;
 
@@ -456,7 +805,10 @@ void fill_active_slots(void)
                return;
 
        while (active_requests < max_requests && request != NULL) {
-               if (pushing && request->state == NEED_PUSH) {
+               next = request->next;
+               if (request->state == NEED_FETCH) {
+                       start_fetch_loose(request);
+               } else if (pushing && request->state == NEED_PUSH) {
                        if (remote_dir_exists[request->obj->sha1[0]] == 1) {
                                start_put(request);
                        } else {
@@ -464,7 +816,7 @@ void fill_active_slots(void)
                        }
                        curl_multi_perform(curlm, &num_transfers);
                }
-               request = request->next;
+               request = next;
        }
 
        while (slot != NULL) {
@@ -475,14 +827,51 @@ void fill_active_slots(void)
                slot = slot->next;
        }
 }
+#endif
 
 static void get_remote_object_list(unsigned char parent);
 
-static void add_request(struct object *obj, struct remote_lock *lock)
+static void add_fetch_request(struct object *obj)
+{
+       struct transfer_request *request;
+
+       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;
+
+#ifdef USE_CURL_MULTI
+       fill_active_slots();
+       step_active_slots();
+#endif
+}
+
+static int add_send_request(struct object *obj, struct remote_lock *lock)
 {
        struct transfer_request *request = request_queue_head;
        struct packed_git *target;
 
+       /* 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
@@ -490,11 +879,11 @@ static void add_request(struct object *obj, struct remote_lock *lock)
        if (remote_dir_exists[obj->sha1[0]] == -1)
                get_remote_object_list(obj->sha1[0]);
        if (obj->flags & (REMOTE | PUSHING))
-               return;
+               return 0;
        target = find_sha1_pack(obj->sha1, remote->packs);
        if (target) {
                obj->flags |= REMOTE;
-               return;
+               return 0;
        }
 
        obj->flags |= PUSHING;
@@ -503,12 +892,18 @@ static void add_request(struct object *obj, struct remote_lock *lock)
        request->url = NULL;
        request->lock = lock;
        request->headers = NULL;
+       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
        fill_active_slots();
        step_active_slots();
+#endif
+
+       return 1;
 }
 
 static int fetch_index(unsigned char *sha1)
@@ -526,8 +921,8 @@ static int fetch_index(unsigned char *sha1)
        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);
@@ -548,9 +943,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");
@@ -625,17 +1020,16 @@ static int fetch_indices(void)
        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;
@@ -686,13 +1080,14 @@ static int fetch_indices(void)
 
 static inline int needs_quote(int ch)
 {
-       switch (ch) {
-       case '/': case '-': case '.':
-       case 'A'...'Z': case 'a'...'z': case '0'...'9':
+       if (((ch >= 'A') && (ch <= 'Z'))
+                       || ((ch >= 'a') && (ch <= 'z'))
+                       || ((ch >= '0') && (ch <= '9'))
+                       || (ch == '/')
+                       || (ch == '-')
+                       || (ch == '.'))
                return 0;
-       default:
-               return 1;
-       }
+       return 1;
 }
 
 static inline int hex(int v)
@@ -780,7 +1175,7 @@ static void one_remote_object(const char *hex)
 
        obj->flags |= REMOTE;
        if (!object_list_contains(objects, obj))
-               add_object(obj, &objects, NULL, "");
+               object_list_insert(obj, &objects);
 }
 
 static void handle_lockprop_ctx(struct xml_ctx *ctx, int tag_closed)
@@ -829,7 +1224,7 @@ static void
 xml_start_tag(void *userData, const char *name, const char **atts)
 {
        struct xml_ctx *ctx = (struct xml_ctx *)userData;
-       const char *c = index(name, ':');
+       const char *c = strchr(name, ':');
        int new_len;
 
        if (c == NULL)
@@ -846,10 +1241,8 @@ xml_start_tag(void *userData, const char *name, const char **atts)
        strcat(ctx->name, ".");
        strcat(ctx->name, c);
 
-       if (ctx->cdata) {
-               free(ctx->cdata);
-               ctx->cdata = NULL;
-       }
+       free(ctx->cdata);
+       ctx->cdata = NULL;
 
        ctx->userFunc(ctx, 0);
 }
@@ -858,7 +1251,7 @@ static void
 xml_end_tag(void *userData, const char *name)
 {
        struct xml_ctx *ctx = (struct xml_ctx *)userData;
-       const char *c = index(name, ':');
+       const char *c = strchr(name, ':');
        char *ep;
 
        ctx->userFunc(ctx, 1);
@@ -876,13 +1269,12 @@ 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);
+       free(ctx->cdata);
+       ctx->cdata = xmalloc(len + 1);
+       strlcpy(ctx->cdata, s, len + 1);
 }
 
-static struct remote_lock *lock_remote(char *path, long timeout)
+static struct remote_lock *lock_remote(const char *path, long timeout)
 {
        struct active_request_slot *slot;
        struct slot_results results;
@@ -893,7 +1285,7 @@ static struct remote_lock *lock_remote(char *path, long timeout)
        char *url;
        char *ep;
        char timeout_header[25];
-       struct remote_lock *lock = remote_locks;
+       struct remote_lock *lock = NULL;
        XML_Parser parser = XML_ParserCreate(NULL);
        enum XML_Status result;
        struct curl_slist *dav_headers = NULL;
@@ -902,18 +1294,6 @@ static struct remote_lock *lock_remote(char *path, long timeout)
        url = xmalloc(strlen(remote->url) + strlen(path) + 1);
        sprintf(url, "%s%s", remote->url, path);
 
-       /* Make sure the url is not already locked */
-       while (lock && strcmp(lock->url, url)) {
-               lock = lock->next;
-       }
-       if (lock) {
-               free(url);
-               if (refresh_lock(lock))
-                       return lock;
-               else
-                       return NULL;
-       }
-
        /* Make sure leading directories exist for the remote ref */
        ep = strchr(url + strlen(remote->url) + 11, '/');
        while (ep) {
@@ -935,7 +1315,7 @@ static struct remote_lock *lock_remote(char *path, long timeout)
                                return NULL;
                        }
                } else {
-                       fprintf(stderr, "Unable to start request\n");
+                       fprintf(stderr, "Unable to start MKCOL request\n");
                        free(url);
                        return NULL;
                }
@@ -971,10 +1351,7 @@ static struct remote_lock *lock_remote(char *path, long timeout)
        curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
 
        lock = xcalloc(1, sizeof(*lock));
-       lock->owner = NULL;
-       lock->token = NULL;
        lock->timeout = -1;
-       lock->refreshing = 0;
 
        if (start_active_slot(slot)) {
                run_active_slot(slot);
@@ -999,7 +1376,7 @@ static struct remote_lock *lock_remote(char *path, long timeout)
                        }
                }
        } else {
-               fprintf(stderr, "Unable to start request\n");
+               fprintf(stderr, "Unable to start LOCK request\n");
        }
 
        curl_slist_free_all(dav_headers);
@@ -1016,10 +1393,9 @@ static struct remote_lock *lock_remote(char *path, long timeout)
                lock = NULL;
        } else {
                lock->url = url;
-               lock->active = 1;
                lock->start_time = time(NULL);
-               lock->next = remote_locks;
-               remote_locks = lock;
+               lock->next = remote->locks;
+               remote->locks = lock;
        }
 
        return lock;
@@ -1029,6 +1405,7 @@ static int unlock_remote(struct remote_lock *lock)
 {
        struct active_request_slot *slot;
        struct slot_results results;
+       struct remote_lock *prev = remote->locks;
        char *lock_token_header;
        struct curl_slist *dav_headers = NULL;
        int rc = 0;
@@ -1050,16 +1427,29 @@ static int unlock_remote(struct remote_lock *lock)
                if (results.curl_result == CURLE_OK)
                        rc = 1;
                else
-                       fprintf(stderr, "Got HTTP error %ld\n",
+                       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);
 
-       lock->active = 0;
+       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;
 }
@@ -1083,7 +1473,7 @@ static void process_ls_object(struct remote_ls_ctx *ls)
                return;
        path += 8;
        obj_hex = xmalloc(strlen(path));
-       strncpy(obj_hex, path, 2);
+       strlcpy(obj_hex, path, 3);
        strcpy(obj_hex + 2, path + 3);
        one_remote_object(obj_hex);
        free(obj_hex);
@@ -1128,9 +1518,7 @@ static void handle_remote_ls_ctx(struct xml_ctx *ctx, int tag_closed)
                        ls->dentry_flags |= IS_DIR;
                }
        } else if (!strcmp(ctx->name, DAV_PROPFIND_RESP)) {
-               if (ls->dentry_name) {
-                       free(ls->dentry_name);
-               }
+               free(ls->dentry_name);
                ls->dentry_name = NULL;
                ls->dentry_flags = 0;
        }
@@ -1154,7 +1542,7 @@ static void remote_ls(const char *path, int flags,
        struct remote_ls_ctx ls;
 
        ls.flags = flags;
-       ls.path = strdup(path);
+       ls.path = xstrdup(path);
        ls.dentry_name = NULL;
        ls.dentry_flags = 0;
        ls.userData = userData;
@@ -1300,7 +1688,7 @@ static int locking_available(void)
                        }
                }
        } else {
-               fprintf(stderr, "Unable to start request\n");
+               fprintf(stderr, "Unable to start PROPFIND request\n");
        }
 
        free(out_data);
@@ -1310,6 +1698,15 @@ static int locking_available(void)
        return lock_flags;
 }
 
+static struct object_list **add_one_object(struct object *obj, struct object_list **p)
+{
+       struct object_list *entry = xmalloc(sizeof(struct object_list));
+       entry->item = obj;
+       entry->next = *p;
+       *p = entry;
+       return &entry->next;
+}
+
 static struct object_list **process_blob(struct blob *blob,
                                         struct object_list **p,
                                         struct name_path *path,
@@ -1323,7 +1720,7 @@ static struct object_list **process_blob(struct blob *blob,
                return p;
 
        obj->flags |= SEEN;
-       return add_object(obj, p, path, name);
+       return add_one_object(obj, p);
 }
 
 static struct object_list **process_tree(struct tree *tree,
@@ -1332,7 +1729,8 @@ static struct object_list **process_tree(struct tree *tree,
                                         const char *name)
 {
        struct object *obj = &tree->object;
-       struct tree_entry_list *entry;
+       struct tree_desc desc;
+       struct name_entry entry;
        struct name_path me;
 
        obj->flags |= LOCAL;
@@ -1343,52 +1741,57 @@ static struct object_list **process_tree(struct tree *tree,
                die("bad tree object %s", sha1_to_hex(obj->sha1));
 
        obj->flags |= SEEN;
-       p = add_object(obj, p, NULL, name);
+       name = xstrdup(name);
+       p = add_one_object(obj, p);
        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);
+
+       desc.buf = tree->buffer;
+       desc.size = tree->size;
+
+       while (tree_entry(&desc, &entry)) {
+               if (S_ISDIR(entry.mode))
+                       p = process_tree(lookup_tree(entry.sha1), p, &me, name);
                else
-                       p = process_blob(entry->item.blob, p, &me, entry->name);
-               free(entry);
-               entry = next;
+                       p = process_blob(lookup_blob(entry.sha1), p, &me, name);
        }
+       free(tree->buffer);
+       tree->buffer = NULL;
        return p;
 }
 
-static void get_delta(struct rev_info *revs, struct remote_lock *lock)
+static int get_delta(struct rev_info *revs, struct remote_lock *lock)
 {
+       int i;
        struct commit *commit;
-       struct object_list **p = &objects, *pending;
+       struct object_list **p = &objects;
+       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))
-                       add_request(&commit->object, lock);
+                       count += add_send_request(&commit->object, lock);
        }
 
-       for (pending = revs->pending_objects; pending; pending = pending->next) {
-               struct object *obj = pending->item;
-               const char *name = pending->name;
+       for (i = 0; i < revs->pending.nr; i++) {
+               struct object_array_entry *entry = revs->pending.objects + i;
+               struct object *obj = entry->item;
+               const char *name = entry->name;
 
                if (obj->flags & (UNINTERESTING | SEEN))
                        continue;
-               if (obj->type == tag_type) {
+               if (obj->type == OBJ_TAG) {
                        obj->flags |= SEEN;
-                       p = add_object(obj, p, NULL, name);
+                       p = add_one_object(obj, p);
                        continue;
                }
-               if (obj->type == tree_type) {
+               if (obj->type == OBJ_TREE) {
                        p = process_tree((struct tree *)obj, p, NULL, name);
                        continue;
                }
-               if (obj->type == blob_type) {
+               if (obj->type == OBJ_BLOB) {
                        p = process_blob((struct blob *)obj, p, NULL, name);
                        continue;
                }
@@ -1397,9 +1800,11 @@ static void get_delta(struct rev_info *revs, struct remote_lock *lock)
 
        while (objects) {
                if (!(objects->item->flags & UNINTERESTING))
-                       add_request(objects->item, lock);
+                       count += add_send_request(objects->item, lock);
                objects = objects->next;
        }
+
+       return count;
 }
 
 static int update_remote(unsigned char *sha1, struct remote_lock *lock)
@@ -1462,12 +1867,12 @@ static int update_remote(unsigned char *sha1, struct remote_lock *lock)
 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)
+static int one_local_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
 {
        struct ref *ref;
        int len = strlen(refname) + 1;
        ref = xcalloc(1, sizeof(*ref) + len);
-       memcpy(ref->new_sha1, sha1, 20);
+       hashcpy(ref->new_sha1, sha1);
        memcpy(ref->name, refname, len);
        *local_tail = ref;
        local_tail = &ref->next;
@@ -1478,6 +1883,8 @@ 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,
@@ -1486,9 +1893,21 @@ static void one_remote_ref(char *refname)
                return;
        }
 
-       int len = strlen(refname) + 1;
+       /*
+        * 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);
+       hashcpy(ref->old_sha1, remote_sha1);
        memcpy(ref->name, refname, len);
        *remote_tail = ref;
        remote_tail = &ref->next;
@@ -1497,7 +1916,7 @@ static void one_remote_ref(char *refname)
 static void get_local_heads(void)
 {
        local_tail = &local_refs;
-       for_each_ref(one_local_ref);
+       for_each_ref(one_local_ref, NULL);
 }
 
 static void get_dav_remote_heads(void)
@@ -1539,12 +1958,12 @@ static int ref_newer(const unsigned char *new_sha1,
         * old.  Otherwise we require --force.
         */
        o = deref_tag(parse_object(old_sha1), NULL, 0);
-       if (!o || o->type != commit_type)
+       if (!o || o->type != OBJ_COMMIT)
                return 0;
        old = (struct commit *) o;
 
        o = deref_tag(parse_object(new_sha1), NULL, 0);
-       if (!o || o->type != commit_type)
+       if (!o || o->type != OBJ_COMMIT)
                return 0;
        new = (struct commit *) o;
 
@@ -1591,24 +2010,297 @@ static void mark_edges_uninteresting(struct commit_list *list)
        }
 }
 
+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 == OBJ_TAG) {
+               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 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(const 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;
+       hashclr(sha1);
+
+       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 = xmalloc(buffer.posn - 5);
+               strlcpy(*symref, (char *)buffer.buffer + 5, buffer.posn - 5);
+       } else {
+               get_sha1_hex(buffer.buffer, sha1);
+       }
+
+       free(buffer.buffer);
+}
+
+static int verify_merge_base(unsigned char *head_sha1, unsigned char *branch_sha1)
+{
+       struct commit *head = lookup_commit(head_sha1);
+       struct commit *branch = lookup_commit(branch_sha1);
+       struct commit_list *merge_bases = get_merge_bases(head, branch, 1);
+
+       return (merge_bases && !merge_bases->next && merge_bases->item == branch);
+}
+
+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;
-       struct remote_lock *ref_lock;
+       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->path_len = 0;
-       remote->packs = NULL;
+       remote = xcalloc(sizeof(*remote), 1);
 
        argv++;
        for (i = 1; i < argc; i++, argv++) {
@@ -1627,13 +2319,21 @@ 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) {
-                       remote->url = arg;
                        char *path = strstr(arg, "//");
+                       remote->url = arg;
                        if (path) {
-                               path = index(path+2, '/');
+                               path = strchr(path+2, '/');
                                if (path)
                                        remote->path_len = strlen(path);
                        }
@@ -1647,6 +2347,9 @@ int main(int argc, char **argv)
        if (!remote->url)
                usage(http_push_usage);
 
+       if (delete_branch && nr_refspec != 1)
+               die("You must specify only one branch name when deleting a remote branch");
+
        memset(remote_dir_exists, -1, 256);
 
        http_init();
@@ -1665,11 +2368,31 @@ int main(int argc, char **argv)
                goto cleanup;
        }
 
+       /* 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;
@@ -1681,14 +2404,16 @@ int main(int argc, char **argv)
                return 0;
        }
 
-       int ret = 0;
-       int new_refs = 0;
-       struct ref *ref;
+       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 (!hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) {
                        if (push_verbosely || 1)
                                fprintf(stderr, "'%s': up-to-date\n", ref->name);
                        continue;
@@ -1713,14 +2438,14 @@ int main(int argc, char **argv)
                                      "need to pull first?",
                                      ref->name,
                                      ref->peer_ref->name);
-                               ret = -2;
+                               rc = -2;
                                continue;
                        }
                }
-               memcpy(ref->new_sha1, ref->peer_ref->new_sha1, 20);
+               hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
                if (is_zero_sha1(ref->new_sha1)) {
                        error("cannot happen anymore");
-                       ret = -3;
+                       rc = -3;
                        continue;
                }
                new_refs++;
@@ -1743,10 +2468,9 @@ int main(int argc, char **argv)
                }
 
                /* Set up revision info for this refspec */
-               const char *commit_argv[4];
-               int commit_argc = 3;
-               char *new_sha1_hex = strdup(sha1_to_hex(ref->new_sha1));
-               char *old_sha1_hex = NULL;
+               commit_argc = 3;
+               new_sha1_hex = xstrdup(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)) {
@@ -1756,6 +2480,7 @@ int main(int argc, char **argv)
                        commit_argv[3] = old_sha1_hex;
                        commit_argc++;
                }
+               init_revisions(&revs, setup_git_directory());
                setup_revisions(commit_argc, commit_argv, &revs, NULL);
                free(new_sha1_hex);
                if (old_sha1_hex) {
@@ -1767,14 +2492,18 @@ int main(int argc, char **argv)
                pushing = 0;
                prepare_revision_walk(&revs);
                mark_edges_uninteresting(revs.commits);
-               fetch_indices();
-               get_delta(&revs, ref_lock);
+               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;
+               if (objects_to_send)
+                       fprintf(stderr, "    sending %d objects\n",
+                               objects_to_send);
+#ifdef USE_CURL_MULTI
                fill_active_slots();
+#endif
                finish_all_active_slots();
 
                /* Update the remote branch if all went well */
@@ -1787,7 +2516,20 @@ int main(int argc, char **argv)
                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);