summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/git-cvsimport.txt27
-rw-r--r--Documentation/git-svnimport.txt25
-rw-r--r--cache.h1
-rw-r--r--config.c5
-rwxr-xr-xcontrib/git-svn/git-svn.perl14
-rw-r--r--environment.c1
-rwxr-xr-xgit-commit.sh2
-rw-r--r--http-fetch.c8
-rw-r--r--http-push.c221
-rw-r--r--sha1_name.c23
10 files changed, 297 insertions, 30 deletions
diff --git a/Documentation/git-cvsimport.txt b/Documentation/git-cvsimport.txt
index 57027b4..b0c6d7c 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 <ae@op5.se>
spawn=Simon Pawn <spawn@frog-pond.org>
- 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 9d38657..b1b87c2 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 <author_file>::
Read a file with lines on the form
++
+------
+ username = User's Full Name <email@addr.es>
- username = User's Full Name <email@addr.es>
-
- and use "User's Full Name <email@addr.es>" 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 <email@addr.es>" 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 1f96280..255e6b5 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 7dbdce1..95ec349 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 cf233ef..f3fc3ec 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 16c08f0..5d29b92 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 330a434..1e7c09e 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 7de818b..dc67218 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 42b0d59..b35d400 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 <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,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 && 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;
diff --git a/sha1_name.c b/sha1_name.c
index d67de18..74c479c 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;
}