http-push: add support for deleting remote branches
Processes new command-line arguments -d and -D to remove a remote branch if the following conditions are met: - one branch name is present on the command line - the specified branch name matches exactly one remote branch name - the remote HEAD is a symref - the specified branch is not the remote HEAD - the remote HEAD resolves to an object that exists locally (-d only) - the specified branch resolves to an object that exists locally (-d only) - the specified branch is an ancestor of the remote HEAD (-d only) Signed-off-by: Nick Hengeveld <nickh@reactrix.com> Signed-off-by: Junio C Hamano <junkio@cox.net>
This commit is contained in:
parent
1fa7a68f4b
commit
3dfaf7bcfd
219
http-push.c
219
http-push.c
@ -7,6 +7,7 @@
|
||||
#include "http.h"
|
||||
#include "refs.h"
|
||||
#include "revision.h"
|
||||
#include "exec_cmd.h"
|
||||
|
||||
#include <expat.h>
|
||||
|
||||
@ -32,6 +33,7 @@ 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)
|
||||
@ -64,6 +66,9 @@ enum XML_Status {
|
||||
#define FETCHING (1u << 7)
|
||||
#define PUSHING (1u << 8)
|
||||
|
||||
/* 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];
|
||||
@ -2103,6 +2108,197 @@ static int remote_exists(const char *path)
|
||||
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;
|
||||
@ -2112,6 +2308,8 @@ int main(int argc, char **argv)
|
||||
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;
|
||||
@ -2138,7 +2336,15 @@ 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) {
|
||||
remote->url = arg;
|
||||
@ -2158,6 +2364,9 @@ int main(int argc, char **argv)
|
||||
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();
|
||||
@ -2193,6 +2402,14 @@ int main(int argc, char **argv)
|
||||
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;
|
||||
|
Loading…
Reference in New Issue
Block a user