summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/git-push.txt8
-rw-r--r--Makefile5
-rw-r--r--builtin-fetch.c10
-rw-r--r--builtin-ls-remote.c74
-rw-r--r--builtin-push.c14
-rw-r--r--builtin-send-pack.c652
-rw-r--r--builtin.h2
-rw-r--r--cache.h20
-rw-r--r--connect.c10
-rwxr-xr-xcontrib/examples/git-ls-remote.sh (renamed from git-ls-remote.sh)0
-rw-r--r--git.c3
-rw-r--r--http-push.c6
-rw-r--r--peek-remote.c73
-rw-r--r--receive-pack.c4
-rw-r--r--refs.c8
-rw-r--r--remote.c53
-rw-r--r--remote.h17
-rw-r--r--send-pack.c461
-rw-r--r--send-pack.h18
-rwxr-xr-xt/t5404-tracking-branches.sh53
-rwxr-xr-xt/t5405-send-pack-rewind.sh42
-rwxr-xr-xt/t5406-remote-rejects.sh24
-rwxr-xr-xt/t5512-ls-remote.sh52
-rwxr-xr-xt/t5517-push-mirror.sh228
-rw-r--r--transport.c70
-rw-r--r--transport.h9
26 files changed, 1285 insertions, 631 deletions
diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt
index 4a68aaba34..b8003c63c7 100644
--- a/Documentation/git-push.txt
+++ b/Documentation/git-push.txt
@@ -63,6 +63,14 @@ the remote repository.
Instead of naming each ref to push, specifies that all
refs under `$GIT_DIR/refs/heads/` be pushed.
+\--mirror::
+ Instead of naming each ref to push, specifies that all
+ refs under `$GIT_DIR/refs/heads/` and `$GIT_DIR/refs/tags/`
+ be mirrored to the remote repository. Newly created local
+ refs will be pushed to the remote end, locally updated refs
+ will be force updated on the remote end, and deleted refs
+ will be removed from the remote end.
+
\--dry-run::
Do everything except actually send the updates.
diff --git a/Makefile b/Makefile
index 8809b0ace6..ccf522adce 100644
--- a/Makefile
+++ b/Makefile
@@ -214,7 +214,6 @@ BASIC_LDFLAGS =
SCRIPT_SH = \
git-bisect.sh git-checkout.sh \
git-clone.sh git-commit.sh \
- git-ls-remote.sh \
git-merge-one-file.sh git-mergetool.sh git-parse-remote.sh \
git-pull.sh git-rebase.sh git-rebase--interactive.sh \
git-repack.sh git-request-pull.sh \
@@ -243,7 +242,7 @@ PROGRAMS = \
git-fast-import$X \
git-daemon$X \
git-merge-index$X git-mktag$X git-mktree$X git-patch-id$X \
- git-peek-remote$X git-receive-pack$X \
+ git-receive-pack$X \
git-send-pack$X git-shell$X \
git-show-index$X \
git-unpack-file$X \
@@ -350,6 +349,7 @@ BUILTIN_OBJS = \
builtin-log.o \
builtin-ls-files.o \
builtin-ls-tree.o \
+ builtin-ls-remote.o \
builtin-mailinfo.o \
builtin-mailsplit.o \
builtin-merge-base.o \
@@ -363,6 +363,7 @@ BUILTIN_OBJS = \
builtin-push.o \
builtin-read-tree.o \
builtin-reflog.o \
+ builtin-send-pack.o \
builtin-config.o \
builtin-rerere.o \
builtin-reset.o \
diff --git a/builtin-fetch.c b/builtin-fetch.c
index be9e3ea2bc..31e138eab8 100644
--- a/builtin-fetch.c
+++ b/builtin-fetch.c
@@ -31,7 +31,7 @@ static void unlock_pack_on_signal(int signo)
}
static void add_merge_config(struct ref **head,
- struct ref *remote_refs,
+ const struct ref *remote_refs,
struct branch *branch,
struct ref ***tail)
{
@@ -79,7 +79,7 @@ static struct ref *get_ref_map(struct transport *transport,
struct ref *ref_map = NULL;
struct ref **tail = &ref_map;
- struct ref *remote_refs = transport_get_remote_refs(transport);
+ const struct ref *remote_refs = transport_get_remote_refs(transport);
if (ref_count || tags) {
for (i = 0; i < ref_count; i++) {
@@ -424,12 +424,12 @@ static struct ref *find_non_local_tags(struct transport *transport,
struct path_list new_refs = { NULL, 0, 0, 1 };
char *ref_name;
int ref_name_len;
- unsigned char *ref_sha1;
- struct ref *tag_ref;
+ const unsigned char *ref_sha1;
+ const struct ref *tag_ref;
struct ref *rm = NULL;
struct ref *ref_map = NULL;
struct ref **tail = &ref_map;
- struct ref *ref;
+ const struct ref *ref;
for_each_ref(add_existing, &existing_refs);
for (ref = transport_get_remote_refs(transport); ref; ref = ref->next) {
diff --git a/builtin-ls-remote.c b/builtin-ls-remote.c
new file mode 100644
index 0000000000..56f3f88023
--- /dev/null
+++ b/builtin-ls-remote.c
@@ -0,0 +1,74 @@
+#include "builtin.h"
+#include "cache.h"
+#include "transport.h"
+#include "remote.h"
+
+static const char ls_remote_usage[] =
+"git-ls-remote [--upload-pack=<git-upload-pack>] [<host>:]<directory>";
+
+int cmd_ls_remote(int argc, const char **argv, const char *prefix)
+{
+ int i;
+ const char *dest = NULL;
+ int nongit = 0;
+ unsigned flags = 0;
+ const char *uploadpack = NULL;
+
+ struct remote *remote;
+ struct transport *transport;
+ const struct ref *ref;
+
+ setup_git_directory_gently(&nongit);
+
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+
+ if (*arg == '-') {
+ if (!prefixcmp(arg, "--upload-pack=")) {
+ uploadpack = arg + 14;
+ continue;
+ }
+ if (!prefixcmp(arg, "--exec=")) {
+ uploadpack = arg + 7;
+ continue;
+ }
+ if (!strcmp("--tags", arg)) {
+ flags |= REF_TAGS;
+ continue;
+ }
+ if (!strcmp("--heads", arg)) {
+ flags |= REF_HEADS;
+ continue;
+ }
+ if (!strcmp("--refs", arg)) {
+ flags |= REF_NORMAL;
+ continue;
+ }
+ usage(ls_remote_usage);
+ }
+ dest = arg;
+ break;
+ }
+
+ if (!dest || i != argc - 1)
+ usage(ls_remote_usage);
+
+ remote = nongit ? NULL : remote_get(dest);
+ if (remote && !remote->url_nr)
+ die("remote %s has no configured URL", dest);
+ transport = transport_get(remote, remote ? remote->url[0] : dest);
+ if (uploadpack != NULL)
+ transport_set_option(transport, TRANS_OPT_UPLOADPACK, uploadpack);
+
+ ref = transport_get_remote_refs(transport);
+
+ if (!ref)
+ return 1;
+
+ while (ref) {
+ if (check_ref_type(ref, flags))
+ printf("%s %s\n", sha1_to_hex(ref->old_sha1), ref->name);
+ ref = ref->next;
+ }
+ return 0;
+}
diff --git a/builtin-push.c b/builtin-push.c
index 6d1da07c46..41df717f84 100644
--- a/builtin-push.c
+++ b/builtin-push.c
@@ -10,7 +10,7 @@
#include "parse-options.h"
static const char * const push_usage[] = {
- "git-push [--all] [--dry-run] [--tags] [--receive-pack=<git-receive-pack>] [--repo=all] [-f | --force] [-v] [<repository> <refspec>...]",
+ "git-push [--all | --mirror] [--dry-run] [--tags] [--receive-pack=<git-receive-pack>] [--repo=all] [-f | --force] [-v] [<repository> <refspec>...]",
NULL,
};
@@ -91,6 +91,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
{
int flags = 0;
int all = 0;
+ int mirror = 0;
int dry_run = 0;
int force = 0;
int tags = 0;
@@ -100,6 +101,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
OPT__VERBOSE(&verbose),
OPT_STRING( 0 , "repo", &repo, "repository", "repository"),
OPT_BOOLEAN( 0 , "all", &all, "push all refs"),
+ OPT_BOOLEAN( 0 , "mirror", &mirror, "mirror all refs"),
OPT_BOOLEAN( 0 , "tags", &tags, "push tags"),
OPT_BOOLEAN( 0 , "dry-run", &dry_run, "dry run"),
OPT_BOOLEAN('f', "force", &force, "force updates"),
@@ -121,13 +123,21 @@ int cmd_push(int argc, const char **argv, const char *prefix)
add_refspec("refs/tags/*");
if (all)
flags |= TRANSPORT_PUSH_ALL;
+ if (mirror)
+ flags |= (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE);
if (argc > 0) {
repo = argv[0];
set_refspecs(argv + 1, argc - 1);
}
- if ((flags & TRANSPORT_PUSH_ALL) && refspec)
+ if ((flags & (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) && refspec)
usage_with_options(push_usage, options);
+ if ((flags & (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) ==
+ (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) {
+ error("--all and --mirror are incompatible");
+ usage_with_options(push_usage, options);
+ }
+
return do_push(repo, flags);
}
diff --git a/builtin-send-pack.c b/builtin-send-pack.c
new file mode 100644
index 0000000000..25ae1fe860
--- /dev/null
+++ b/builtin-send-pack.c
@@ -0,0 +1,652 @@
+#include "cache.h"
+#include "commit.h"
+#include "tag.h"
+#include "refs.h"
+#include "pkt-line.h"
+#include "run-command.h"
+#include "remote.h"
+#include "send-pack.h"
+
+static const char send_pack_usage[] =
+"git-send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\n"
+" --all and explicit <ref> specification are mutually exclusive.";
+
+static struct send_pack_args args = {
+ /* .receivepack = */ "git-receive-pack",
+};
+
+/*
+ * Make a pack stream and spit it out into file descriptor fd
+ */
+static int pack_objects(int fd, struct ref *refs)
+{
+ /*
+ * The child becomes pack-objects --revs; we feed
+ * the revision parameters to it via its stdin and
+ * let its stdout go back to the other end.
+ */
+ const char *argv[] = {
+ "pack-objects",
+ "--all-progress",
+ "--revs",
+ "--stdout",
+ NULL,
+ NULL,
+ };
+ struct child_process po;
+
+ if (args.use_thin_pack)
+ argv[4] = "--thin";
+ memset(&po, 0, sizeof(po));
+ po.argv = argv;
+ po.in = -1;
+ po.out = fd;
+ po.git_cmd = 1;
+ if (start_command(&po))
+ die("git-pack-objects failed (%s)", strerror(errno));
+
+ /*
+ * We feed the pack-objects we just spawned with revision
+ * parameters by writing to the pipe.
+ */
+ while (refs) {
+ char buf[42];
+
+ if (!is_null_sha1(refs->old_sha1) &&
+ has_sha1_file(refs->old_sha1)) {
+ memcpy(buf + 1, sha1_to_hex(refs->old_sha1), 40);
+ buf[0] = '^';
+ buf[41] = '\n';
+ if (!write_or_whine(po.in, buf, 42,
+ "send-pack: send refs"))
+ break;
+ }
+ if (!is_null_sha1(refs->new_sha1)) {
+ memcpy(buf, sha1_to_hex(refs->new_sha1), 40);
+ buf[40] = '\n';
+ if (!write_or_whine(po.in, buf, 41,
+ "send-pack: send refs"))
+ break;
+ }
+ refs = refs->next;
+ }
+
+ if (finish_command(&po))
+ return error("pack-objects died with strange error");
+ return 0;
+}
+
+static void unmark_and_free(struct commit_list *list, unsigned int mark)
+{
+ while (list) {
+ struct commit_list *temp = list;
+ temp->item->object.flags &= ~mark;
+ list = temp->next;
+ free(temp);
+ }
+}
+
+static int ref_newer(const unsigned char *new_sha1,
+ const unsigned char *old_sha1)
+{
+ struct object *o;
+ struct commit *old, *new;
+ struct commit_list *list, *used;
+ int found = 0;
+
+ /* Both new and old must be commit-ish and new is descendant of
+ * old. Otherwise we require --force.
+ */
+ o = deref_tag(parse_object(old_sha1), NULL, 0);
+ if (!o || o->type != OBJ_COMMIT)
+ return 0;
+ old = (struct commit *) o;
+
+ o = deref_tag(parse_object(new_sha1), NULL, 0);
+ if (!o || o->type != OBJ_COMMIT)
+ return 0;
+ new = (struct commit *) o;
+
+ if (parse_commit(new) < 0)
+ return 0;
+
+ used = list = NULL;
+ commit_list_insert(new, &list);
+ while (list) {
+ new = pop_most_recent_commit(&list, 1);
+ commit_list_insert(new, &used);
+ if (new == old) {
+ found = 1;
+ break;
+ }
+ }
+ unmark_and_free(list, 1);
+ unmark_and_free(used, 1);
+ return found;
+}
+
+static struct ref *local_refs, **local_tail;
+static struct ref *remote_refs, **remote_tail;
+
+static int one_local_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+{
+ struct ref *ref;
+ int len = strlen(refname) + 1;
+ ref = xcalloc(1, sizeof(*ref) + len);
+ hashcpy(ref->new_sha1, sha1);
+ memcpy(ref->name, refname, len);
+ *local_tail = ref;
+ local_tail = &ref->next;
+ return 0;
+}
+
+static void get_local_heads(void)
+{
+ local_tail = &local_refs;
+ for_each_ref(one_local_ref, NULL);
+}
+
+static int receive_status(int in, struct ref *refs)
+{
+ struct ref *hint;
+ char line[1000];
+ int ret = 0;
+ int len = packet_read_line(in, line, sizeof(line));
+ if (len < 10 || memcmp(line, "unpack ", 7))
+ return error("did not receive remote status");
+ if (memcmp(line, "unpack ok\n", 10)) {
+ char *p = line + strlen(line) - 1;
+ if (*p == '\n')
+ *p = '\0';
+ error("unpack failed: %s", line + 7);
+ ret = -1;
+ }
+ hint = NULL;
+ while (1) {
+ char *refname;
+ char *msg;
+ len = packet_read_line(in, line, sizeof(line));
+ if (!len)
+ break;
+ if (len < 3 ||
+ (memcmp(line, "ok ", 3) && memcmp(line, "ng ", 3))) {
+ fprintf(stderr, "protocol error: %s\n", line);
+ ret = -1;
+ break;
+ }
+
+ line[strlen(line)-1] = '\0';
+ refname = line + 3;
+ msg = strchr(refname, ' ');
+ if (msg)
+ *msg++ = '\0';
+
+ /* first try searching at our hint, falling back to all refs */
+ if (hint)
+ hint = find_ref_by_name(hint, refname);
+ if (!hint)
+ hint = find_ref_by_name(refs, refname);
+ if (!hint) {
+ warning("remote reported status on unknown ref: %s",
+ refname);
+ continue;
+ }
+ if (hint->status != REF_STATUS_EXPECTING_REPORT) {
+ warning("remote reported status on unexpected ref: %s",
+ refname);
+ continue;
+ }
+
+ if (line[0] == 'o' && line[1] == 'k')
+ hint->status = REF_STATUS_OK;
+ else {
+ hint->status = REF_STATUS_REMOTE_REJECT;
+ ret = -1;
+ }
+ if (msg)
+ hint->remote_status = xstrdup(msg);
+ /* start our next search from the next ref */
+ hint = hint->next;
+ }
+ return ret;
+}
+
+static void update_tracking_ref(struct remote *remote, struct ref *ref)
+{
+ struct refspec rs;
+
+ if (ref->status != REF_STATUS_OK)
+ return;
+
+ rs.src = ref->name;
+ rs.dst = NULL;
+
+ if (!remote_find_tracking(remote, &rs)) {
+ if (args.verbose)
+ fprintf(stderr, "updating local tracking ref '%s'\n", rs.dst);
+ if (ref->deletion) {
+ if (delete_ref(rs.dst, NULL))
+ error("Failed to delete");
+ } else
+ update_ref("update by push", rs.dst,
+ ref->new_sha1, NULL, 0, 0);
+ free(rs.dst);
+ }
+}
+
+static const char *prettify_ref(const struct ref *ref)
+{
+ const char *name = ref->name;
+ return name + (
+ !prefixcmp(name, "refs/heads/") ? 11 :
+ !prefixcmp(name, "refs/tags/") ? 10 :
+ !prefixcmp(name, "refs/remotes/") ? 13 :
+ 0);
+}
+
+#define SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
+
+static void print_ref_status(char flag, const char *summary, struct ref *to, struct ref *from, const char *msg)
+{
+ fprintf(stderr, " %c %-*s ", flag, SUMMARY_WIDTH, summary);
+ if (from)
+ fprintf(stderr, "%s -> %s", prettify_ref(from), prettify_ref(to));
+ else
+ fputs(prettify_ref(to), stderr);
+ if (msg) {
+ fputs(" (", stderr);
+ fputs(msg, stderr);
+ fputc(')', stderr);
+ }
+ fputc('\n', stderr);
+}
+
+static const char *status_abbrev(unsigned char sha1[20])
+{
+ const char *abbrev;
+ abbrev = find_unique_abbrev(sha1, DEFAULT_ABBREV);
+ return abbrev ? abbrev : sha1_to_hex(sha1);
+}
+
+static void print_ok_ref_status(struct ref *ref)
+{
+ if (ref->deletion)
+ print_ref_status('-', "[deleted]", ref, NULL, NULL);
+ else if (is_null_sha1(ref->old_sha1))
+ print_ref_status('*',
+ (!prefixcmp(ref->name, "refs/tags/") ? "[new tag]" :
+ "[new branch]"),
+ ref, ref->peer_ref, NULL);
+ else {
+ char quickref[84];
+ char type;
+ const char *msg;
+
+ strcpy(quickref, status_abbrev(ref->old_sha1));
+ if (ref->nonfastforward) {
+ strcat(quickref, "...");
+ type = '+';
+ msg = "forced update";
+ } else {
+ strcat(quickref, "..");
+ type = ' ';
+ msg = NULL;
+ }
+ strcat(quickref, status_abbrev(ref->new_sha1));
+
+ print_ref_status(type, quickref, ref, ref->peer_ref, msg);
+ }
+}
+
+static int print_one_push_status(struct ref *ref, const char *dest, int count)
+{
+ if (!count)
+ fprintf(stderr, "To %s\n", dest);
+
+ switch(ref->status) {
+ case REF_STATUS_NONE:
+ print_ref_status('X', "[no match]", ref, NULL, NULL);
+ break;
+ case REF_STATUS_REJECT_NODELETE:
+ print_ref_status('!', "[rejected]", ref, NULL,
+ "remote does not support deleting refs");
+ break;
+ case REF_STATUS_UPTODATE:
+ print_ref_status('=', "[up to date]", ref,
+ ref->peer_ref, NULL);
+ break;
+ case REF_STATUS_REJECT_NONFASTFORWARD:
+ print_ref_status('!', "[rejected]", ref, ref->peer_ref,
+ "non-fast forward");
+ break;
+ case REF_STATUS_REMOTE_REJECT:
+ print_ref_status('!', "[remote rejected]", ref,
+ ref->deletion ? NULL : ref->peer_ref,
+ ref->remote_status);
+ break;
+ case REF_STATUS_EXPECTING_REPORT:
+ print_ref_status('!', "[remote failure]", ref,
+ ref->deletion ? NULL : ref->peer_ref,
+ "remote failed to report status");
+ break;
+ case REF_STATUS_OK:
+ print_ok_ref_status(ref);
+ break;
+ }
+
+ return 1;
+}
+
+static void print_push_status(const char *dest, struct ref *refs)
+{
+ struct ref *ref;
+ int n = 0;
+
+ if (args.verbose) {
+ for (ref = refs; ref; ref = ref->next)
+ if (ref->status == REF_STATUS_UPTODATE)
+ n += print_one_push_status(ref, dest, n);
+ }
+
+ for (ref = refs; ref; ref = ref->next)
+ if (ref->status == REF_STATUS_OK)
+ n += print_one_push_status(ref, dest, n);
+
+ for (ref = refs; ref; ref = ref->next) {
+ if (ref->status != REF_STATUS_NONE &&
+ ref->status != REF_STATUS_UPTODATE &&
+ ref->status != REF_STATUS_OK)
+ n += print_one_push_status(ref, dest, n);
+ }
+}
+
+static int refs_pushed(struct ref *ref)
+{
+ for (; ref; ref = ref->next) {
+ switch(ref->status) {
+ case REF_STATUS_NONE:
+ case REF_STATUS_UPTODATE:
+ break;
+ default:
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int do_send_pack(int in, int out, struct remote *remote, const char *dest, int nr_refspec, const char **refspec)
+{
+ struct ref *ref;
+ int new_refs;
+ int ask_for_status_report = 0;
+ int allow_deleting_refs = 0;
+ int expect_status_report = 0;
+ int flags = MATCH_REFS_NONE;
+ int ret;
+
+ if (args.send_all)
+ flags |= MATCH_REFS_ALL;
+ if (args.send_mirror)
+ flags |= MATCH_REFS_MIRROR;
+
+ /* No funny business with the matcher */
+ remote_tail = get_remote_heads(in, &remote_refs, 0, NULL, REF_NORMAL);
+ get_local_heads();
+
+ /* Does the other end support the reporting? */
+ if (server_supports("report-status"))
+ ask_for_status_report = 1;
+ if (server_supports("delete-refs"))
+ allow_deleting_refs = 1;
+
+ /* match them up */
+ if (!remote_tail)
+ remote_tail = &remote_refs;
+ if (match_refs(local_refs, remote_refs, &remote_tail,
+ nr_refspec, refspec, flags))
+ return -1;
+
+ if (!remote_refs) {
+ fprintf(stderr, "No refs in common and none specified; doing nothing.\n"
+ "Perhaps you should specify a branch such as 'master'.\n");
+ return 0;
+ }
+
+ /*
+ * Finally, tell the other end!
+ */
+ new_refs = 0;
+ for (ref = remote_refs; ref; ref = ref->next) {
+ const unsigned char *new_sha1;
+
+ if (!ref->peer_ref) {
+ if (!args.send_mirror)
+ continue;
+ new_sha1 = null_sha1;
+ }
+ else
+ new_sha1 = ref->peer_ref->new_sha1;
+
+
+ ref->deletion = is_null_sha1(new_sha1);
+ if (ref->deletion && !allow_deleting_refs) {
+ ref->status = REF_STATUS_REJECT_NODELETE;
+ continue;
+ }
+ if (!ref->deletion &&
+ !hashcmp(ref->old_sha1, new_sha1)) {
+ ref->status = REF_STATUS_UPTODATE;
+ continue;
+ }
+
+ /* This part determines what can overwrite what.
+ * The rules are:
+ *
+ * (0) you can always use --force or +A:B notation to
+ * selectively force individual ref pairs.
+ *
+ * (1) if the old thing does not exist, it is OK.
+ *
+ * (2) if you do not have the old thing, you are not allowed
+ * to overwrite it; you would not know what you are losing
+ * otherwise.
+ *
+ * (3) if both new and old are commit-ish, and new is a
+ * descendant of old, it is OK.
+ *
+ * (4) regardless of all of the above, removing :B is
+ * always allowed.
+ */
+
+ ref->nonfastforward =
+ !ref->deletion &&
+ !is_null_sha1(ref->old_sha1) &&
+ (!has_sha1_file(ref->old_sha1)
+ || !ref_newer(new_sha1, ref->old_sha1));
+
+ if (ref->nonfastforward && !ref->force && !args.force_update) {
+ ref->status = REF_STATUS_REJECT_NONFASTFORWARD;
+ continue;
+ }
+
+ hashcpy(ref->new_sha1, new_sha1);
+ if (!ref->deletion)
+ new_refs++;
+
+ if (!args.dry_run) {
+ char *old_hex = sha1_to_hex(ref->old_sha1);
+ char *new_hex = sha1_to_hex(ref->new_sha1);
+
+ if (ask_for_status_report) {
+ packet_write(out, "%s %s %s%c%s",
+ old_hex, new_hex, ref->name, 0,
+ "report-status");
+ ask_for_status_report = 0;
+ expect_status_report = 1;
+ }
+ else
+ packet_write(out, "%s %s %s",
+ old_hex, new_hex, ref->name);
+ }
+ ref->status = expect_status_report ?
+ REF_STATUS_EXPECTING_REPORT :
+ REF_STATUS_OK;
+ }
+
+ packet_flush(out);
+ if (new_refs && !args.dry_run) {
+ if (pack_objects(out, remote_refs) < 0) {
+ close(out);
+ return -1;
+ }
+ }
+ close(out);
+
+ if (expect_status_report)
+ ret = receive_status(in, remote_refs);
+ else
+ ret = 0;
+
+ print_push_status(dest, remote_refs);
+
+ if (!args.dry_run && remote) {
+ for (ref = remote_refs; ref; ref = ref->next)
+ update_tracking_ref(remote, ref);
+ }
+
+ if (!refs_pushed(remote_refs))
+ fprintf(stderr, "Everything up-to-date\n");
+ if (ret < 0)
+ return ret;
+ for (ref = remote_refs; ref; ref = ref->next) {
+ switch (ref->status) {
+ case REF_STATUS_NONE:
+ case REF_STATUS_UPTODATE:
+ case REF_STATUS_OK:
+ break;
+ default:
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static void verify_remote_names(int nr_heads, const char **heads)
+{
+ int i;
+
+ for (i = 0; i < nr_heads; i++) {
+ const char *remote = strchr(heads[i], ':');
+
+ remote = remote ? (remote + 1) : heads[i];
+ switch (check_ref_format(remote)) {
+ case 0: /* ok */
+ case -2: /* ok but a single level -- that is fine for
+ * a match pattern.
+ */
+ case -3: /* ok but ends with a pattern-match character */
+ continue;
+ }
+ die("remote part of refspec is not a valid name in %s",
+ heads[i]);
+ }
+}
+
+int cmd_send_pack(int argc, const char **argv, const char *prefix)
+{
+ int i, nr_heads = 0;
+ const char **heads = NULL;
+ const char *remote_name = NULL;
+ struct remote *remote = NULL;
+ const char *dest = NULL;
+
+ argv++;
+ for (i = 1; i < argc; i++, argv++) {
+ const char *arg = *argv;
+
+ if (*arg == '-') {
+ if (!prefixcmp(arg, "--receive-pack=")) {
+ args.receivepack = arg + 15;
+ continue;
+ }
+ if (!prefixcmp(arg, "--exec=")) {
+ args.receivepack = arg + 7;
+ continue;
+ }
+ if (!prefixcmp(arg, "--remote=")) {
+ remote_name = arg + 9;
+ continue;
+ }
+ if (!strcmp(arg, "--all")) {
+ args.send_all = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--dry-run")) {
+ args.dry_run = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--mirror")) {
+ args.send_mirror = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--force")) {
+ args.force_update = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--verbose")) {
+ args.verbose = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--thin")) {
+ args.use_thin_pack = 1;
+ continue;
+ }
+ usage(send_pack_usage);
+ }
+ if (!dest) {
+ dest = arg;
+ continue;
+ }
+ heads = (const char **) argv;
+ nr_heads = argc - i;
+ break;
+ }
+ if (!dest)
+ usage(send_pack_usage);
+ /*
+ * --all and --mirror are incompatible; neither makes sense
+ * with any refspecs.
+ */
+ if ((heads && (args.send_all || args.send_mirror)) ||
+ (args.send_all && args.send_mirror))
+ usage(send_pack_usage);
+
+ if (remote_name) {
+ remote = remote_get(remote_name);
+ if (!remote_has_url(remote, dest)) {
+ die("Destination %s is not a uri for %s",
+ dest, remote_name);
+ }
+ }
+
+ return send_pack(&args, dest, remote, nr_heads, heads);
+}
+
+int send_pack(struct send_pack_args *my_args,
+ const char *dest, struct remote *remote,
+ int nr_heads, const char **heads)
+{
+ int fd[2], ret;
+ struct child_process *conn;
+
+ memcpy(&args, my_args, sizeof(args));
+
+ verify_remote_names(nr_heads, heads);
+
+ conn = git_connect(fd, dest, args.receivepack, args.verbose ? CONNECT_VERBOSE : 0);
+ ret = do_send_pack(fd[0], fd[1], remote, dest, nr_heads, heads);
+ close(fd[0]);
+ close(fd[1]);
+ ret |= finish_connect(conn);
+ return !!ret;
+}
diff --git a/builtin.h b/builtin.h
index 6ecf12be32..98ada7b7f7 100644
--- a/builtin.h
+++ b/builtin.h
@@ -49,6 +49,7 @@ extern int cmd_log(int argc, const char **argv, const char *prefix);
extern int cmd_log_reflog(int argc, const char **argv, const char *prefix);
extern int cmd_ls_files(int argc, const char **argv, const char *prefix);
extern int cmd_ls_tree(int argc, const char **argv, const char *prefix);
+extern int cmd_ls_remote(int argc, const char **argv, const char *prefix);
extern int cmd_mailinfo(int argc, const char **argv, const char *prefix);
extern int cmd_mailsplit(int argc, const char **argv, const char *prefix);
extern int cmd_merge_base(int argc, const char **argv, const char *prefix);
@@ -71,6 +72,7 @@ extern int cmd_rev_parse(int argc, const char **argv, const char *prefix);
extern int cmd_revert(int argc, const char **argv, const char *prefix);
extern int cmd_rm(int argc, const char **argv, const char *prefix);
extern int cmd_runstatus(int argc, const char **argv, const char *prefix);
+extern int cmd_send_pack(int argc, const char **argv, const char *prefix);
extern int cmd_shortlog(int argc, const char **argv, const char *prefix);
extern int cmd_show(int argc, const char **argv, const char *prefix);
extern int cmd_show_branch(int argc, const char **argv, const char *prefix);
diff --git a/cache.h b/cache.h
index a0b93770d4..aaa135bfde 100644
--- a/cache.h
+++ b/cache.h
@@ -500,8 +500,20 @@ struct ref {
struct ref *next;
unsigned char old_sha1[20];
unsigned char new_sha1[20];
- unsigned char force;
- unsigned char merge;
+ unsigned int force:1,
+ merge:1,
+ nonfastforward:1,
+ deletion:1;
+ enum {
+ REF_STATUS_NONE = 0,
+ REF_STATUS_OK,
+ REF_STATUS_REJECT_NONFASTFORWARD,
+ REF_STATUS_REJECT_NODELETE,
+ REF_STATUS_UPTODATE,
+ REF_STATUS_REMOTE_REJECT,
+ REF_STATUS_EXPECTING_REPORT,
+ } status;
+ char *remote_status;
struct ref *peer_ref; /* when renaming */
char name[FLEX_ARRAY]; /* more */
};
@@ -510,8 +522,10 @@ struct ref {
#define REF_HEADS (1u << 1)
#define REF_TAGS (1u << 2)
+extern struct ref *find_ref_by_name(struct ref *list, const char *name);
+
#define CONNECT_VERBOSE (1u << 0)
-extern struct child_process *git_connect(int fd[2], char *url, const char *prog, int flags);
+extern struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags);
extern int finish_connect(struct child_process *conn);
extern int path_match(const char *path, int nr, char **match);
extern int get_ack(int fd, unsigned char *result_sha1);
diff --git a/connect.c b/connect.c
index 44e423dafd..3aefd4ace5 100644
--- a/connect.c
+++ b/connect.c
@@ -36,6 +36,11 @@ static int check_ref(const char *name, int len, unsigned int flags)
return !(flags & ~REF_NORMAL);
}
+int check_ref_type(const struct ref *ref, int flags)
+{
+ return check_ref(ref->name, strlen(ref->name), flags);
+}
+
/*
* Read all the refs from the other end
*/
@@ -476,9 +481,10 @@ char *get_port(char *host)
*
* If it returns, the connect is successful; it just dies on errors.
*/
-struct child_process *git_connect(int fd[2], char *url,
+struct child_process *git_connect(int fd[2], const char *url_orig,
const char *prog, int flags)
{
+ char *url = xstrdup(url_orig);
char *host, *path = url;
char *end;
int c;
@@ -568,6 +574,7 @@ struct child_process *git_connect(int fd[2], char *url,
prog, path, 0,
target_host, 0);
free(target_host);
+ free(url);
if (free_path)
free(path);
return NULL;
@@ -619,6 +626,7 @@ struct child_process *git_connect(int fd[2], char *url,
fd[0] = conn->out; /* read from child's stdout */
fd[1] = conn->in; /* write to child's stdin */
strbuf_release(&cmd);
+ free(url);
if (free_path)
free(path);
return conn;
diff --git a/git-ls-remote.sh b/contrib/examples/git-ls-remote.sh
index fec70bbf88..fec70bbf88 100755
--- a/git-ls-remote.sh
+++ b/contrib/examples/git-ls-remote.sh
diff --git a/git.c b/git.c
index 517ef414f7..01bbbc7325 100644
--- a/git.c
+++ b/git.c
@@ -322,6 +322,7 @@ static void handle_internal_command(int argc, const char **argv)
{ "log", cmd_log, RUN_SETUP | USE_PAGER },
{ "ls-files", cmd_ls_files, RUN_SETUP },
{ "ls-tree", cmd_ls_tree, RUN_SETUP },
+ { "ls-remote", cmd_ls_remote },
{ "mailinfo", cmd_mailinfo },
{ "mailsplit", cmd_mailsplit },
{ "merge-base", cmd_merge_base, RUN_SETUP },
@@ -330,6 +331,7 @@ static void handle_internal_command(int argc, const char **argv)
{ "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE },
{ "name-rev", cmd_name_rev, RUN_SETUP },
{ "pack-objects", cmd_pack_objects, RUN_SETUP },
+ { "peek-remote", cmd_ls_remote },
{ "pickaxe", cmd_blame, RUN_SETUP },
{ "prune", cmd_prune, RUN_SETUP },
{ "prune-packed", cmd_prune_packed, RUN_SETUP },
@@ -344,6 +346,7 @@ static void handle_internal_command(int argc, const char **argv)
{ "revert", cmd_revert, RUN_SETUP | NEED_WORK_TREE },
{ "rm", cmd_rm, RUN_SETUP },
{ "runstatus", cmd_runstatus, RUN_SETUP | NEED_WORK_TREE },
+ { "send-pack", cmd_send_pack, RUN_SETUP },
{ "shortlog", cmd_shortlog, RUN_SETUP | USE_PAGER },
{ "show-branch", cmd_show_branch, RUN_SETUP },
{ "show", cmd_show, RUN_SETUP | USE_PAGER },
diff --git a/http-push.c b/http-push.c
index 9314621a11..66b81f1726 100644
--- a/http-push.c
+++ b/http-push.c
@@ -78,7 +78,7 @@ static struct curl_slist *no_pragma_header;
static struct curl_slist *default_headers;
static int push_verbosely;
-static int push_all;
+static int push_all = MATCH_REFS_NONE;
static int force_all;
static int dry_run;
@@ -2300,7 +2300,7 @@ int main(int argc, char **argv)
if (*arg == '-') {
if (!strcmp(arg, "--all")) {
- push_all = 1;
+ push_all = MATCH_REFS_ALL;
continue;
}
if (!strcmp(arg, "--force")) {
@@ -2393,7 +2393,7 @@ int main(int argc, char **argv)
if (!remote_tail)
remote_tail = &remote_refs;
if (match_refs(local_refs, remote_refs, &remote_tail,
- nr_refspec, refspec, push_all))
+ nr_refspec, (const char **) refspec, push_all))
return -1;
if (!remote_refs) {
fprintf(stderr, "No refs in common and none specified; doing nothing.\n");
diff --git a/peek-remote.c b/peek-remote.c
deleted file mode 100644
index 8d20f7c9c6..0000000000
--- a/peek-remote.c
+++ /dev/null
@@ -1,73 +0,0 @@
-#include "cache.h"
-#include "refs.h"
-#include "pkt-line.h"
-
-static const char peek_remote_usage[] =
-"git-peek-remote [--upload-pack=<git-upload-pack>] [<host>:]<directory>";
-static const char *uploadpack = "git-upload-pack";
-
-static int peek_remote(int fd[2], unsigned flags)
-{
- struct ref *ref;
-
- get_remote_heads(fd[0], &ref, 0, NULL, flags);
- packet_flush(fd[1]);
-
- while (ref) {
- printf("%s %s\n", sha1_to_hex(ref->old_sha1), ref->name);
- ref = ref->next;
- }
- return 0;
-}
-
-int main(int argc, char **argv)
-{
- int i, ret;
- char *dest = NULL;
- int fd[2];
- struct child_process *conn;
- int nongit = 0;
- unsigned flags = 0;
-
- setup_git_directory_gently(&nongit);
-
- for (i = 1; i < argc; i++) {
- char *arg = argv[i];
-
- if (*arg == '-') {
- if (!prefixcmp(arg, "--upload-pack=")) {
- uploadpack = arg + 14;
- continue;
- }
- if (!prefixcmp(arg, "--exec=")) {
- uploadpack = arg + 7;
- continue;
- }
- if (!strcmp("--tags", arg)) {
- flags |= REF_TAGS;
- continue;
- }
- if (!strcmp("--heads", arg)) {
- flags |= REF_HEADS;
- continue;
- }
- if (!strcmp("--refs", arg)) {
- flags |= REF_NORMAL;
- continue;
- }
- usage(peek_remote_usage);
- }
- dest = arg;
- break;
- }
-
- if (!dest || i != argc - 1)
- usage(peek_remote_usage);
-
- conn = git_connect(fd, dest, uploadpack, 0);
- ret = peek_remote(fd, flags);
- close(fd[0]);
- close(fd[1]);
- ret |= finish_connect(conn);
- return !!ret;
-}
diff --git a/receive-pack.c b/receive-pack.c
index 38e35c06b9..ed44b897f6 100644
--- a/receive-pack.c
+++ b/receive-pack.c
@@ -204,8 +204,6 @@ static const char *update(struct command *cmd)
error("failed to delete %s", name);
return "failed to delete";
}
- fprintf(stderr, "%s: %s -> deleted\n", name,
- sha1_to_hex(old_sha1));
return NULL; /* good */
}
else {
@@ -217,8 +215,6 @@ static const char *update(struct command *cmd)
if (write_ref_sha1(lock, new_sha1, "push")) {
return "failed to write"; /* error() already called */
}
- fprintf(stderr, "%s: %s -> %s\n", name,
- sha1_to_hex(old_sha1), sha1_to_hex(new_sha1));
return NULL; /* good */
}
}
diff --git a/refs.c b/refs.c
index ae532540cf..54ec98d153 100644
--- a/refs.c
+++ b/refs.c
@@ -1433,3 +1433,11 @@ int update_ref(const char *action, const char *refname,
}
return 0;
}
+
+struct ref *find_ref_by_name(struct ref *list, const char *name)
+{
+ for ( ; list; list = list->next)
+ if (!strcmp(list->name, name))
+ return list;
+ return NULL;
+}
diff --git a/remote.c b/remote.c
index bec2ba1adb..bb01059083 100644
--- a/remote.c
+++ b/remote.c
@@ -485,7 +485,7 @@ struct ref *alloc_ref(unsigned namelen)
return ret;
}
-static struct ref *copy_ref(struct ref *ref)
+static struct ref *copy_ref(const struct ref *ref)
{
struct ref *ret = xmalloc(sizeof(struct ref) + strlen(ref->name) + 1);
memcpy(ret, ref, sizeof(struct ref) + strlen(ref->name) + 1);
@@ -493,6 +493,18 @@ static struct ref *copy_ref(struct ref *ref)
return ret;
}
+struct ref *copy_ref_list(const struct ref *ref)
+{
+ struct ref *ret = NULL;
+ struct ref **tail = &ret;
+ while (ref) {
+ *tail = copy_ref(ref);
+ ref = ref->next;
+ tail = &((*tail)->next);
+ }
+ return ret;
+}
+
void free_refs(struct ref *ref)
{
struct ref *next;
@@ -684,14 +696,6 @@ static int match_explicit_refs(struct ref *src, struct ref *dst,
return -errs;
}
-static struct ref *find_ref_by_name(struct ref *list, const char *name)
-{
- for ( ; list; list = list->next)
- if (!strcmp(list->name, name))
- return list;
- return NULL;
-}
-
static const struct refspec *check_pattern_match(const struct refspec *rs,
int rs_nr,
const struct ref *src)
@@ -710,10 +714,12 @@ static const struct refspec *check_pattern_match(const struct refspec *rs,
* without thinking.
*/
int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
- int nr_refspec, char **refspec, int all)
+ int nr_refspec, const char **refspec, int flags)
{
struct refspec *rs =
parse_ref_spec(nr_refspec, (const char **) refspec);
+ int send_all = flags & MATCH_REFS_ALL;
+ int send_mirror = flags & MATCH_REFS_MIRROR;
if (match_explicit_refs(src, dst, dst_tail, rs, nr_refspec))
return -1;
@@ -730,7 +736,7 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
if (!pat)
continue;
}
- else if (prefixcmp(src->name, "refs/heads/"))
+ else if (!send_mirror && prefixcmp(src->name, "refs/heads/"))
/*
* "matching refs"; traditionally we pushed everything
* including refs outside refs/heads/ hierarchy, but
@@ -751,10 +757,13 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
if (dst_peer && dst_peer->peer_ref)
/* We're already sending something to this ref. */
goto free_name;
- if (!dst_peer && !nr_refspec && !all)
- /* Remote doesn't have it, and we have no
+
+ if (!dst_peer && !nr_refspec && !(send_all || send_mirror))
+ /*
+ * Remote doesn't have it, and we have no
* explicit pattern, and we don't have
- * --all. */
+ * --all nor --mirror.
+ */
goto free_name;
if (!dst_peer) {
/* Create a new one and link it */
@@ -810,10 +819,10 @@ int branch_merge_matches(struct branch *branch,
return ref_matches_abbrev(branch->merge[i]->src, refname);
}
-static struct ref *get_expanded_map(struct ref *remote_refs,
+static struct ref *get_expanded_map(const struct ref *remote_refs,
const struct refspec *refspec)
{
- struct ref *ref;
+ const struct ref *ref;
struct ref *ret = NULL;
struct ref **tail = &ret;
@@ -824,7 +833,7 @@ static struct ref *get_expanded_map(struct ref *remote_refs,
if (strchr(ref->name, '^'))
continue; /* a dereference item */
if (!prefixcmp(ref->name, refspec->src)) {
- char *match;
+ const char *match;
struct ref *cpy = copy_ref(ref);
match = ref->name + remote_prefix_len;
@@ -842,9 +851,9 @@ static struct ref *get_expanded_map(struct ref *remote_refs,
return ret;
}
-static struct ref *find_ref_by_name_abbrev(struct ref *refs, const char *name)
+static const struct ref *find_ref_by_name_abbrev(const struct ref *refs, const char *name)
{
- struct ref *ref;
+ const struct ref *ref;
for (ref = refs; ref; ref = ref->next) {
if (ref_matches_abbrev(name, ref->name))
return ref;
@@ -852,9 +861,9 @@ static struct ref *find_ref_by_name_abbrev(struct ref *refs, const char *name)
return NULL;
}
-struct ref *get_remote_ref(struct ref *remote_refs, const char *name)
+struct ref *get_remote_ref(const struct ref *remote_refs, const char *name)
{
- struct ref *ref = find_ref_by_name_abbrev(remote_refs, name);
+ const struct ref *ref = find_ref_by_name_abbrev(remote_refs, name);
if (!ref)
return NULL;
@@ -887,7 +896,7 @@ static struct ref *get_local_ref(const char *name)
return ret;
}
-int get_fetch_map(struct ref *remote_refs,
+int get_fetch_map(const struct ref *remote_refs,
const struct refspec *refspec,
struct ref ***tail,
int missing_ok)
diff --git a/remote.h b/remote.h
index 878b4ecc32..b10036cae6 100644
--- a/remote.h
+++ b/remote.h
@@ -44,6 +44,10 @@ struct refspec {
struct ref *alloc_ref(unsigned namelen);
+struct ref *copy_ref_list(const struct ref *ref);
+
+int check_ref_type(const struct ref *ref, int flags);
+
/*
* Frees the entire list and peers of elements.
*/
@@ -57,7 +61,7 @@ void ref_remove_duplicates(struct ref *ref_map);
struct refspec *parse_ref_spec(int nr_refspec, const char **refspec);
int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
- int nr_refspec, char **refspec, int all);
+ int nr_refspec, const char **refspec, int all);
/*
* Given a list of the remote refs and the specification of things to
@@ -71,10 +75,10 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
* missing_ok is usually false, but when we are adding branch.$name.merge
* it is Ok if the branch is not at the remote anymore.
*/
-int get_fetch_map(struct ref *remote_refs, const struct refspec *refspec,
+int get_fetch_map(const struct ref *remote_refs, const struct refspec *refspec,
struct ref ***tail, int missing_ok);
-struct ref *get_remote_ref(struct ref *remote_refs, const char *name);
+struct ref *get_remote_ref(const struct ref *remote_refs, const char *name);
/*
* For the given remote, reads the refspec's src and sets the other fields.
@@ -98,4 +102,11 @@ struct branch *branch_get(const char *name);
int branch_has_merge_config(struct branch *branch);
int branch_merge_matches(struct branch *, int n, const char *);
+/* Flags to match_refs. */
+enum match_refs_flags {
+ MATCH_REFS_NONE = 0,
+ MATCH_REFS_ALL = (1 << 0),
+ MATCH_REFS_MIRROR = (1 << 1),
+};
+
#endif
diff --git a/send-pack.c b/send-pack.c
deleted file mode 100644
index b74fd454f2..0000000000
--- a/send-pack.c
+++ /dev/null
@@ -1,461 +0,0 @@
-#include "cache.h"
-#include "commit.h"
-#include "tag.h"
-#include "refs.h"
-#include "pkt-line.h"
-#include "run-command.h"
-#include "remote.h"
-
-static const char send_pack_usage[] =
-"git-send-pack [--all] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\n"
-" --all and explicit <ref> specification are mutually exclusive.";
-static const char *receivepack = "git-receive-pack";
-static int verbose;
-static int send_all;
-static int force_update;
-static int use_thin_pack;
-static int dry_run;
-
-/*
- * Make a pack stream and spit it out into file descriptor fd
- */
-static int pack_objects(int fd, struct ref *refs)
-{
- /*
- * The child becomes pack-objects --revs; we feed
- * the revision parameters to it via its stdin and
- * let its stdout go back to the other end.
- */
- const char *args[] = {
- "pack-objects",
- "--all-progress",
- "--revs",
- "--stdout",
- NULL,
- NULL,
- };
- struct child_process po;
-
- if (use_thin_pack)
- args[4] = "--thin";
- memset(&po, 0, sizeof(po));
- po.argv = args;
- po.in = -1;
- po.out = fd;
- po.git_cmd = 1;
- if (start_command(&po))
- die("git-pack-objects failed (%s)", strerror(errno));
-
- /*
- * We feed the pack-objects we just spawned with revision
- * parameters by writing to the pipe.
- */
- while (refs) {
- char buf[42];
-
- if (!is_null_sha1(refs->old_sha1) &&
- has_sha1_file(refs->old_sha1)) {
- memcpy(buf + 1, sha1_to_hex(refs->old_sha1), 40);
- buf[0] = '^';
- buf[41] = '\n';
- if (!write_or_whine(po.in, buf, 42,
- "send-pack: send refs"))
- break;
- }
- if (!is_null_sha1(refs->new_sha1)) {
- memcpy(buf, sha1_to_hex(refs->new_sha1), 40);
- buf[40] = '\n';
- if (!write_or_whine(po.in, buf, 41,
- "send-pack: send refs"))
- break;
- }
- refs = refs->next;
- }
-
- if (finish_command(&po))
- return error("pack-objects died with strange error");
- return 0;
-}
-
-static void unmark_and_free(struct commit_list *list, unsigned int mark)
-{
- while (list) {
- struct commit_list *temp = list;
- temp->item->object.flags &= ~mark;
- list = temp->next;
- free(temp);
- }
-}
-
-static int ref_newer(const unsigned char *new_sha1,
- const unsigned char *old_sha1)
-{
- struct object *o;
- struct commit *old, *new;
- struct commit_list *list, *used;
- int found = 0;
-
- /* Both new and old must be commit-ish and new is descendant of
- * old. Otherwise we require --force.
- */
- o = deref_tag(parse_object(old_sha1), NULL, 0);
- if (!o || o->type != OBJ_COMMIT)
- return 0;
- old = (struct commit *) o;
-
- o = deref_tag(parse_object(new_sha1), NULL, 0);
- if (!o || o->type != OBJ_COMMIT)
- return 0;
- new = (struct commit *) o;
-
- if (parse_commit(new) < 0)
- return 0;
-
- used = list = NULL;
- commit_list_insert(new, &list);
- while (list) {
- new = pop_most_recent_commit(&list, 1);
- commit_list_insert(new, &used);
- if (new == old) {
- found = 1;
- break;
- }
- }
- unmark_and_free(list, 1);
- unmark_and_free(used, 1);
- return found;
-}
-
-static struct ref *local_refs, **local_tail;
-static struct ref *remote_refs, **remote_tail;
-
-static int one_local_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
-{
- struct ref *ref;
- int len = strlen(refname) + 1;
- ref = xcalloc(1, sizeof(*ref) + len);
- hashcpy(ref->new_sha1, sha1);
- memcpy(ref->name, refname, len);
- *local_tail = ref;
- local_tail = &ref->next;
- return 0;
-}
-
-static void get_local_heads(void)
-{
- local_tail = &local_refs;
- for_each_ref(one_local_ref, NULL);
-}
-
-static int receive_status(int in)
-{
- char line[1000];
- int ret = 0;
- int len = packet_read_line(in, line, sizeof(line));
- if (len < 10 || memcmp(line, "unpack ", 7)) {
- fprintf(stderr, "did not receive status back\n");
- return -1;
- }
- if (memcmp(line, "unpack ok\n", 10)) {
- fputs(line, stderr);
- ret = -1;
- }
- while (1) {
- len = packet_read_line(in, line, sizeof(line));
- if (!len)
- break;
- if (len < 3 ||
- (memcmp(line, "ok", 2) && memcmp(line, "ng", 2))) {
- fprintf(stderr, "protocol error: %s\n", line);
- ret = -1;
- break;
- }
- if (!memcmp(line, "ok", 2))
- continue;
- fputs(line, stderr);
- ret = -1;
- }
- return ret;
-}
-
-static void update_tracking_ref(struct remote *remote, struct ref *ref)
-{
- struct refspec rs;
- int will_delete_ref;
-
- rs.src = ref->name;
- rs.dst = NULL;
-
- if (!ref->peer_ref)
- return;
-
- will_delete_ref = is_null_sha1(ref->peer_ref->new_sha1);
-
- if (!will_delete_ref &&
- !hashcmp(ref->old_sha1, ref->peer_ref->new_sha1))
- return;
-
- if (!remote_find_tracking(remote, &rs)) {
- fprintf(stderr, "updating local tracking ref '%s'\n", rs.dst);
- if (is_null_sha1(ref->peer_ref->new_sha1)) {
- if (delete_ref(rs.dst, NULL))
- error("Failed to delete");
- } else
- update_ref("update by push", rs.dst,
- ref->new_sha1, NULL, 0, 0);
- free(rs.dst);
- }
-}
-
-static int send_pack(int in, int out, struct remote *remote, int nr_refspec, char **refspec)
-{
- struct ref *ref;
- int new_refs;
- int ret = 0;
- int ask_for_status_report = 0;
- int allow_deleting_refs = 0;
- int expect_status_report = 0;
-
- /* No funny business with the matcher */
- remote_tail = get_remote_heads(in, &remote_refs, 0, NULL, REF_NORMAL);
- get_local_heads();
-
- /* Does the other end support the reporting? */
- if (server_supports("report-status"))
- ask_for_status_report = 1;
- if (server_supports("delete-refs"))
- allow_deleting_refs = 1;
-
- /* match them up */
- if (!remote_tail)
- remote_tail = &remote_refs;
- if (match_refs(local_refs, remote_refs, &remote_tail,
- nr_refspec, refspec, send_all))
- return -1;
-
- if (!remote_refs) {
- fprintf(stderr, "No refs in common and none specified; doing nothing.\n"
- "Perhaps you should specify a branch such as 'master'.\n");
- return 0;
- }
-
- /*
- * Finally, tell the other end!
- */
- new_refs = 0;
- for (ref = remote_refs; ref; ref = ref->next) {
- char old_hex[60], *new_hex;
- int will_delete_ref;
-
- if (!ref->peer_ref)
- continue;
-
-
- will_delete_ref = is_null_sha1(ref->peer_ref->new_sha1);
- if (will_delete_ref && !allow_deleting_refs) {
- error("remote does not support deleting refs");
- ret = -2;
- continue;
- }
- if (!will_delete_ref &&
- !hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) {
- if (verbose)
- fprintf(stderr, "'%s': up-to-date\n", ref->name);
- continue;
- }
-
- /* This part determines what can overwrite what.
- * The rules are:
- *
- * (0) you can always use --force or +A:B notation to
- * selectively force individual ref pairs.
- *
- * (1) if the old thing does not exist, it is OK.
- *
- * (2) if you do not have the old thing, you are not allowed
- * to overwrite it; you would not know what you are losing
- * otherwise.
- *
- * (3) if both new and old are commit-ish, and new is a
- * descendant of old, it is OK.
- *
- * (4) regardless of all of the above, removing :B is
- * always allowed.
- */
-
- if (!force_update &&
- !will_delete_ref &&
- !is_null_sha1(ref->old_sha1) &&
- !ref->force) {
- if (!has_sha1_file(ref->old_sha1) ||
- !ref_newer(ref->peer_ref->new_sha1,
- ref->old_sha1)) {
- /* We do not have the remote ref, or
- * we know that the remote ref is not
- * an ancestor of what we are trying to
- * push. Either way this can be losing
- * commits at the remote end and likely
- * we were not up to date to begin with.
- */
- error("remote '%s' is not an ancestor of\n"
- " local '%s'.\n"
- " Maybe you are not up-to-date and "
- "need to pull first?",
- ref->name,
- ref->peer_ref->name);
- ret = -2;
- continue;
- }
- }
- hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
- if (!will_delete_ref)
- new_refs++;
- strcpy(old_hex, sha1_to_hex(ref->old_sha1));
- new_hex = sha1_to_hex(ref->new_sha1);
-
- if (!dry_run) {
- if (ask_for_status_report) {
- packet_write(out, "%s %s %s%c%s",
- old_hex, new_hex, ref->name, 0,
- "report-status");
- ask_for_status_report = 0;
- expect_status_report = 1;
- }
- else
- packet_write(out, "%s %s %s",
- old_hex, new_hex, ref->name);
- }
- if (will_delete_ref)
- fprintf(stderr, "deleting '%s'\n", ref->name);
- else {
- fprintf(stderr, "updating '%s'", ref->name);
- if (strcmp(ref->name, ref->peer_ref->name))
- fprintf(stderr, " using '%s'",
- ref->peer_ref->name);
- fprintf(stderr, "\n from %s\n to %s\n",
- old_hex, new_hex);
- }
- }
-
- packet_flush(out);
- if (new_refs && !dry_run)
- ret = pack_objects(out, remote_refs);
- close(out);
-
- if (expect_status_report) {
- if (receive_status(in))
- ret = -4;
- }
-
- if (!dry_run && remote && ret == 0) {
- for (ref = remote_refs; ref; ref = ref->next)
- update_tracking_ref(remote, ref);
- }
-
- if (!new_refs && ret == 0)
- fprintf(stderr, "Everything up-to-date\n");
- return ret;
-}
-
-static void verify_remote_names(int nr_heads, char **heads)
-{
- int i;
-
- for (i = 0; i < nr_heads; i++) {
- const char *remote = strchr(heads[i], ':');
-
- remote = remote ? (remote + 1) : heads[i];
- switch (check_ref_format(remote)) {
- case 0: /* ok */
- case -2: /* ok but a single level -- that is fine for
- * a match pattern.
- */
- case -3: /* ok but ends with a pattern-match character */
- continue;
- }
- die("remote part of refspec is not a valid name in %s",
- heads[i]);
- }
-}
-
-int main(int argc, char **argv)
-{
- int i, nr_heads = 0;
- char *dest = NULL;
- char **heads = NULL;
- int fd[2], ret;
- struct child_process *conn;
- char *remote_name = NULL;
- struct remote *remote = NULL;
-
- setup_git_directory();
- git_config(git_default_config);
-
- argv++;
- for (i = 1; i < argc; i++, argv++) {
- char *arg = *argv;
-
- if (*arg == '-') {
- if (!prefixcmp(arg, "--receive-pack=")) {
- receivepack = arg + 15;
- continue;
- }
- if (!prefixcmp(arg, "--exec=")) {
- receivepack = arg + 7;
- continue;
- }
- if (!prefixcmp(arg, "--remote=")) {
- remote_name = arg + 9;
- continue;
- }
- if (!strcmp(arg, "--all")) {
- send_all = 1;
- continue;
- }
- if (!strcmp(arg, "--dry-run")) {
- dry_run = 1;
- continue;
- }
- if (!strcmp(arg, "--force")) {
- force_update = 1;
- continue;
- }
- if (!strcmp(arg, "--verbose")) {
- verbose = 1;
- continue;
- }
- if (!strcmp(arg, "--thin")) {
- use_thin_pack = 1;
- continue;
- }
- usage(send_pack_usage);
- }
- if (!dest) {
- dest = arg;
- continue;
- }
- heads = argv;
- nr_heads = argc - i;
- break;
- }
- if (!dest)
- usage(send_pack_usage);
- if (heads && send_all)
- usage(send_pack_usage);
- verify_remote_names(nr_heads, heads);
-
- if (remote_name) {
- remote = remote_get(remote_name);
- if (!remote_has_url(remote, dest)) {
- die("Destination %s is not a uri for %s",
- dest, remote_name);
- }
- }
-
- conn = git_connect(fd, dest, receivepack, verbose ? CONNECT_VERBOSE : 0);
- ret = send_pack(fd[0], fd[1], remote, nr_heads, heads);
- close(fd[0]);
- close(fd[1]);
- ret |= finish_connect(conn);
- return !!ret;
-}
diff --git a/send-pack.h b/send-pack.h
new file mode 100644
index 0000000000..8ff1dc3539
--- /dev/null
+++ b/send-pack.h
@@ -0,0 +1,18 @@
+#ifndef SEND_PACK_H
+#define SEND_PACK_H
+
+struct send_pack_args {
+ const char *receivepack;
+ unsigned verbose:1,
+ send_all:1,
+ send_mirror:1,
+ force_update:1,
+ use_thin_pack:1,
+ dry_run:1;
+};
+
+int send_pack(struct send_pack_args *args,
+ const char *dest, struct remote *remote,
+ int nr_heads, const char **heads);
+
+#endif
diff --git a/t/t5404-tracking-branches.sh b/t/t5404-tracking-branches.sh
new file mode 100755
index 0000000000..1493a92c06
--- /dev/null
+++ b/t/t5404-tracking-branches.sh
@@ -0,0 +1,53 @@
+#!/bin/sh
+
+test_description='tracking branch update checks for git push'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ echo 1 >file &&
+ git add file &&
+ git commit -m 1 &&
+ git branch b1 &&
+ git branch b2 &&
+ git clone . aa &&
+ git checkout b1 &&
+ echo b1 >>file &&
+ git commit -a -m b1 &&
+ git checkout b2 &&
+ echo b2 >>file &&
+ git commit -a -m b2
+'
+
+test_expect_success 'prepare pushable branches' '
+ cd aa &&
+ b1=$(git rev-parse origin/b1) &&
+ b2=$(git rev-parse origin/b2) &&
+ git checkout -b b1 origin/b1 &&
+ echo aa-b1 >>file &&
+ git commit -a -m aa-b1 &&
+ git checkout -b b2 origin/b2 &&
+ echo aa-b2 >>file &&
+ git commit -a -m aa-b2 &&
+ git checkout master &&
+ echo aa-master >>file &&
+ git commit -a -m aa-master
+'
+
+test_expect_success 'mixed-success push returns error' '! git push'
+
+test_expect_success 'check tracking branches updated correctly after push' '
+ test "$(git rev-parse origin/master)" = "$(git rev-parse master)"
+'
+
+test_expect_success 'check tracking branches not updated for failed refs' '
+ test "$(git rev-parse origin/b1)" = "$b1" &&
+ test "$(git rev-parse origin/b2)" = "$b2"
+'
+
+test_expect_success 'deleted branches have their tracking branches removed' '
+ git push origin :b1 &&
+ test "$(git rev-parse origin/b1)" = "origin/b1"
+'
+
+test_done
diff --git a/t/t5405-send-pack-rewind.sh b/t/t5405-send-pack-rewind.sh
new file mode 100755
index 0000000000..86abc62271
--- /dev/null
+++ b/t/t5405-send-pack-rewind.sh
@@ -0,0 +1,42 @@
+#!/bin/sh
+
+test_description='forced push to replace commit we do not have'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+ >file1 && git add file1 && test_tick &&
+ git commit -m Initial &&
+
+ mkdir another && (
+ cd another &&
+ git init &&
+ git fetch .. master:master
+ ) &&
+
+ >file2 && git add file2 && test_tick &&
+ git commit -m Second
+
+'
+
+test_expect_success 'non forced push should die not segfault' '
+
+ (
+ cd another &&
+ git push .. master:master
+ test $? = 1
+ )
+
+'
+
+test_expect_success 'forced push should succeed' '
+
+ (
+ cd another &&
+ git push .. +master:master
+ )
+
+'
+
+test_done
diff --git a/t/t5406-remote-rejects.sh b/t/t5406-remote-rejects.sh
new file mode 100755
index 0000000000..46b2cb4e46
--- /dev/null
+++ b/t/t5406-remote-rejects.sh
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+test_description='remote push rejects are reported by client'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ mkdir .git/hooks &&
+ (echo "#!/bin/sh" ; echo "exit 1") >.git/hooks/update &&
+ chmod +x .git/hooks/update &&
+ echo 1 >file &&
+ git add file &&
+ git commit -m 1 &&
+ git clone . child &&
+ cd child &&
+ echo 2 >file &&
+ git commit -a -m 2
+'
+
+test_expect_success 'push reports error' '! git push 2>stderr'
+
+test_expect_success 'individual ref reports error' 'grep rejected stderr'
+
+test_done
diff --git a/t/t5512-ls-remote.sh b/t/t5512-ls-remote.sh
new file mode 100755
index 0000000000..6ec5f7c48b
--- /dev/null
+++ b/t/t5512-ls-remote.sh
@@ -0,0 +1,52 @@
+#!/bin/sh
+
+test_description='git ls-remote'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+ >file &&
+ git add file &&
+ test_tick &&
+ git commit -m initial &&
+ git tag mark &&
+ git show-ref --tags -d | sed -e "s/ / /" >expected.tag &&
+ (
+ echo "$(git rev-parse HEAD) HEAD"
+ git show-ref -d | sed -e "s/ / /"
+ ) >expected.all &&
+
+ git remote add self $(pwd)/.git
+
+'
+
+test_expect_success 'ls-remote --tags .git' '
+
+ git ls-remote --tags .git >actual &&
+ diff -u expected.tag actual
+
+'
+
+test_expect_success 'ls-remote .git' '
+
+ git ls-remote .git >actual &&
+ diff -u expected.all actual
+
+'
+
+test_expect_success 'ls-remote --tags self' '
+
+ git ls-remote --tags self >actual &&
+ diff -u expected.tag actual
+
+'
+
+test_expect_success 'ls-remote self' '
+
+ git ls-remote self >actual &&
+ diff -u expected.all actual
+
+'
+
+test_done
diff --git a/t/t5517-push-mirror.sh b/t/t5517-push-mirror.sh
new file mode 100755
index 0000000000..ed3fec192a
--- /dev/null
+++ b/t/t5517-push-mirror.sh
@@ -0,0 +1,228 @@
+#!/bin/sh
+
+test_description='pushing to a mirror repository'
+
+. ./test-lib.sh
+
+D=`pwd`
+
+invert () {
+ if "$@"; then
+ return 1
+ else
+ return 0
+ fi
+}
+
+mk_repo_pair () {
+ rm -rf master mirror &&
+ mkdir mirror &&
+ (
+ cd mirror &&
+ git init
+ ) &&
+ mkdir master &&
+ (
+ cd master &&
+ git init &&
+ git config remote.up.url ../mirror
+ )
+}
+
+
+# BRANCH tests
+test_expect_success 'push mirror creates new branches' '
+
+ mk_repo_pair &&
+ (
+ cd master &&
+ echo one >foo && git add foo && git commit -m one &&
+ git push --mirror up
+ ) &&
+ master_master=$(cd master && git show-ref -s --verify refs/heads/master) &&
+ mirror_master=$(cd mirror && git show-ref -s --verify refs/heads/master) &&
+ test "$master_master" = "$mirror_master"
+
+'
+
+test_expect_success 'push mirror updates existing branches' '
+
+ mk_repo_pair &&
+ (
+ cd master &&
+ echo one >foo && git add foo && git commit -m one &&
+ git push --mirror up &&
+ echo two >foo && git add foo && git commit -m two &&
+ git push --mirror up
+ ) &&
+ master_master=$(cd master && git show-ref -s --verify refs/heads/master) &&
+ mirror_master=$(cd mirror && git show-ref -s --verify refs/heads/master) &&
+ test "$master_master" = "$mirror_master"
+
+'
+
+test_expect_success 'push mirror force updates existing branches' '
+
+ mk_repo_pair &&
+ (
+ cd master &&
+ echo one >foo && git add foo && git commit -m one &&
+ git push --mirror up &&
+ echo two >foo && git add foo && git commit -m two &&
+ git push --mirror up &&
+ git reset --hard HEAD^
+ git push --mirror up
+ ) &&
+ master_master=$(cd master && git show-ref -s --verify refs/heads/master) &&
+ mirror_master=$(cd mirror && git show-ref -s --verify refs/heads/master) &&
+ test "$master_master" = "$mirror_master"
+
+'
+
+test_expect_success 'push mirror removes branches' '
+
+ mk_repo_pair &&
+ (
+ cd master &&
+ echo one >foo && git add foo && git commit -m one &&
+ git branch remove master &&
+ git push --mirror up &&
+ git branch -D remove
+ git push --mirror up
+ ) &&
+ (
+ cd mirror &&
+ invert git show-ref -s --verify refs/heads/remove
+ )
+
+'
+
+test_expect_success 'push mirror adds, updates and removes branches together' '
+
+ mk_repo_pair &&
+ (
+ cd master &&
+ echo one >foo && git add foo && git commit -m one &&
+ git branch remove master &&
+ git push --mirror up &&
+ git branch -D remove &&
+ git branch add master &&
+ echo two >foo && git add foo && git commit -m two &&
+ git push --mirror up
+ ) &&
+ master_master=$(cd master && git show-ref -s --verify refs/heads/master) &&
+ master_add=$(cd master && git show-ref -s --verify refs/heads/add) &&
+ mirror_master=$(cd mirror && git show-ref -s --verify refs/heads/master) &&
+ mirror_add=$(cd mirror && git show-ref -s --verify refs/heads/add) &&
+ test "$master_master" = "$mirror_master" &&
+ test "$master_add" = "$mirror_add" &&
+ (
+ cd mirror &&
+ invert git show-ref -s --verify refs/heads/remove
+ )
+
+'
+
+
+# TAG tests
+test_expect_success 'push mirror creates new tags' '
+
+ mk_repo_pair &&
+ (
+ cd master &&
+ echo one >foo && git add foo && git commit -m one &&
+ git tag -f tmaster master &&
+ git push --mirror up
+ ) &&
+ master_master=$(cd master && git show-ref -s --verify refs/tags/tmaster) &&
+ mirror_master=$(cd mirror && git show-ref -s --verify refs/tags/tmaster) &&
+ test "$master_master" = "$mirror_master"
+
+'
+
+test_expect_success 'push mirror updates existing tags' '
+
+ mk_repo_pair &&
+ (
+ cd master &&
+ echo one >foo && git add foo && git commit -m one &&
+ git tag -f tmaster master &&
+ git push --mirror up &&
+ echo two >foo && git add foo && git commit -m two &&
+ git tag -f tmaster master &&
+ git push --mirror up
+ ) &&
+ master_master=$(cd master && git show-ref -s --verify refs/tags/tmaster) &&
+ mirror_master=$(cd mirror && git show-ref -s --verify refs/tags/tmaster) &&
+ test "$master_master" = "$mirror_master"
+
+'
+
+test_expect_success 'push mirror force updates existing tags' '
+
+ mk_repo_pair &&
+ (
+ cd master &&
+ echo one >foo && git add foo && git commit -m one &&
+ git tag -f tmaster master &&
+ git push --mirror up &&
+ echo two >foo && git add foo && git commit -m two &&
+ git tag -f tmaster master &&
+ git push --mirror up &&
+ git reset --hard HEAD^
+ git tag -f tmaster master &&
+ git push --mirror up
+ ) &&
+ master_master=$(cd master && git show-ref -s --verify refs/tags/tmaster) &&
+ mirror_master=$(cd mirror && git show-ref -s --verify refs/tags/tmaster) &&
+ test "$master_master" = "$mirror_master"
+
+'
+
+test_expect_success 'push mirror removes tags' '
+
+ mk_repo_pair &&
+ (
+ cd master &&
+ echo one >foo && git add foo && git commit -m one &&
+ git tag -f tremove master &&
+ git push --mirror up &&
+ git tag -d tremove
+ git push --mirror up
+ ) &&
+ (
+ cd mirror &&
+ invert git show-ref -s --verify refs/tags/tremove
+ )
+
+'
+
+test_expect_success 'push mirror adds, updates and removes tags together' '
+
+ mk_repo_pair &&
+ (
+ cd master &&
+ echo one >foo && git add foo && git commit -m one &&
+ git tag -f tmaster master &&
+ git tag -f tremove master &&
+ git push --mirror up &&
+ git tag -d tremove &&
+ git tag tadd master &&
+ echo two >foo && git add foo && git commit -m two &&
+ git tag -f tmaster master &&
+ git push --mirror up
+ ) &&
+ master_master=$(cd master && git show-ref -s --verify refs/tags/tmaster) &&
+ master_add=$(cd master && git show-ref -s --verify refs/tags/tadd) &&
+ mirror_master=$(cd mirror && git show-ref -s --verify refs/tags/tmaster) &&
+ mirror_add=$(cd mirror && git show-ref -s --verify refs/tags/tadd) &&
+ test "$master_master" = "$mirror_master" &&
+ test "$master_add" = "$mirror_add" &&
+ (
+ cd mirror &&
+ invert git show-ref -s --verify refs/tags/tremove
+ )
+
+'
+
+test_done
diff --git a/transport.c b/transport.c
index 43b9e7c410..50db9807d0 100644
--- a/transport.c
+++ b/transport.c
@@ -6,6 +6,7 @@
#endif
#include "pkt-line.h"
#include "fetch-pack.h"
+#include "send-pack.h"
#include "walker.h"
#include "bundle.h"
#include "dir.h"
@@ -141,7 +142,7 @@ static void insert_packed_refs(const char *packed_refs, struct ref **list)
}
}
-static struct ref *get_refs_via_rsync(const struct transport *transport)
+static struct ref *get_refs_via_rsync(struct transport *transport)
{
struct strbuf buf = STRBUF_INIT, temp_dir = STRBUF_INIT;
struct ref dummy, *tail = &dummy;
@@ -283,6 +284,9 @@ static int rsync_transport_push(struct transport *transport,
struct child_process rsync;
const char *args[10];
+ if (flags & TRANSPORT_PUSH_MIRROR)
+ return error("rsync transport does not support mirror mode");
+
/* first push the objects */
strbuf_addstr(&buf, transport->url);
@@ -388,6 +392,9 @@ static int curl_transport_push(struct transport *transport, int refspec_nr, cons
int argc;
int err;
+ if (flags & TRANSPORT_PUSH_MIRROR)
+ return error("http transport does not support mirror mode");
+
argv = xmalloc((refspec_nr + 12) * sizeof(char *));
argv[0] = "http-push";
argc = 1;
@@ -432,7 +439,7 @@ static int missing__target(int code, int result)
#define missing_target(a) missing__target((a)->http_code, (a)->curl_result)
-static struct ref *get_refs_via_curl(const struct transport *transport)
+static struct ref *get_refs_via_curl(struct transport *transport)
{
struct buffer buffer;
char *data, *start, *mid;
@@ -529,7 +536,7 @@ struct bundle_transport_data {
struct bundle_header header;
};
-static struct ref *get_refs_from_bundle(const struct transport *transport)
+static struct ref *get_refs_from_bundle(struct transport *transport)
{
struct bundle_transport_data *data = transport->data;
struct ref *result = NULL;
@@ -601,7 +608,7 @@ static int set_git_option(struct transport *connection,
return 1;
}
-static struct ref *get_refs_via_connect(const struct transport *transport)
+static struct ref *get_refs_via_connect(struct transport *transport)
{
struct git_transport_data *data = transport->data;
struct ref *refs;
@@ -654,50 +661,17 @@ static int fetch_refs_via_pack(struct transport *transport,
static int git_transport_push(struct transport *transport, int refspec_nr, const char **refspec, int flags)
{
struct git_transport_data *data = transport->data;
- const char **argv;
- char *rem;
- int argc;
- int err;
+ struct send_pack_args args;
- argv = xmalloc((refspec_nr + 12) * sizeof(char *));
- argv[0] = "send-pack";
- argc = 1;
- if (flags & TRANSPORT_PUSH_ALL)
- argv[argc++] = "--all";
- if (flags & TRANSPORT_PUSH_FORCE)
- argv[argc++] = "--force";
- if (flags & TRANSPORT_PUSH_DRY_RUN)
- argv[argc++] = "--dry-run";
- if (flags & TRANSPORT_PUSH_VERBOSE)
- argv[argc++] = "--verbose";
- if (data->receivepack) {
- char *rp = xmalloc(strlen(data->receivepack) + 16);
- sprintf(rp, "--receive-pack=%s", data->receivepack);
- argv[argc++] = rp;
- }
- if (data->thin)
- argv[argc++] = "--thin";
- rem = xmalloc(strlen(transport->remote->name) + 10);
- sprintf(rem, "--remote=%s", transport->remote->name);
- argv[argc++] = rem;
- argv[argc++] = transport->url;
- while (refspec_nr--)
- argv[argc++] = *refspec++;
- argv[argc] = NULL;
- err = run_command_v_opt(argv, RUN_GIT_CMD);
- switch (err) {
- case -ERR_RUN_COMMAND_FORK:
- error("unable to fork for %s", argv[0]);
- case -ERR_RUN_COMMAND_EXEC:
- error("unable to exec %s", argv[0]);
- break;
- case -ERR_RUN_COMMAND_WAITPID:
- case -ERR_RUN_COMMAND_WAITPID_WRONG_PID:
- case -ERR_RUN_COMMAND_WAITPID_SIGNAL:
- case -ERR_RUN_COMMAND_WAITPID_NOEXIT:
- error("%s died with strange error", argv[0]);
- }
- return !!err;
+ args.receivepack = data->receivepack;
+ args.send_all = !!(flags & TRANSPORT_PUSH_ALL);
+ args.send_mirror = !!(flags & TRANSPORT_PUSH_MIRROR);
+ args.force_update = !!(flags & TRANSPORT_PUSH_FORCE);
+ args.use_thin_pack = data->thin;
+ args.verbose = !!(flags & TRANSPORT_PUSH_VERBOSE);
+ args.dry_run = !!(flags & TRANSPORT_PUSH_DRY_RUN);
+
+ return send_pack(&args, transport->url, transport->remote, refspec_nr, refspec);
}
static int disconnect_git(struct transport *transport)
@@ -789,7 +763,7 @@ int transport_push(struct transport *transport,
return transport->push(transport, refspec_nr, refspec, flags);
}
-struct ref *transport_get_remote_refs(struct transport *transport)
+const struct ref *transport_get_remote_refs(struct transport *transport)
{
if (!transport->remote_refs)
transport->remote_refs = transport->get_refs_list(transport);
diff --git a/transport.h b/transport.h
index 2f80ab4b03..6fb4526cda 100644
--- a/transport.h
+++ b/transport.h
@@ -8,7 +8,7 @@ struct transport {
struct remote *remote;
const char *url;
void *data;
- struct ref *remote_refs;
+ const struct ref *remote_refs;
/**
* Returns 0 if successful, positive if the option is not
@@ -18,7 +18,7 @@ struct transport {
int (*set_option)(struct transport *connection, const char *name,
const char *value);
- struct ref *(*get_refs_list)(const struct transport *transport);
+ struct ref *(*get_refs_list)(struct transport *transport);
int (*fetch)(struct transport *transport, int refs_nr, struct ref **refs);
int (*push)(struct transport *connection, int refspec_nr, const char **refspec, int flags);
@@ -30,7 +30,8 @@ struct transport {
#define TRANSPORT_PUSH_ALL 1
#define TRANSPORT_PUSH_FORCE 2
#define TRANSPORT_PUSH_DRY_RUN 4
-#define TRANSPORT_PUSH_VERBOSE 8
+#define TRANSPORT_PUSH_MIRROR 8
+#define TRANSPORT_PUSH_VERBOSE 16
/* Returns a transport suitable for the url */
struct transport *transport_get(struct remote *, const char *);
@@ -62,7 +63,7 @@ int transport_set_option(struct transport *transport, const char *name,
int transport_push(struct transport *connection,
int refspec_nr, const char **refspec, int flags);
-struct ref *transport_get_remote_refs(struct transport *transport);
+const struct ref *transport_get_remote_refs(struct transport *transport);
int transport_fetch_refs(struct transport *transport, struct ref *refs);
void transport_unlock_pack(struct transport *transport);