{"thread":{"id":"3684","subject":"[PATCH] http-push: add support for deleting remote branches","startedAt":"2006-03-20T18:31:06Z","lastAt":"2006-03-20T18:31:06Z","messageCount":1,"participants":["Nick Hengeveld"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"17713","messageId":"20060320183106.GL3997@reactrix.com","threadId":"3684","inReplyTo":null,"subject":"[PATCH] http-push: add support for deleting remote branches","fromName":"Nick Hengeveld","fromEmail":"nickh@reactrix.com","sentAt":"2006-03-20T18:31:06Z","receivedAt":"2006-03-20T18:31:06Z","isPatch":true,"sender":{"key":"nickh@reactrix.com","avatar":null},"body":"Processes new command-line arguments -d and -D to remove a remote branch\nif the following conditions are met:\n- one branch name is present on the command line\n- the specified branch name matches exactly one remote branch name\n- the remote HEAD is a symref\n- the specified branch is not the remote HEAD\n- the remote HEAD resolves to an object that exists locally (-d only)\n- the specified branch resolves to an object that exists locally (-d only)\n- the specified branch is an ancestor of the remote HEAD (-d only)\n\nSigned-off-by: Nick Hengeveld <nickh@reactrix.com>\n\n\n---\n\n http-push.c |  219 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 files changed, 218 insertions(+), 1 deletions(-)\n\n245873d765d8d380a146c7b77d09c1e9c2645daa\ndiff --git a/http-push.c b/http-push.c\nindex 42b0d59..e44310f 100644\n--- a/http-push.c\n+++ b/http-push.c\n@@ -7,6 +7,7 @@\n #include \"http.h\"\n #include \"refs.h\"\n #include \"revision.h\"\n+#include \"exec_cmd.h\"\n \n #include <expat.h>\n \n@@ -32,6 +33,7 @@ enum XML_Status {\n #define DAV_PROPFIND \"PROPFIND\"\n #define DAV_PUT \"PUT\"\n #define DAV_UNLOCK \"UNLOCK\"\n+#define DAV_DELETE \"DELETE\"\n \n /* DAV lock flags */\n #define DAV_PROP_LOCKWR (1u << 0)\n@@ -64,6 +66,9 @@ enum XML_Status {\n #define FETCHING (1u << 7)\n #define PUSHING  (1u << 8)\n \n+/* We allow \"recursive\" symbolic refs. Only within reason, though */\n+#define MAXDEPTH 5\n+\n static int pushing = 0;\n static int aborted = 0;\n static char remote_dir_exists[256];\n@@ -2103,6 +2108,197 @@ static int remote_exists(const char *pat\n \treturn -1;\n }\n \n+static void fetch_symref(char *path, char **symref, unsigned char *sha1)\n+{\n+\tchar *url;\n+\tstruct buffer buffer;\n+\tstruct active_request_slot *slot;\n+\tstruct slot_results results;\n+\n+\turl = xmalloc(strlen(remote->url) + strlen(path) + 1);\n+\tsprintf(url, \"%s%s\", remote->url, path);\n+\n+\tbuffer.size = 4096;\n+\tbuffer.posn = 0;\n+\tbuffer.buffer = xmalloc(buffer.size);\n+\n+\tslot = get_active_slot();\n+\tslot->results = &results;\n+\tcurl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);\n+\tcurl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);\n+\tcurl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);\n+\tcurl_easy_setopt(slot->curl, CURLOPT_URL, url);\n+\tif (start_active_slot(slot)) {\n+\t\trun_active_slot(slot);\n+\t\tif (results.curl_result != CURLE_OK) {\n+\t\t\tdie(\"Couldn't get %s for remote symref\\n%s\",\n+\t\t\t    url, curl_errorstr);\n+\t\t}\n+\t} else {\n+\t\tdie(\"Unable to start remote symref request\");\n+\t}\n+\tfree(url);\n+\n+\tif (*symref != NULL)\n+\t\tfree(*symref);\n+\t*symref = NULL;\n+\tmemset(sha1, 0, 20);\n+\n+\tif (buffer.posn == 0)\n+\t\treturn;\n+\n+\t/* If it's a symref, set the refname; otherwise try for a sha1 */\n+\tif (!strncmp((char *)buffer.buffer, \"ref: \", 5)) {\n+\t\t*symref = xcalloc(buffer.posn - 5, 1);\n+\t\tstrncpy(*symref, (char *)buffer.buffer + 5, buffer.posn - 6);\n+\t} else {\n+\t\tget_sha1_hex(buffer.buffer, sha1);\n+\t}\n+\n+\tfree(buffer.buffer);\n+}\n+\n+static int verify_merge_base(unsigned char *head_sha1, unsigned char *branch_sha1)\n+{\n+\tint pipe_fd[2];\n+\tpid_t merge_base_pid;\n+\tchar line[PATH_MAX + 20];\n+\tunsigned char merge_sha1[20];\n+\tint verified = 0;\n+\n+\tif (pipe(pipe_fd) < 0)\n+\t\tdie(\"Verify merge base: pipe failed\");\n+\n+\tmerge_base_pid = fork();\n+\tif (!merge_base_pid) {\n+\t\tstatic const char *args[] = {\n+\t\t\t\"merge-base\",\n+\t\t\t\"-a\",\n+\t\t\tNULL,\n+\t\t\tNULL,\n+\t\t\tNULL\n+\t\t};\n+\t\targs[2] = strdup(sha1_to_hex(head_sha1));\n+\t\targs[3] = sha1_to_hex(branch_sha1);\n+\n+\t\tdup2(pipe_fd[1], 1);\n+\t\tclose(pipe_fd[0]);\n+\t\tclose(pipe_fd[1]);\n+\t\texecv_git_cmd(args);\n+\t\tdie(\"merge-base setup failed\");\n+\t}\n+\tif (merge_base_pid < 0)\n+\t\tdie(\"merge-base fork failed\");\n+\n+\tdup2(pipe_fd[0], 0);\n+\tclose(pipe_fd[0]);\n+\tclose(pipe_fd[1]);\n+\twhile (fgets(line, sizeof(line), stdin) != NULL) {\n+\t\tif (get_sha1_hex(line, merge_sha1))\n+\t\t\tdie(\"expected sha1, got garbage:\\n %s\", line);\n+\t\tif (!memcmp(branch_sha1, merge_sha1, 20)) {\n+\t\t\tverified = 1;\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\treturn verified;\n+}\n+\n+static int delete_remote_branch(char *pattern, int force)\n+{\n+\tstruct ref *refs = remote_refs;\n+\tstruct ref *remote_ref = NULL;\n+\tunsigned char head_sha1[20];\n+\tchar *symref = NULL;\n+\tint match;\n+\tint patlen = strlen(pattern);\n+\tint i;\n+\tstruct active_request_slot *slot;\n+\tstruct slot_results results;\n+\tchar *url;\n+\n+\t/* Find the remote branch(es) matching the specified branch name */\n+\tfor (match = 0; refs; refs = refs->next) {\n+\t\tchar *name = refs->name;\n+\t\tint namelen = strlen(name);\n+\t\tif (namelen < patlen ||\n+\t\t    memcmp(name + namelen - patlen, pattern, patlen))\n+\t\t\tcontinue;\n+\t\tif (namelen != patlen && name[namelen - patlen - 1] != '/')\n+\t\t\tcontinue;\n+\t\tmatch++;\n+\t\tremote_ref = refs;\n+\t}\n+\tif (match == 0)\n+\t\treturn error(\"No remote branch matches %s\", pattern);\n+\tif (match != 1)\n+\t\treturn error(\"More than one remote branch matches %s\",\n+\t\t\t     pattern);\n+\n+\t/*\n+\t * Remote HEAD must be a symref (not exactly foolproof; a remote\n+\t * symlink to a symref will look like a symref)\n+\t */\n+\tfetch_symref(\"HEAD\", &symref, head_sha1);\n+\tif (!symref)\n+\t\treturn error(\"Remote HEAD is not a symref\");\n+\n+\t/* Remote branch must not be the remote HEAD */\n+\tfor (i=0; symref && i<MAXDEPTH; i++) {\n+\t\tif (!strcmp(remote_ref->name, symref))\n+\t\t\treturn error(\"Remote branch %s is the current HEAD\",\n+\t\t\t\t     remote_ref->name);\n+\t\tfetch_symref(symref, &symref, head_sha1);\n+\t}\n+\n+\t/* Run extra sanity checks if delete is not forced */\n+\tif (!force) {\n+\t\t/* Remote HEAD must resolve to a known object */\n+\t\tif (symref)\n+\t\t\treturn error(\"Remote HEAD symrefs too deep\");\n+\t\tif (is_zero_sha1(head_sha1))\n+\t\t\treturn error(\"Unable to resolve remote HEAD\");\n+\t\tif (!has_sha1_file(head_sha1))\n+\t\t\treturn error(\"Remote HEAD resolves to object %s\\nwhich does not exist locally, perhaps you need to fetch?\", sha1_to_hex(head_sha1));\n+\n+\t\t/* Remote branch must resolve to a known object */\n+\t\tif (is_zero_sha1(remote_ref->old_sha1))\n+\t\t\treturn error(\"Unable to resolve remote branch %s\",\n+\t\t\t\t     remote_ref->name);\n+\t\tif (!has_sha1_file(remote_ref->old_sha1))\n+\t\t\treturn 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));\n+\n+\t\t/* Remote branch must be an ancestor of remote HEAD */\n+\t\tif (!verify_merge_base(head_sha1, remote_ref->old_sha1)) {\n+\t\t\treturn 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);\n+\t\t}\n+\t}\n+\n+\t/* Send delete request */\n+\tfprintf(stderr, \"Removing remote branch '%s'\\n\", remote_ref->name);\n+\turl = xmalloc(strlen(remote->url) + strlen(remote_ref->name) + 1);\n+\tsprintf(url, \"%s%s\", remote->url, remote_ref->name);\n+\tslot = get_active_slot();\n+\tslot->results = &results;\n+\tcurl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);\n+\tcurl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);\n+\tcurl_easy_setopt(slot->curl, CURLOPT_URL, url);\n+\tcurl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_DELETE);\n+\tif (start_active_slot(slot)) {\n+\t\trun_active_slot(slot);\n+\t\tfree(url);\n+\t\tif (results.curl_result != CURLE_OK)\n+\t\t\treturn error(\"DELETE request failed (%d/%ld)\\n\",\n+\t\t\t\t     results.curl_result, results.http_code);\n+\t} else {\n+\t\tfree(url);\n+\t\treturn error(\"Unable to start DELETE request\");\n+\t}\n+\n+\treturn 0;\n+}\n+\n int main(int argc, char **argv)\n {\n \tstruct transfer_request *request;\n@@ -2112,6 +2308,8 @@ int main(int argc, char **argv)\n \tstruct remote_lock *ref_lock = NULL;\n \tstruct remote_lock *info_ref_lock = NULL;\n \tstruct rev_info revs;\n+\tint delete_branch = 0;\n+\tint force_delete = 0;\n \tint objects_to_send;\n \tint rc = 0;\n \tint i;\n@@ -2138,7 +2336,15 @@ int main(int argc, char **argv)\n \t\t\t\tpush_verbosely = 1;\n \t\t\t\tcontinue;\n \t\t\t}\n-\t\t\tusage(http_push_usage);\n+\t\t\tif (!strcmp(arg, \"-d\")) {\n+\t\t\t\tdelete_branch = 1;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\tif (!strcmp(arg, \"-D\")) {\n+\t\t\t\tdelete_branch = 1;\n+\t\t\t\tforce_delete = 1;\n+\t\t\t\tcontinue;\n+\t\t\t}\n \t\t}\n \t\tif (!remote->url) {\n \t\t\tremote->url = arg;\n@@ -2158,6 +2364,9 @@ int main(int argc, char **argv)\n \tif (!remote->url)\n \t\tusage(http_push_usage);\n \n+\tif (delete_branch && nr_refspec != 1)\n+\t\tdie(\"You must specify only one branch name when deleting a remote branch\");\n+\n \tmemset(remote_dir_exists, -1, 256);\n \n \thttp_init();\n@@ -2193,6 +2402,14 @@ int main(int argc, char **argv)\n \tfprintf(stderr, \"Fetching remote heads...\\n\");\n \tget_dav_remote_heads();\n \n+\t/* Remove a remote branch if -d or -D was specified */\n+\tif (delete_branch) {\n+\t\tif (delete_remote_branch(refspec[0], force_delete) == -1)\n+\t\t\tfprintf(stderr, \"Unable to delete remote branch %s\\n\",\n+\t\t\t\trefspec[0]);\n+\t\tgoto cleanup;\n+\t}\n+\n \t/* match them up */\n \tif (!remote_tail)\n \t\tremote_tail = &remote_refs;\n-- \n1.2.4.g55c6-dirty\n"}]}