X-Git-Url: https://git.octo.it/?a=blobdiff_plain;f=http-push.c;h=114d01bced1cb3f9a906c7c0df85bf5cd2b811e0;hb=7d09fbe4ab7f080a8f8f5dcef7e0f3edf5e26019;hp=0b90fb9ae3396d0f2b0a9ebf9acad8374f3db317;hpb=92e2eb9c0a868348dd2caf2b923fd6542ca7ceb7;p=git.git diff --git a/http-push.c b/http-push.c index 0b90fb9a..114d01bc 100644 --- a/http-push.c +++ b/http-push.c @@ -4,29 +4,16 @@ #include "fetch.h" #include "tag.h" #include "blob.h" +#include "http.h" +#include "refs.h" +#include "diff.h" +#include "revision.h" +#include "exec_cmd.h" -#include -#include #include static const char http_push_usage[] = -"git-http-push [--complete] [--force] [--verbose] [...]\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 -#endif +"git-http-push [--all] [--force] [--verbose] [...]\n"; #ifndef XML_STATUS_OK enum XML_Status { @@ -37,60 +24,82 @@ enum XML_Status { #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 "\n\n\n\n\n" +#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 "\n\n\n\n\n" +#define PROPFIND_ALL_REQUEST "\n\n\n" #define LOCK_REQUEST "\n\n\n\n\nmailto:%s\n\n" #define LOCK_TIME 600 #define LOCK_REFRESH 30 -static int active_requests = 0; -static int data_received; +/* bits #0-15 in revision.h */ + +#define LOCAL (1u<<16) +#define REMOTE (1u<<17) +#define FETCHING (1u<<18) +#define PUSHING (1u<<19) + +/* We allow "recursive" symbolic refs. Only within reason, though */ +#define MAXDEPTH 5 + static int pushing = 0; static int aborted = 0; -static char remote_dir_exists[256]; +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, @@ -101,14 +110,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]; @@ -118,351 +129,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; int refreshing; + struct remote_lock *next; }; -struct lockprop -{ - int supported_lock; - int lock_entry; - int lock_scope; - int lock_type; - int lock_exclusive; - int lock_exclusive_write; -}; - -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); - 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); + 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; } -#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; @@ -476,6 +454,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); @@ -488,12 +468,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]; @@ -504,7 +485,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 */ @@ -534,8 +515,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); @@ -553,6 +532,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); @@ -569,6 +550,7 @@ static void start_put(struct transfer_request *request) } else { request->state = ABORTED; free(request->url); + request->url = NULL; } } @@ -578,6 +560,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); @@ -592,12 +576,14 @@ static void start_move(struct transfer_request *request) } else { request->state = ABORTED; free(request->url); + request->url = NULL; } } -int refresh_lock(struct active_lock *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; @@ -612,6 +598,7 @@ int refresh_lock(struct active_lock *lock) 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); @@ -620,8 +607,9 @@ int refresh_lock(struct active_lock *lock) 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); + if (results.curl_result != CURLE_OK) { + fprintf(stderr, "LOCK HTTP error %ld\n", + results.http_code); } else { lock->start_time = time(NULL); rc = 1; @@ -635,48 +623,80 @@ int refresh_lock(struct active_lock *lock) return rc; } -static void finish_request(struct transfer_request *request) +static void check_locks(void) { + struct remote_lock *lock = remote->locks; time_t current_time = time(NULL); int time_remaining; - request->curl_result = request->slot->curl_result; + 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) +{ + 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; - /* Refresh the lock if it is close to timing out */ - time_remaining = request->lock->start_time + request->lock->timeout - - current_time; - if (time_remaining < LOCK_REFRESH && !request->lock->refreshing) { - if (!refresh_lock(request->lock)) { - fprintf(stderr, "Unable to refresh remote lock\n"); - aborted = 1; - } - } + /* 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) { - remote_dir_exists[request->sha1[0]] = 1; - 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->sha1[0]] = 1; + 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; @@ -686,7 +706,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; @@ -694,81 +714,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) { - int curl_result = curl_message->data.result; - curl_multi_remove_handle(curlm, slot->curl); - active_requests--; - slot->done = 1; - slot->in_use = 0; - slot->curl_result = curl_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; @@ -776,17 +799,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) { - if (remote_dir_exists[request->sha1[0]]) + if (remote_dir_exists[request->obj->sha1[0]] == 1) { start_put(request); - else + } else { start_mkcol(request); + } curl_multi_perform(curlm, &num_transfers); } - request = request->next; + request = next; } while (slot != NULL) { @@ -795,49 +819,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) @@ -852,16 +907,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); @@ -875,9 +932,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"); @@ -886,6 +943,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); @@ -909,7 +967,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, @@ -917,6 +975,7 @@ static int fetch_index(unsigned char *sha1) } } else { free(url); + fclose(indexfile); return error("Unable to start request"); } @@ -939,7 +998,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; @@ -948,31 +1007,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); @@ -1032,14 +1091,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); @@ -1060,21 +1118,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 { @@ -1086,112 +1145,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 = strchr(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 = strchr(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; +} + +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); } -struct active_lock *lock_remote(char *file, long timeout) +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; @@ -1199,27 +1276,29 @@ struct active_lock *lock_remote(char *file, long 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); @@ -1227,7 +1306,7 @@ struct active_lock *lock_remote(char *file, long timeout) return NULL; } } else { - fprintf(stderr, "Unable to start request\n"); + fprintf(stderr, "Unable to start MKCOL request\n"); free(url); return NULL; } @@ -1246,81 +1325,78 @@ struct active_lock *lock_remote(char *file, long timeout) in_buffer.posn = 0; in_buffer.buffer = in_data; - new_lock = xcalloc(1, sizeof(*new_lock)); - new_lock->owner = NULL; - new_lock->token = NULL; - new_lock->timeout = -1; - new_lock->refreshing = 0; - 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"); } + 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(url); - 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); + if (lock->token == NULL || lock->timeout <= 0) { + if (lock->token != NULL) + free(lock->token); + if (lock->owner != NULL) + free(lock->owner); free(url); - free(new_lock); - return NULL; + free(lock); + lock = NULL; + } else { + lock->url = url; + lock->start_time = time(NULL); + lock->next = remote->locks; + remote->locks = lock; } - new_lock->url = url; - new_lock->start_time = time(NULL); - return new_lock; + return lock; } -int unlock_remote(struct active_lock *lock) +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; @@ -1331,6 +1407,7 @@ int unlock_remote(struct active_lock *lock) 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, lock->url); curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_UNLOCK); @@ -1338,18 +1415,27 @@ int unlock_remote(struct active_lock *lock) 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); + 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); @@ -1359,21 +1445,107 @@ int unlock_remote(struct active_lock *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; @@ -1382,130 +1554,242 @@ 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(unsigned char *sha1, struct active_lock *lock) +static int update_remote(unsigned char *sha1, struct remote_lock *lock) { struct active_request_slot *slot; + struct slot_results results; char *out_data; char *if_header; struct buffer out_buffer; @@ -1527,6 +1811,7 @@ int update_remote(unsigned char *sha1, struct active_lock *lock) 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); @@ -1541,10 +1826,10 @@ int update_remote(unsigned char *sha1, struct active_lock *lock) run_active_slot(slot); free(out_data); free(if_header); - 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; } @@ -1558,41 +1843,489 @@ int update_remote(unsigned char *sha1, struct active_lock *lock) 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 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: ()", 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 active_request_slot *next_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 && iname, 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; } @@ -1604,10 +2337,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 = strchr(path+2, '/'); + if (path) + remote->path_len = strlen(path); + } continue; } refspec = argv; @@ -1615,52 +2362,15 @@ int main(int argc, char **argv) break; } - memset(remote_dir_exists, 0, 256); + if (!remote->url) + usage(http_push_usage); - curl_global_init(CURL_GLOBAL_ALL); + if (delete_branch && nr_refspec != 1) + die("You must specify only one branch name when deleting a remote branch"); -#ifdef USE_CURL_MULTI - { - char *http_max_requests = getenv("GIT_HTTP_MAX_REQUESTS"); - if (http_max_requests != NULL) - max_requests = atoi(http_max_requests); - } + memset(remote_dir_exists, -1, 256); - 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); - - 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:"); @@ -1669,147 +2379,180 @@ 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, LOCK_TIME); - 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; + /* 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++; } - if (get_sha1(local_ref, local_sha1) != 0) { - fprintf(stderr, "Error resolving local branch %s\n", - local_ref); - rc = 1; - goto unlock; - } - - /* 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(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_lock); - free(remote_path); + 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); 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) { @@ -1818,12 +2561,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; }