diff --git a/Documentation/git-cvsimport.txt b/Documentation/git-cvsimport.txt index 57027b448f..b0c6d7c303 100644 --- a/Documentation/git-cvsimport.txt +++ b/Documentation/git-cvsimport.txt @@ -99,21 +99,24 @@ If you need to pass multiple options, separate them with a comma. CVS by default uses the unix username when writing its commit logs. Using this option and an author-conv-file in this format - ++ +--------- exon=Andreas Ericsson spawn=Simon Pawn - git-cvsimport will make it appear as those authors had - their GIT_AUTHOR_NAME and GIT_AUTHOR_EMAIL set properly - all along. - - For convenience, this data is saved to $GIT_DIR/cvs-authors - each time the -A option is provided and read from that same - file each time git-cvsimport is run. - - It is not recommended to use this feature if you intend to - export changes back to CVS again later with - git-link[1]::git-cvsexportcommit. +--------- ++ +git-cvsimport will make it appear as those authors had +their GIT_AUTHOR_NAME and GIT_AUTHOR_EMAIL set properly +all along. ++ +For convenience, this data is saved to $GIT_DIR/cvs-authors +each time the -A option is provided and read from that same +file each time git-cvsimport is run. ++ +It is not recommended to use this feature if you intend to +export changes back to CVS again later with +git-link[1]::git-cvsexportcommit. OUTPUT ------ diff --git a/Documentation/git-svnimport.txt b/Documentation/git-svnimport.txt index 9d3865719c..b1b87c2fcd 100644 --- a/Documentation/git-svnimport.txt +++ b/Documentation/git-svnimport.txt @@ -75,18 +75,21 @@ When importing incrementally, you might need to edit the .git/svn2git file. -A :: Read a file with lines on the form ++ +------ + username = User's Full Name - username = User's Full Name - - and use "User's Full Name " as the GIT - author and committer for Subversion commits made by - "username". If encountering a commit made by a user not in the - list, abort. - - For convenience, this data is saved to $GIT_DIR/svn-authors - each time the -A option is provided, and read from that same - file each time git-svnimport is run with an existing GIT - repository without -A. +------ ++ +and use "User's Full Name " as the GIT +author and committer for Subversion commits made by +"username". If encountering a commit made by a user not in the +list, abort. ++ +For convenience, this data is saved to $GIT_DIR/svn-authors +each time the -A option is provided, and read from that same +file each time git-svnimport is run with an existing GIT +repository without -A. -m:: Attempt to detect merges based on the commit message. This option diff --git a/cache.h b/cache.h index 1f962809b0..255e6b5cc7 100644 --- a/cache.h +++ b/cache.h @@ -165,6 +165,7 @@ extern void rollback_index_file(struct cache_file *); extern int trust_executable_bit; extern int assume_unchanged; extern int only_use_symrefs; +extern int warn_ambiguous_refs; extern int diff_rename_limit_default; extern int shared_repository; extern const char *apply_default_whitespace; diff --git a/config.c b/config.c index 7dbdce1966..95ec34923d 100644 --- a/config.c +++ b/config.c @@ -232,6 +232,11 @@ int git_default_config(const char *var, const char *value) return 0; } + if (!strcmp(var, "core.warnambiguousrefs")) { + warn_ambiguous_refs = git_config_bool(var, value); + return 0; + } + if (!strcmp(var, "user.name")) { strncpy(git_default_name, value, sizeof(git_default_name)); return 0; diff --git a/contrib/git-svn/git-svn.perl b/contrib/git-svn/git-svn.perl index cf233ef6ed..f3fc3ec1a9 100755 --- a/contrib/git-svn/git-svn.perl +++ b/contrib/git-svn/git-svn.perl @@ -850,11 +850,23 @@ sub assert_revision_unknown { } } +sub trees_eq { + my ($x, $y) = @_; + my @x = safe_qx('git-cat-file','commit',$x); + my @y = safe_qx('git-cat-file','commit',$y); + if (($y[0] ne $x[0]) || $x[0] !~ /^tree $sha1\n$/ + || $y[0] !~ /^tree $sha1\n$/) { + print STDERR "Trees not equal: $y[0] != $x[0]\n"; + return 0 + } + return 1; +} + sub assert_revision_eq_or_unknown { my ($revno, $commit) = @_; if (-f "$REV_DIR/$revno") { my $current = file_to_s("$REV_DIR/$revno"); - if ($commit ne $current) { + if (($commit ne $current) && !trees_eq($commit, $current)) { croak "$REV_DIR/$revno already exists!\n", "current: $current\nexpected: $commit\n"; } diff --git a/environment.c b/environment.c index 16c08f0697..5d29b92bd5 100644 --- a/environment.c +++ b/environment.c @@ -14,6 +14,7 @@ char git_default_name[MAX_GITNAME]; int trust_executable_bit = 1; int assume_unchanged = 0; int only_use_symrefs = 0; +int warn_ambiguous_refs = 0; int repository_format_version = 0; char git_commit_encoding[MAX_ENCODING_LENGTH] = "utf-8"; int shared_repository = 0; diff --git a/git-commit.sh b/git-commit.sh index 330a434b18..1e7c09e1f2 100755 --- a/git-commit.sh +++ b/git-commit.sh @@ -161,7 +161,7 @@ run_status () { } ' - if test -n "$verbose" + if test -n "$verbose" -a -z "$IS_INITIAL" then git-diff-index --cached -M -p --diff-filter=MDTCRA $REFERENCE fi diff --git a/http-fetch.c b/http-fetch.c index 7de818b109..dc67218ae7 100644 --- a/http-fetch.c +++ b/http-fetch.c @@ -8,6 +8,7 @@ #define RANGE_HEADER_SIZE 30 static int got_alternates = -1; +static int corrupt_object_found = 0; static struct curl_slist *no_pragma_header; @@ -834,6 +835,7 @@ static int fetch_object(struct alt_base *repo, unsigned char *sha1) obj_req->errorstr, obj_req->curl_result, obj_req->http_code, hex); } else if (obj_req->zret != Z_STREAM_END) { + corrupt_object_found++; ret = error("File %s (%s) corrupt", hex, obj_req->url); } else if (memcmp(obj_req->sha1, obj_req->real_sha1, 20)) { ret = error("File %s has bad hash", hex); @@ -993,5 +995,11 @@ int main(int argc, char **argv) http_cleanup(); + if (corrupt_object_found) { + fprintf(stderr, +"Some loose object were found to be corrupt, but they might be just\n" +"a false '404 Not Found' error message sent with incorrect HTTP\n" +"status code. Suggest running git fsck-objects.\n"); + } return rc; } diff --git a/http-push.c b/http-push.c index 42b0d59e8c..b35d400ee1 100644 --- a/http-push.c +++ b/http-push.c @@ -7,6 +7,7 @@ #include "http.h" #include "refs.h" #include "revision.h" +#include "exec_cmd.h" #include @@ -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,9 +66,12 @@ 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]; +static signed char remote_dir_exists[256]; static struct curl_slist *no_pragma_header; static struct curl_slist *default_headers; @@ -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 && iname, 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; diff --git a/sha1_name.c b/sha1_name.c index d67de18ba5..74c479c5e6 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -240,9 +240,13 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) "refs", "refs/tags", "refs/heads", + "refs/remotes", NULL }; const char **p; + const char *warning = "warning: refname '%.*s' is ambiguous.\n"; + char *pathname; + int already_found = 0; if (len == 40 && !get_sha1_hex(str, sha1)) return 0; @@ -252,10 +256,23 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) return -1; for (p = prefix; *p; p++) { - char *pathname = git_path("%s/%.*s", *p, len, str); - if (!read_ref(pathname, sha1)) - return 0; + unsigned char sha1_from_ref[20]; + unsigned char *this_result = + already_found ? sha1_from_ref : sha1; + pathname = git_path("%s/%.*s", *p, len, str); + if (!read_ref(pathname, this_result)) { + if (warn_ambiguous_refs) { + if (already_found && + !memcmp(sha1, sha1_from_ref, 20)) + fprintf(stderr, warning, len, str); + already_found++; + } + else + return 0; + } } + if (already_found) + return 0; return -1; }