#include "blob.h"
#include "http.h"
#include "refs.h"
+#include "diff.h"
#include "revision.h"
+#include "exec_cmd.h"
#include <expat.h>
#define DAV_PROPFIND "PROPFIND"
#define DAV_PUT "PUT"
#define DAV_UNLOCK "UNLOCK"
+#define DAV_DELETE "DELETE"
/* DAV lock flags */
#define DAV_PROP_LOCKWR (1u << 0)
#define LOCK_TIME 600
#define LOCK_REFRESH 30
-/* bits #0-4 in revision.h */
+/* bits #0-15 in revision.h */
-#define LOCAL (1u << 5)
-#define REMOTE (1u << 6)
-#define FETCHING (1u << 7)
-#define PUSHING (1u << 8)
+#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];
static struct curl_slist *no_pragma_header;
static struct curl_slist *default_headers;
finish_request(request);
}
+#ifdef USE_CURL_MULTI
static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb,
void *data)
{
struct transfer_request *request = (struct transfer_request *)data;
do {
ssize_t retval = write(request->local_fileno,
- ptr + posn, size - posn);
+ (char *) ptr + posn, size - posn);
if (retval < 0)
return posn;
posn += retval;
}
}
+static void start_mkcol(struct transfer_request *request)
+{
+ char *hex = sha1_to_hex(request->obj->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;
+ }
+}
+#endif
+
static void start_fetch_packed(struct transfer_request *request)
{
char *url;
}
}
-static void start_mkcol(struct transfer_request *request)
-{
- char *hex = sha1_to_hex(request->obj->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->obj->sha1);
return rc;
}
-static void check_locks()
+static void check_locks(void)
{
struct remote_lock *lock = remote->locks;
time_t current_time = time(NULL);
}
}
+#ifdef USE_CURL_MULTI
void fill_active_slots(void)
{
struct transfer_request *request = request_queue_head;
slot = slot->next;
}
}
+#endif
static void get_remote_object_list(unsigned char parent);
request->next = request_queue_head;
request_queue_head = request;
+#ifdef USE_CURL_MULTI
fill_active_slots();
step_active_slots();
+#endif
}
static int add_send_request(struct object *obj, struct remote_lock *lock)
request->next = request_queue_head;
request_queue_head = request;
+#ifdef USE_CURL_MULTI
fill_active_slots();
step_active_slots();
+#endif
return 1;
}
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;
static inline int needs_quote(int ch)
{
- switch (ch) {
- case '/': case '-': case '.':
- case 'A'...'Z': case 'a'...'z': case '0'...'9':
+ if (((ch >= 'A') && (ch <= 'Z'))
+ || ((ch >= 'a') && (ch <= 'z'))
+ || ((ch >= '0') && (ch <= '9'))
+ || (ch == '/')
+ || (ch == '-')
+ || (ch == '.'))
return 0;
- default:
- return 1;
- }
+ return 1;
}
static inline int hex(int v)
obj->flags |= REMOTE;
if (!object_list_contains(objects, obj))
- add_object(obj, &objects, NULL, "");
+ object_list_insert(obj, &objects);
}
static void handle_lockprop_ctx(struct xml_ctx *ctx, int tag_closed)
xml_start_tag(void *userData, const char *name, const char **atts)
{
struct xml_ctx *ctx = (struct xml_ctx *)userData;
- const char *c = index(name, ':');
+ const char *c = strchr(name, ':');
int new_len;
if (c == NULL)
xml_end_tag(void *userData, const char *name)
{
struct xml_ctx *ctx = (struct xml_ctx *)userData;
- const char *c = index(name, ':');
+ const char *c = strchr(name, ':');
char *ep;
ctx->userFunc(ctx, 1);
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);
+ ctx->cdata = xmalloc(len + 1);
+ strlcpy(ctx->cdata, s, len + 1);
}
static struct remote_lock *lock_remote(char *path, long timeout)
return;
path += 8;
obj_hex = xmalloc(strlen(path));
- strncpy(obj_hex, path, 2);
+ strlcpy(obj_hex, path, 3);
strcpy(obj_hex + 2, path + 3);
one_remote_object(obj_hex);
free(obj_hex);
return lock_flags;
}
+struct object_list **add_one_object(struct object *obj, struct object_list **p)
+{
+ struct object_list *entry = xmalloc(sizeof(struct object_list));
+ entry->item = obj;
+ entry->next = *p;
+ *p = entry;
+ return &entry->next;
+}
+
static struct object_list **process_blob(struct blob *blob,
struct object_list **p,
struct name_path *path,
return p;
obj->flags |= SEEN;
- return add_object(obj, p, path, name);
+ return add_one_object(obj, p);
}
static struct object_list **process_tree(struct tree *tree,
const char *name)
{
struct object *obj = &tree->object;
- struct tree_entry_list *entry;
+ struct tree_desc desc;
+ struct name_entry entry;
struct name_path me;
obj->flags |= LOCAL;
die("bad tree object %s", sha1_to_hex(obj->sha1));
obj->flags |= SEEN;
- p = add_object(obj, p, NULL, name);
+ name = strdup(name);
+ p = add_one_object(obj, p);
me.up = path;
me.elem = name;
me.elem_len = strlen(name);
- entry = tree->entries;
- tree->entries = NULL;
- while (entry) {
- struct tree_entry_list *next = entry->next;
- if (entry->directory)
- p = process_tree(entry->item.tree, p, &me, entry->name);
+
+ desc.buf = tree->buffer;
+ desc.size = tree->size;
+
+ while (tree_entry(&desc, &entry)) {
+ if (S_ISDIR(entry.mode))
+ p = process_tree(lookup_tree(entry.sha1), p, &me, name);
else
- p = process_blob(entry->item.blob, p, &me, entry->name);
- free(entry);
- entry = next;
+ p = process_blob(lookup_blob(entry.sha1), p, &me, name);
}
+ free(tree->buffer);
+ tree->buffer = NULL;
return p;
}
static int get_delta(struct rev_info *revs, struct remote_lock *lock)
{
+ int i;
struct commit *commit;
- struct object_list **p = &objects, *pending;
+ struct object_list **p = &objects;
int count = 0;
while ((commit = get_revision(revs)) != NULL) {
count += add_send_request(&commit->object, lock);
}
- for (pending = revs->pending_objects; pending; pending = pending->next) {
- struct object *obj = pending->item;
- const char *name = pending->name;
+ for (i = 0; i < revs->pending.nr; i++) {
+ struct object_array_entry *entry = revs->pending.objects + i;
+ struct object *obj = entry->item;
+ const char *name = entry->name;
if (obj->flags & (UNINTERESTING | SEEN))
continue;
- if (obj->type == tag_type) {
+ if (obj->type == TYPE_TAG) {
obj->flags |= SEEN;
- p = add_object(obj, p, NULL, name);
+ p = add_one_object(obj, p);
continue;
}
- if (obj->type == tree_type) {
+ if (obj->type == TYPE_TREE) {
p = process_tree((struct tree *)obj, p, NULL, name);
continue;
}
- if (obj->type == blob_type) {
+ if (obj->type == TYPE_BLOB) {
p = process_blob((struct blob *)obj, p, NULL, name);
continue;
}
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,
}
}
- int len = strlen(refname) + 1;
ref = xcalloc(1, sizeof(*ref) + len);
memcpy(ref->old_sha1, remote_sha1, 20);
memcpy(ref->name, refname, len);
* old. Otherwise we require --force.
*/
o = deref_tag(parse_object(old_sha1), NULL, 0);
- if (!o || o->type != commit_type)
+ if (!o || o->type != TYPE_COMMIT)
return 0;
old = (struct commit *) o;
o = deref_tag(parse_object(new_sha1), NULL, 0);
- if (!o || o->type != commit_type)
+ if (!o || o->type != TYPE_COMMIT)
return 0;
new = (struct commit *) o;
fwrite_buffer(ref_info, 1, len, buf);
free(ref_info);
- if (o->type == tag_type) {
+ if (o->type == TYPE_TAG) {
o = deref_tag(o, ls->dentry_name, 0);
if (o) {
len = strlen(ls->dentry_name) + 45;
char *if_header;
struct curl_slist *dav_headers = NULL;
- buffer.buffer = xmalloc(4096);
- memset(buffer.buffer, 0, 4096);
+ buffer.buffer = xcalloc(1, 4096);
buffer.size = 4096;
buffer.posn = 0;
remote_ls("refs/", (PROCESS_FILES | RECURSIVE),
return -1;
}
+static void fetch_symref(char *path, char **symref, unsigned char *sha1)
+{
+ char *url;
+ struct buffer buffer;
+ struct active_request_slot *slot;
+ struct slot_results results;
+
+ url = xmalloc(strlen(remote->url) + strlen(path) + 1);
+ sprintf(url, "%s%s", remote->url, path);
+
+ buffer.size = 4096;
+ buffer.posn = 0;
+ buffer.buffer = xmalloc(buffer.size);
+
+ slot = get_active_slot();
+ slot->results = &results;
+ curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
+ curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
+ curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
+ curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+ if (start_active_slot(slot)) {
+ run_active_slot(slot);
+ if (results.curl_result != CURLE_OK) {
+ die("Couldn't get %s for remote symref\n%s",
+ url, curl_errorstr);
+ }
+ } else {
+ die("Unable to start remote symref request");
+ }
+ free(url);
+
+ if (*symref != NULL)
+ free(*symref);
+ *symref = NULL;
+ memset(sha1, 0, 20);
+
+ if (buffer.posn == 0)
+ return;
+
+ /* If it's a symref, set the refname; otherwise try for a sha1 */
+ if (!strncmp((char *)buffer.buffer, "ref: ", 5)) {
+ *symref = xmalloc(buffer.posn - 5);
+ strlcpy(*symref, (char *)buffer.buffer + 5, buffer.posn - 5);
+ } else {
+ get_sha1_hex(buffer.buffer, sha1);
+ }
+
+ free(buffer.buffer);
+}
+
+static int verify_merge_base(unsigned char *head_sha1, unsigned char *branch_sha1)
+{
+ int pipe_fd[2];
+ pid_t merge_base_pid;
+ char line[PATH_MAX + 20];
+ unsigned char merge_sha1[20];
+ int verified = 0;
+
+ if (pipe(pipe_fd) < 0)
+ die("Verify merge base: pipe failed");
+
+ merge_base_pid = fork();
+ if (!merge_base_pid) {
+ static const char *args[] = {
+ "merge-base",
+ "-a",
+ NULL,
+ NULL,
+ NULL
+ };
+ args[2] = strdup(sha1_to_hex(head_sha1));
+ args[3] = sha1_to_hex(branch_sha1);
+
+ dup2(pipe_fd[1], 1);
+ close(pipe_fd[0]);
+ close(pipe_fd[1]);
+ execv_git_cmd(args);
+ die("merge-base setup failed");
+ }
+ if (merge_base_pid < 0)
+ die("merge-base fork failed");
+
+ dup2(pipe_fd[0], 0);
+ close(pipe_fd[0]);
+ close(pipe_fd[1]);
+ while (fgets(line, sizeof(line), stdin) != NULL) {
+ if (get_sha1_hex(line, merge_sha1))
+ die("expected sha1, got garbage:\n %s", line);
+ if (!memcmp(branch_sha1, merge_sha1, 20)) {
+ verified = 1;
+ break;
+ }
+ }
+
+ return verified;
+}
+
+static int delete_remote_branch(char *pattern, int force)
+{
+ struct ref *refs = remote_refs;
+ struct ref *remote_ref = NULL;
+ unsigned char head_sha1[20];
+ char *symref = NULL;
+ int match;
+ int patlen = strlen(pattern);
+ int i;
+ struct active_request_slot *slot;
+ struct slot_results results;
+ char *url;
+
+ /* Find the remote branch(es) matching the specified branch name */
+ for (match = 0; refs; refs = refs->next) {
+ char *name = refs->name;
+ int namelen = strlen(name);
+ if (namelen < patlen ||
+ memcmp(name + namelen - patlen, pattern, patlen))
+ continue;
+ if (namelen != patlen && name[namelen - patlen - 1] != '/')
+ continue;
+ match++;
+ remote_ref = refs;
+ }
+ if (match == 0)
+ return error("No remote branch matches %s", pattern);
+ if (match != 1)
+ return error("More than one remote branch matches %s",
+ pattern);
+
+ /*
+ * Remote HEAD must be a symref (not exactly foolproof; a remote
+ * symlink to a symref will look like a symref)
+ */
+ fetch_symref("HEAD", &symref, head_sha1);
+ if (!symref)
+ return error("Remote HEAD is not a symref");
+
+ /* Remote branch must not be the remote HEAD */
+ for (i=0; symref && i<MAXDEPTH; i++) {
+ if (!strcmp(remote_ref->name, symref))
+ return error("Remote branch %s is the current HEAD",
+ remote_ref->name);
+ fetch_symref(symref, &symref, head_sha1);
+ }
+
+ /* Run extra sanity checks if delete is not forced */
+ if (!force) {
+ /* Remote HEAD must resolve to a known object */
+ if (symref)
+ return error("Remote HEAD symrefs too deep");
+ if (is_zero_sha1(head_sha1))
+ return error("Unable to resolve remote HEAD");
+ if (!has_sha1_file(head_sha1))
+ return error("Remote HEAD resolves to object %s\nwhich does not exist locally, perhaps you need to fetch?", sha1_to_hex(head_sha1));
+
+ /* Remote branch must resolve to a known object */
+ if (is_zero_sha1(remote_ref->old_sha1))
+ return error("Unable to resolve remote branch %s",
+ remote_ref->name);
+ if (!has_sha1_file(remote_ref->old_sha1))
+ return error("Remote branch %s resolves to object %s\nwhich does not exist locally, perhaps you need to fetch?", remote_ref->name, sha1_to_hex(remote_ref->old_sha1));
+
+ /* Remote branch must be an ancestor of remote HEAD */
+ if (!verify_merge_base(head_sha1, remote_ref->old_sha1)) {
+ return error("The branch '%s' is not a strict subset of your current HEAD.\nIf you are sure you want to delete it, run:\n\t'git http-push -D %s %s'", remote_ref->name, remote->url, pattern);
+ }
+ }
+
+ /* Send delete request */
+ fprintf(stderr, "Removing remote branch '%s'\n", remote_ref->name);
+ url = xmalloc(strlen(remote->url) + strlen(remote_ref->name) + 1);
+ sprintf(url, "%s%s", remote->url, remote_ref->name);
+ slot = get_active_slot();
+ slot->results = &results;
+ curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);
+ curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
+ curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+ curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_DELETE);
+ if (start_active_slot(slot)) {
+ run_active_slot(slot);
+ free(url);
+ if (results.curl_result != CURLE_OK)
+ return error("DELETE request failed (%d/%ld)\n",
+ results.curl_result, results.http_code);
+ } else {
+ free(url);
+ return error("Unable to start DELETE request");
+ }
+
+ return 0;
+}
+
int main(int argc, char **argv)
{
struct transfer_request *request;
struct 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();
push_verbosely = 1;
continue;
}
- usage(http_push_usage);
+ if (!strcmp(arg, "-d")) {
+ delete_branch = 1;
+ continue;
+ }
+ if (!strcmp(arg, "-D")) {
+ delete_branch = 1;
+ force_delete = 1;
+ continue;
+ }
}
if (!remote->url) {
- remote->url = arg;
char *path = strstr(arg, "//");
+ remote->url = arg;
if (path) {
- path = index(path+2, '/');
+ path = strchr(path+2, '/');
if (path)
remote->path_len = strlen(path);
}
if (!remote->url)
usage(http_push_usage);
+ if (delete_branch && nr_refspec != 1)
+ die("You must specify only one branch name when deleting a remote branch");
+
memset(remote_dir_exists, -1, 256);
http_init();
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;
return 0;
}
- int new_refs = 0;
- struct ref *ref;
+ new_refs = 0;
for (ref = remote_refs; ref; ref = ref->next) {
char old_hex[60], *new_hex;
+ const char *commit_argv[4];
+ int commit_argc;
+ char *new_sha1_hex, *old_sha1_hex;
+
if (!ref->peer_ref)
continue;
if (!memcmp(ref->old_sha1, ref->peer_ref->new_sha1, 20)) {
}
/* Set up revision info for this refspec */
- const char *commit_argv[4];
- int commit_argc = 3;
- char *new_sha1_hex = strdup(sha1_to_hex(ref->new_sha1));
- char *old_sha1_hex = NULL;
+ commit_argc = 3;
+ new_sha1_hex = 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)) {
commit_argv[3] = old_sha1_hex;
commit_argc++;
}
+ init_revisions(&revs);
setup_revisions(commit_argc, commit_argv, &revs, NULL);
free(new_sha1_hex);
if (old_sha1_hex) {
if (objects_to_send)
fprintf(stderr, " sending %d objects\n",
objects_to_send);
+#ifdef USE_CURL_MULTI
fill_active_slots();
+#endif
finish_all_active_slots();
/* Update the remote branch if all went well */