#include "blob.h"
#include "http.h"
#include "refs.h"
+#include "diff.h"
#include "revision.h"
+#include "exec_cmd.h"
#include <expat.h>
static const char http_push_usage[] =
-"git-http-push [--complete] [--force] [--verbose] <url> <ref> [<ref>...]\n";
+"git-http-push [--all] [--force] [--verbose] <remote> [<head>...]\n";
#ifndef XML_STATUS_OK
enum XML_Status {
#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;
return rc;
}
-static void check_locks()
+static void check_locks(void)
{
struct remote_lock *lock = remote->locks;
time_t current_time = time(NULL);
}
} else if (request->state == RUN_MOVE) {
if (request->curl_result == CURLE_OK) {
- fprintf(stderr, " sent %s\n",
- sha1_to_hex(request->obj->sha1));
+ if (push_verbosely)
+ fprintf(stderr, " sent %s\n",
+ sha1_to_hex(request->obj->sha1));
request->obj->flags |= REMOTE;
release_request(request);
} else {
step_active_slots();
}
-static void add_send_request(struct object *obj, struct remote_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;
if (remote_dir_exists[obj->sha1[0]] == -1)
get_remote_object_list(obj->sha1[0]);
if (obj->flags & (REMOTE | PUSHING))
- return;
+ return 0;
target = find_sha1_pack(obj->sha1, remote->packs);
if (target) {
obj->flags |= REMOTE;
- return;
+ return 0;
}
obj->flags |= PUSHING;
fill_active_slots();
step_active_slots();
+
+ return 1;
}
static int fetch_index(unsigned char *sha1)
struct slot_results results;
/* Don't use the index if the pack isn't there */
- url = xmalloc(strlen(remote->url) + 65);
- sprintf(url, "%s/objects/pack/pack-%s.pack", remote->url, hex);
+ url = xmalloc(strlen(remote->url) + 64);
+ sprintf(url, "%sobjects/pack/pack-%s.pack", remote->url, hex);
slot = get_active_slot();
slot->results = &results;
curl_easy_setopt(slot->curl, CURLOPT_URL, url);
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");
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;
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);
return NULL;
}
} else {
- fprintf(stderr, "Unable to start request\n");
+ fprintf(stderr, "Unable to start MKCOL request\n");
free(url);
return NULL;
}
}
}
} else {
- fprintf(stderr, "Unable to start request\n");
+ fprintf(stderr, "Unable to start LOCK request\n");
}
curl_slist_free_all(dav_headers);
}
}
} else {
- fprintf(stderr, "Unable to start request\n");
+ fprintf(stderr, "Unable to start PROPFIND request\n");
}
free(out_data);
return p;
}
-static void get_delta(struct rev_info *revs, struct remote_lock *lock)
+static int get_delta(struct rev_info *revs, struct remote_lock *lock)
{
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))
- add_send_request(&commit->object, lock);
+ count += add_send_request(&commit->object, lock);
}
for (pending = revs->pending_objects; pending; pending = pending->next) {
while (objects) {
if (!(objects->item->flags & UNINTERESTING))
- add_send_request(objects->item, lock);
+ count += add_send_request(objects->item, lock);
objects = objects->next;
}
+
+ return count;
}
static int update_remote(unsigned char *sha1, struct remote_lock *lock)
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);
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 = xcalloc(buffer.posn - 5, 1);
+ strncpy(*symref, (char *)buffer.buffer + 5, buffer.posn - 6);
+ } else {
+ get_sha1_hex(buffer.buffer, sha1);
+ }
+
+ free(buffer.buffer);
+}
+
+static int verify_merge_base(unsigned char *head_sha1, unsigned char *branch_sha1)
+{
+ int pipe_fd[2];
+ pid_t merge_base_pid;
+ char line[PATH_MAX + 20];
+ unsigned char merge_sha1[20];
+ int verified = 0;
+
+ if (pipe(pipe_fd) < 0)
+ die("Verify merge base: pipe failed");
+
+ merge_base_pid = fork();
+ if (!merge_base_pid) {
+ static const char *args[] = {
+ "merge-base",
+ "-a",
+ NULL,
+ NULL,
+ NULL
+ };
+ args[2] = strdup(sha1_to_hex(head_sha1));
+ args[3] = sha1_to_hex(branch_sha1);
+
+ dup2(pipe_fd[1], 1);
+ close(pipe_fd[0]);
+ close(pipe_fd[1]);
+ execv_git_cmd(args);
+ die("merge-base setup failed");
+ }
+ if (merge_base_pid < 0)
+ die("merge-base fork failed");
+
+ dup2(pipe_fd[0], 0);
+ close(pipe_fd[0]);
+ close(pipe_fd[1]);
+ while (fgets(line, sizeof(line), stdin) != NULL) {
+ if (get_sha1_hex(line, merge_sha1))
+ die("expected sha1, got garbage:\n %s", line);
+ if (!memcmp(branch_sha1, merge_sha1, 20)) {
+ verified = 1;
+ break;
+ }
+ }
+
+ return verified;
+}
+
+static int delete_remote_branch(char *pattern, int force)
+{
+ struct ref *refs = remote_refs;
+ struct ref *remote_ref = NULL;
+ unsigned char head_sha1[20];
+ char *symref = NULL;
+ int match;
+ int patlen = strlen(pattern);
+ int i;
+ struct active_request_slot *slot;
+ struct slot_results results;
+ char *url;
+
+ /* Find the remote branch(es) matching the specified branch name */
+ for (match = 0; refs; refs = refs->next) {
+ char *name = refs->name;
+ int namelen = strlen(name);
+ if (namelen < patlen ||
+ memcmp(name + namelen - patlen, pattern, patlen))
+ continue;
+ if (namelen != patlen && name[namelen - patlen - 1] != '/')
+ continue;
+ match++;
+ remote_ref = refs;
+ }
+ if (match == 0)
+ return error("No remote branch matches %s", pattern);
+ if (match != 1)
+ return error("More than one remote branch matches %s",
+ pattern);
+
+ /*
+ * Remote HEAD must be a symref (not exactly foolproof; a remote
+ * symlink to a symref will look like a symref)
+ */
+ fetch_symref("HEAD", &symref, head_sha1);
+ if (!symref)
+ return error("Remote HEAD is not a symref");
+
+ /* Remote branch must not be the remote HEAD */
+ for (i=0; symref && i<MAXDEPTH; i++) {
+ if (!strcmp(remote_ref->name, symref))
+ return error("Remote branch %s is the current HEAD",
+ remote_ref->name);
+ fetch_symref(symref, &symref, head_sha1);
+ }
+
+ /* Run extra sanity checks if delete is not forced */
+ if (!force) {
+ /* Remote HEAD must resolve to a known object */
+ if (symref)
+ return error("Remote HEAD symrefs too deep");
+ if (is_zero_sha1(head_sha1))
+ return error("Unable to resolve remote HEAD");
+ if (!has_sha1_file(head_sha1))
+ return error("Remote HEAD resolves to object %s\nwhich does not exist locally, perhaps you need to fetch?", sha1_to_hex(head_sha1));
+
+ /* Remote branch must resolve to a known object */
+ if (is_zero_sha1(remote_ref->old_sha1))
+ return error("Unable to resolve remote branch %s",
+ remote_ref->name);
+ if (!has_sha1_file(remote_ref->old_sha1))
+ return error("Remote branch %s resolves to object %s\nwhich does not exist locally, perhaps you need to fetch?", remote_ref->name, sha1_to_hex(remote_ref->old_sha1));
+
+ /* Remote branch must be an ancestor of remote HEAD */
+ if (!verify_merge_base(head_sha1, remote_ref->old_sha1)) {
+ return error("The branch '%s' is not a strict subset of your current HEAD.\nIf you are sure you want to delete it, run:\n\t'git http-push -D %s %s'", remote_ref->name, remote->url, pattern);
+ }
+ }
+
+ /* Send delete request */
+ fprintf(stderr, "Removing remote branch '%s'\n", remote_ref->name);
+ url = xmalloc(strlen(remote->url) + strlen(remote_ref->name) + 1);
+ sprintf(url, "%s%s", remote->url, remote_ref->name);
+ slot = get_active_slot();
+ slot->results = &results;
+ curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);
+ curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
+ curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+ curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_DELETE);
+ if (start_active_slot(slot)) {
+ run_active_slot(slot);
+ free(url);
+ if (results.curl_result != CURLE_OK)
+ return error("DELETE request failed (%d/%ld)\n",
+ results.curl_result, results.http_code);
+ } else {
+ free(url);
+ return error("Unable to start DELETE request");
+ }
+
+ return 0;
+}
+
int main(int argc, char **argv)
{
struct transfer_request *request;
struct 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 ret = 0;
- int new_refs = 0;
- struct ref *ref;
+ new_refs = 0;
for (ref = remote_refs; ref; ref = ref->next) {
char old_hex[60], *new_hex;
+ const char *commit_argv[4];
+ int commit_argc;
+ char *new_sha1_hex, *old_sha1_hex;
+
if (!ref->peer_ref)
continue;
if (!memcmp(ref->old_sha1, ref->peer_ref->new_sha1, 20)) {
"need to pull first?",
ref->name,
ref->peer_ref->name);
- ret = -2;
+ rc = -2;
continue;
}
}
memcpy(ref->new_sha1, ref->peer_ref->new_sha1, 20);
if (is_zero_sha1(ref->new_sha1)) {
error("cannot happen anymore");
- ret = -3;
+ rc = -3;
continue;
}
new_refs++;
}
/* 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) {
pushing = 0;
prepare_revision_walk(&revs);
mark_edges_uninteresting(revs.commits);
- get_delta(&revs, ref_lock);
+ objects_to_send = get_delta(&revs, ref_lock);
finish_all_active_slots();
/* Push missing objects to remote, this would be a
convenient time to pack them first if appropriate. */
pushing = 1;
+ if (objects_to_send)
+ fprintf(stderr, " sending %d objects\n",
+ objects_to_send);
fill_active_slots();
finish_all_active_slots();