diff options
Diffstat (limited to 'http-push.c')
-rw-r--r-- | http-push.c | 1426 |
1 files changed, 1426 insertions, 0 deletions
diff --git a/http-push.c b/http-push.c new file mode 100644 index 0000000000..76c788673e --- /dev/null +++ b/http-push.c @@ -0,0 +1,1426 @@ +#include "cache.h" +#include "commit.h" +#include "pack.h" +#include "fetch.h" +#include "tag.h" +#include "blob.h" +#include "http.h" + +#include <expat.h> + +static const char http_push_usage[] = +"git-http-push [--complete] [--force] [--verbose] <url> <ref> [<ref>...]\n"; + +#ifndef XML_STATUS_OK +enum XML_Status { + XML_STATUS_OK = 1, + XML_STATUS_ERROR = 0 +}; +#define XML_STATUS_OK 1 +#define XML_STATUS_ERROR 0 +#endif + +#define RANGE_HEADER_SIZE 30 + +/* 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" + +/* 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" + +/* DAV request body templates */ +#define PROPFIND_REQUEST "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:prop xmlns:R=\"%s\">\n<D:supportedlock/>\n</D:prop>\n</D:propfind>" +#define LOCK_REQUEST "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:lockinfo xmlns:D=\"DAV:\">\n<D:lockscope><D:exclusive/></D:lockscope>\n<D:locktype><D:write/></D:locktype>\n<D:owner>\n<D:href>mailto:%s</D:href>\n</D:owner>\n</D:lockinfo>" + +#define LOCK_TIME 600 +#define LOCK_REFRESH 30 + +static int pushing = 0; +static int aborted = 0; +static 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; + +struct repo +{ + char *url; + struct packed_git *packs; +}; + +static struct repo *remote = NULL; + +enum transfer_state { + NEED_CHECK, + RUN_HEAD, + NEED_PUSH, + RUN_MKCOL, + RUN_PUT, + RUN_MOVE, + ABORTED, + COMPLETE, +}; + +struct transfer_request +{ + unsigned char sha1[20]; + char *url; + char *dest; + struct active_lock *lock; + struct curl_slist *headers; + struct buffer buffer; + char filename[PATH_MAX]; + char tmpfile[PATH_MAX]; + enum transfer_state state; + CURLcode curl_result; + char errorstr[CURL_ERROR_SIZE]; + long http_code; + unsigned char real_sha1[20]; + SHA_CTX c; + z_stream stream; + int zret; + int rename; + struct active_request_slot *slot; + struct transfer_request *next; +}; + +static struct transfer_request *request_queue_head = NULL; + +struct xml_ctx +{ + char *name; + int len; + char *cdata; + void (*userFunc)(struct xml_ctx *ctx, int tag_closed); + void *userData; +}; + +struct active_lock +{ + char *url; + char *owner; + char *token; + time_t start_time; + long timeout; + int refreshing; +}; + +static void finish_request(struct transfer_request *request); + +static void process_response(void *callback_data) +{ + struct transfer_request *request = + (struct transfer_request *)callback_data; + + finish_request(request); +} + +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); + + slot = get_active_slot(); + slot->callback_func = process_response; + slot->callback_data = request; + 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); + + if (start_active_slot(slot)) { + request->slot = slot; + request->state = RUN_HEAD; + } else { + request->state = ABORTED; + free(request->url); + request->url = NULL; + } +} + +static void start_mkcol(struct transfer_request *request) +{ + char *hex = sha1_to_hex(request->sha1); + struct active_request_slot *slot; + char *posn; + + request->url = xmalloc(strlen(remote->url) + 13); + strcpy(request->url, remote->url); + posn = request->url + strlen(remote->url); + strcpy(posn, "objects/"); + posn += 8; + memcpy(posn, hex, 2); + posn += 2; + 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); + curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_MKCOL); + curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null); + + if (start_active_slot(slot)) { + request->slot = slot; + request->state = RUN_MKCOL; + } 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); + struct active_request_slot *slot; + char *posn; + char type[20]; + char hdr[50]; + void *unpacked; + unsigned long len; + int hdrlen; + ssize_t size; + z_stream stream; + + unpacked = read_sha1_file(request->sha1, type, &len); + hdrlen = sprintf(hdr, "%s %lu", type, len) + 1; + + /* Set it up */ + memset(&stream, 0, sizeof(stream)); + deflateInit(&stream, Z_BEST_COMPRESSION); + size = deflateBound(&stream, len + hdrlen); + request->buffer.buffer = xmalloc(size); + + /* Compress it */ + stream.next_out = request->buffer.buffer; + stream.avail_out = size; + + /* First header.. */ + stream.next_in = (void *)hdr; + stream.avail_in = hdrlen; + while (deflate(&stream, 0) == Z_OK) + /* nothing */; + + /* Then the data itself.. */ + stream.next_in = unpacked; + stream.avail_in = len; + while (deflate(&stream, Z_FINISH) == Z_OK) + /* nothing */; + deflateEnd(&stream); + free(unpacked); + + request->buffer.size = stream.total_out; + request->buffer.posn = 0; + + request->url = xmalloc(strlen(remote->url) + + strlen(request->lock->token) + 51); + 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); + request->dest = xmalloc(strlen(request->url) + 14); + sprintf(request->dest, "Destination: %s", request->url); + posn += 38; + *(posn++) = '.'; + 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); + curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null); + curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PUT); + curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1); + curl_easy_setopt(slot->curl, CURLOPT_PUT, 1); + curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0); + curl_easy_setopt(slot->curl, CURLOPT_URL, request->url); + + if (start_active_slot(slot)) { + request->slot = slot; + request->state = RUN_PUT; + } else { + request->state = ABORTED; + free(request->url); + request->url = NULL; + } +} + +static void start_move(struct transfer_request *request) +{ + struct active_request_slot *slot; + 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); + dav_headers = curl_slist_append(dav_headers, "Overwrite: T"); + curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers); + curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null); + curl_easy_setopt(slot->curl, CURLOPT_URL, request->url); + + if (start_active_slot(slot)) { + request->slot = slot; + request->state = RUN_MOVE; + } else { + request->state = ABORTED; + free(request->url); + request->url = NULL; + } +} + +static int refresh_lock(struct active_lock *lock) +{ + struct active_request_slot *slot; + char *if_header; + char timeout_header[25]; + struct curl_slist *dav_headers = NULL; + int rc = 0; + + lock->refreshing = 1; + + if_header = xmalloc(strlen(lock->token) + 25); + sprintf(if_header, "If: (<opaquelocktoken:%s>)", lock->token); + sprintf(timeout_header, "Timeout: Second-%ld", lock->timeout); + dav_headers = curl_slist_append(dav_headers, if_header); + dav_headers = curl_slist_append(dav_headers, timeout_header); + + slot = get_active_slot(); + curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); + curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null); + curl_easy_setopt(slot->curl, CURLOPT_URL, lock->url); + curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_LOCK); + curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers); + + if (start_active_slot(slot)) { + run_active_slot(slot); + if (slot->curl_result != CURLE_OK) { + fprintf(stderr, "Got HTTP error %ld\n", slot->http_code); + } else { + lock->start_time = time(NULL); + rc = 1; + } + } + + lock->refreshing = 0; + curl_slist_free_all(dav_headers); + free(if_header); + + return rc; +} + +static void finish_request(struct transfer_request *request) +{ + time_t current_time = time(NULL); + int time_remaining; + + 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; + } + } + + if (request->headers != NULL) + curl_slist_free_all(request->headers); + + /* URL is reused for MOVE after PUT */ + if (request->state != RUN_PUT) { + free(request->url); + request->url = NULL; + } + + 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) { + if (request->curl_result == CURLE_OK || + request->http_code == 405) { + remote_dir_exists[request->sha1[0]] = 1; + start_put(request); + } else { + fprintf(stderr, "MKCOL %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_PUT) { + if (request->curl_result == CURLE_OK) { + start_move(request); + } else { + fprintf(stderr, "PUT %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_MOVE) { + if (request->curl_result == CURLE_OK) { + if (push_verbosely) + fprintf(stderr, + "sent %s\n", + sha1_to_hex(request->sha1)); + request->state = COMPLETE; + } else { + fprintf(stderr, "MOVE %s failed, aborting (%d/%ld)\n", + sha1_to_hex(request->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; + } + + if (request->url != NULL) + free(request->url); + free(request); +} + +void fill_active_slots(void) +{ + struct transfer_request *request = request_queue_head; + struct active_request_slot *slot = active_queue_head; + int num_transfers; + + if (aborted) + return; + + while (active_requests < max_requests && request != NULL) { + if (!pushing && request->state == NEED_CHECK) { + start_check(request); + curl_multi_perform(curlm, &num_transfers); + } else if (pushing && request->state == NEED_PUSH) { + if (remote_dir_exists[request->sha1[0]]) + start_put(request); + else + start_mkcol(request); + curl_multi_perform(curlm, &num_transfers); + } + request = request->next; + } + + while (slot != NULL) { + if (!slot->in_use && slot->curl != NULL) { + curl_easy_cleanup(slot->curl); + slot->curl = NULL; + } + slot = slot->next; + } +} + +static void add_request(unsigned char *sha1, struct active_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; + + request = xmalloc(sizeof(*request)); + memcpy(request->sha1, sha1, 20); + request->url = NULL; + request->lock = lock; + request->headers = NULL; + request->state = NEED_CHECK; + request->next = request_queue_head; + request_queue_head = request; + + fill_active_slots(); + step_active_slots(); +} + +static int fetch_index(unsigned char *sha1) +{ + char *hex = sha1_to_hex(sha1); + char *filename; + char *url; + char tmpfile[PATH_MAX]; + long prev_posn = 0; + char range[RANGE_HEADER_SIZE]; + struct curl_slist *range_header = NULL; + + FILE *indexfile; + struct active_request_slot *slot; + + /* 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); + slot = get_active_slot(); + 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) { + free(url); + return error("Unable to verify pack %s is available", + hex); + } + } else { + return error("Unable to start request"); + } + + if (has_pack_index(sha1)) + return 0; + + if (push_verbosely) + fprintf(stderr, "Getting index for pack %s\n", hex); + + sprintf(url, "%s/objects/pack/pack-%s.idx", remote->url, hex); + + filename = sha1_pack_index_name(sha1); + snprintf(tmpfile, sizeof(tmpfile), "%s.temp", filename); + indexfile = fopen(tmpfile, "a"); + if (!indexfile) + return error("Unable to open local file %s for pack index", + filename); + + slot = get_active_slot(); + curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0); + curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); + curl_easy_setopt(slot->curl, CURLOPT_FILE, indexfile); + 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 = indexfile; + + /* If there is data present from a previous transfer attempt, + resume where it left off */ + prev_posn = ftell(indexfile); + if (prev_posn>0) { + if (push_verbosely) + fprintf(stderr, + "Resuming fetch of index for pack %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); + } + + if (start_active_slot(slot)) { + run_active_slot(slot); + if (slot->curl_result != CURLE_OK) { + free(url); + fclose(indexfile); + return error("Unable to get pack index %s\n%s", url, + curl_errorstr); + } + } else { + free(url); + fclose(indexfile); + return error("Unable to start request"); + } + + free(url); + fclose(indexfile); + + return move_temp_to_file(tmpfile, filename); +} + +static int setup_index(unsigned char *sha1) +{ + struct packed_git *new_pack; + + if (fetch_index(sha1)) + return -1; + + new_pack = parse_pack_index(sha1); + new_pack->next = remote->packs; + remote->packs = new_pack; + return 0; +} + +static int fetch_indices(void) +{ + unsigned char sha1[20]; + char *url; + struct buffer buffer; + char *data; + int i = 0; + + struct active_request_slot *slot; + + data = xmalloc(4096); + memset(data, 0, 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); + + slot = get_active_slot(); + curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer); + 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) { + free(buffer.buffer); + free(url); + if (slot->http_code == 404) + return 0; + else + return error("%s", curl_errorstr); + } + } else { + free(buffer.buffer); + free(url); + return error("Unable to start request"); + } + free(url); + + data = buffer.buffer; + while (i < buffer.posn) { + switch (data[i]) { + case 'P': + i++; + if (i + 52 < buffer.posn && + !strncmp(data + i, " pack-", 6) && + !strncmp(data + i + 46, ".pack\n", 6)) { + get_sha1_hex(data + i + 6, sha1); + setup_index(sha1); + i += 51; + break; + } + default: + while (data[i] != '\n') + i++; + } + i++; + } + + free(buffer.buffer); + return 0; +} + +static inline int needs_quote(int ch) +{ + switch (ch) { + case '/': case '-': case '.': + case 'A'...'Z': case 'a'...'z': case '0'...'9': + return 0; + default: + return 1; + } +} + +static inline int hex(int v) +{ + if (v < 10) return '0' + v; + else return 'A' + v - 10; +} + +static char *quote_ref_url(const char *base, const char *ref) +{ + const char *cp; + char *dp, *qref; + int len, baselen, ch; + + baselen = strlen(base); + len = baselen + 12; /* "refs/heads/" + NUL */ + 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++) { + if (needs_quote(ch)) { + *dp++ = '%'; + *dp++ = hex((ch >> 4) & 0xF); + *dp++ = hex(ch & 0xF); + } + else + *dp++ = ch; + } + *dp = 0; + + return qref; +} + +int fetch_ref(char *ref, unsigned char *sha1) +{ + char *url; + char hex[42]; + struct buffer buffer; + char *base = remote->url; + struct active_request_slot *slot; + buffer.size = 41; + buffer.posn = 0; + buffer.buffer = hex; + hex[41] = '\0'; + + url = quote_ref_url(base, ref); + slot = get_active_slot(); + 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 (slot->curl_result != CURLE_OK) + return error("Couldn't get %s for %s\n%s", + url, ref, curl_errorstr); + } else { + return error("Unable to start request"); + } + + hex[40] = '\0'; + get_sha1_hex(hex, sha1); + return 0; +} + +static void handle_lockprop_ctx(struct xml_ctx *ctx, int tag_closed) +{ + 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 handle_new_lock_ctx(struct xml_ctx *ctx, int tag_closed) +{ + struct active_lock *lock = (struct active_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); + } + } + } +} + +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, ':'); + int new_len; + + 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 +xml_end_tag(void *userData, const char *name) +{ + struct xml_ctx *ctx = (struct xml_ctx *)userData; + const char *c = index(name, ':'); + char *ep; + + 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); +} + +static struct active_lock *lock_remote(char *file, long timeout) +{ + struct active_request_slot *slot; + struct buffer out_buffer; + struct buffer in_buffer; + char *out_data; + char *in_data; + char *url; + char *ep; + char timeout_header[25]; + struct active_lock *new_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); + + /* Make sure leading directories exist for the remote ref */ + ep = strchr(url + strlen(remote->url) + 11, '/'); + while (ep) { + *ep = 0; + slot = get_active_slot(); + 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) { + fprintf(stderr, + "Unable to create branch path %s\n", + url); + free(url); + return NULL; + } + } else { + fprintf(stderr, "Unable to start request\n"); + free(url); + return NULL; + } + *ep = '/'; + ep = strchr(ep + 1, '/'); + } + + out_buffer.size = strlen(LOCK_REQUEST) + strlen(git_default_email) - 2; + out_data = xmalloc(out_buffer.size + 1); + snprintf(out_data, out_buffer.size + 1, LOCK_REQUEST, git_default_email); + 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; + + 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(); + 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, 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); + + new_lock = xcalloc(1, sizeof(*new_lock)); + new_lock->owner = NULL; + new_lock->token = NULL; + new_lock->timeout = -1; + new_lock->refreshing = 0; + + if (start_active_slot(slot)) { + run_active_slot(slot); + if (slot->curl_result == CURLE_OK) { + ctx.name = xcalloc(10, 1); + ctx.len = 0; + ctx.cdata = NULL; + ctx.userFunc = handle_new_lock_ctx; + ctx.userData = new_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))); + new_lock->timeout = -1; + } + } + } else { + fprintf(stderr, "Unable to start request\n"); + } + + curl_slist_free_all(dav_headers); + free(out_data); + free(in_data); + + if (new_lock->token == NULL || new_lock->timeout <= 0) { + if (new_lock->token != NULL) + free(new_lock->token); + if (new_lock->owner != NULL) + free(new_lock->owner); + free(url); + free(new_lock); + new_lock = NULL; + } else { + new_lock->url = url; + new_lock->start_time = time(NULL); + } + + return new_lock; +} + +static int unlock_remote(struct active_lock *lock) +{ + struct active_request_slot *slot; + char *lock_token_header; + struct curl_slist *dav_headers = NULL; + int rc = 0; + + lock_token_header = xmalloc(strlen(lock->token) + 31); + sprintf(lock_token_header, "Lock-Token: <opaquelocktoken:%s>", + lock->token); + dav_headers = curl_slist_append(dav_headers, lock_token_header); + + slot = get_active_slot(); + 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); + curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers); + + if (start_active_slot(slot)) { + run_active_slot(slot); + if (slot->curl_result == CURLE_OK) + rc = 1; + else + fprintf(stderr, "Got HTTP error %ld\n", + slot->http_code); + } else { + fprintf(stderr, "Unable to start request\n"); + } + + curl_slist_free_all(dav_headers); + free(lock_token_header); + + if (lock->owner != NULL) + free(lock->owner); + free(lock->url); +/* Freeing the token causes a segfault... + free(lock->token); +*/ + free(lock); + + return rc; +} + +static int locking_available(void) +{ + struct active_request_slot *slot; + 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_REQUEST) + strlen(remote->url) - 2; + out_data = xmalloc(out_buffer.size + 1); + snprintf(out_data, out_buffer.size + 1, PROPFIND_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(); + 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 (slot->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 request\n"); + } + + free(out_data); + free(in_buffer.buffer); + curl_slist_free_all(dav_headers); + + return lock_flags; +} + +static int is_ancestor(unsigned char *sha1, struct commit *commit) +{ + struct commit_list *parents; + + 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; + } + } + return 0; +} + +static void get_delta(unsigned char *sha1, struct object *obj, + struct active_lock *lock) +{ + struct commit *commit; + struct commit_list *parents; + struct tree *tree; + struct tree_entry_list *entry; + + if (sha1 && !memcmp(sha1, obj->sha1, 20)) + return; + + if (aborted) + return; + + 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; + } + 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; + } + 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; + } + } else if (obj->type == blob_type || obj->type == tag_type) { + add_request(obj->sha1, lock); + } +} + +static int update_remote(unsigned char *sha1, struct active_lock *lock) +{ + struct active_request_slot *slot; + char *out_data; + char *if_header; + struct buffer out_buffer; + struct curl_slist *dav_headers = NULL; + int i; + + if_header = xmalloc(strlen(lock->token) + 25); + sprintf(if_header, "If: (<opaquelocktoken:%s>)", lock->token); + dav_headers = curl_slist_append(dav_headers, if_header); + + out_buffer.size = 41; + out_data = xmalloc(out_buffer.size + 1); + i = snprintf(out_data, out_buffer.size + 1, "%s\n", sha1_to_hex(sha1)); + if (i != out_buffer.size) { + fprintf(stderr, "Unable to initialize PUT request body\n"); + return 0; + } + out_buffer.posn = 0; + out_buffer.buffer = out_data; + + slot = get_active_slot(); + 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_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); + + if (start_active_slot(slot)) { + run_active_slot(slot); + free(out_data); + free(if_header); + if (slot->curl_result != CURLE_OK) { + fprintf(stderr, + "PUT error: curl result=%d, HTTP code=%ld\n", + slot->curl_result, slot->http_code); + /* We should attempt recovery? */ + return 0; + } + } else { + free(out_data); + free(if_header); + fprintf(stderr, "Unable to start PUT request\n"); + return 0; + } + + return 1; +} + +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; + int rc = 0; + int i; + + setup_ident(); + + remote = xmalloc(sizeof(*remote)); + remote->url = NULL; + remote->packs = NULL; + + argv++; + for (i = 1; i < argc; i++, argv++) { + char *arg = *argv; + + if (*arg == '-') { + if (!strcmp(arg, "--complete")) { + push_all = 1; + continue; + } + if (!strcmp(arg, "--force")) { + force_all = 1; + continue; + } + if (!strcmp(arg, "--verbose")) { + push_verbosely = 1; + continue; + } + usage(http_push_usage); + } + if (!remote->url) { + remote->url = arg; + continue; + } + refspec = argv; + nr_refspec = argc - i; + break; + } + + memset(remote_dir_exists, 0, 256); + + http_init(); + + no_pragma_header = curl_slist_append(no_pragma_header, "Pragma:"); + default_headers = curl_slist_append(default_headers, "Range:"); + default_headers = curl_slist_append(default_headers, "Destination:"); + default_headers = curl_slist_append(default_headers, "If:"); + default_headers = curl_slist_append(default_headers, + "Pragma: no-cache"); + + /* Verify DAV compliance/lock support */ + 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++; + } + ep = strchr(local_ref, ':'); + if (ep) { + remote_ref = ep + 1; + *ep = 0; + } + else + remote_ref = local_ref; + + /* 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) { + fprintf(stderr, "Unable to lock remote branch %s\n", + remote_ref); + rc = 1; + continue; + } + + /* Resolve local and remote refs */ + if (fetch_ref(remote_ref, remote_sha1) != 0) { + fprintf(stderr, + "Remote branch %s does not exist on %s\n", + remote_ref, remote->url); + new_branch = 1; + } + if (get_sha1(local_ref, local_sha1) != 0) { + fprintf(stderr, "Error resolving local branch %s\n", + local_ref); + rc = 1; + goto unlock; + } + + /* 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; + } + } + + /* Generate and check list of required objects */ + pushing = 0; + if (do_remote_update || push_all) + fetch_indices(); + get_delta(push_all ? NULL : remote_sha1, + local_object, remote_lock); + finish_all_active_slots(); + + /* Push missing objects to remote, this would be a + convenient time to pack them first if appropriate. */ + pushing = 1; + 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; + } + } + + unlock: + unlock_remote(remote_lock); + free(remote_path); + } + + cleanup: + free(remote); + + curl_slist_free_all(no_pragma_header); + curl_slist_free_all(default_headers); + + http_cleanup(); + + request = request_queue_head; + while (request != NULL) { + next_request = request->next; + release_request(request); + request = next_request; + } + + return rc; +} |