Merge branch 'jk/send-pack'
authorJunio C Hamano <gitster@pobox.com>
Sun, 25 Nov 2007 00:45:37 +0000 (16:45 -0800)
committerJunio C Hamano <gitster@pobox.com>
Sun, 25 Nov 2007 00:45:37 +0000 (16:45 -0800)
* jk/send-pack: (24 commits)
send-pack: cluster ref status reporting
send-pack: fix "everything up-to-date" message
send-pack: tighten remote error reporting
make "find_ref_by_name" a public function
Fix warning about bitfield in struct ref
send-pack: assign remote errors to each ref
send-pack: check ref->status before updating tracking refs
send-pack: track errors for each ref
git-push: add documentation for the newly added --mirror mode
Add tests for git push'es mirror mode
Update the tracking references only if they were succesfully updated on remote
Add a test checking if send-pack updated local tracking branches correctly
git-push: plumb in --mirror mode
Teach send-pack a mirror mode
send-pack: segfault fix on forced push
Reteach builtin-ls-remote to understand remotes
send-pack: require --verbose to show update of tracking refs
receive-pack: don't mention successful updates
more terse push output
Build in ls-remote
...

27 files changed:
Documentation/git-push.txt
Makefile
builtin-fetch.c
builtin-ls-remote.c [new file with mode: 0644]
builtin-push.c
builtin-send-pack.c [new file with mode: 0644]
builtin.h
cache.h
connect.c
contrib/examples/git-ls-remote.sh [new file with mode: 0755]
git-ls-remote.sh [deleted file]
git.c
http-push.c
peek-remote.c [deleted file]
receive-pack.c
refs.c
remote.c
remote.h
send-pack.c [deleted file]
send-pack.h [new file with mode: 0644]
t/t5404-tracking-branches.sh [new file with mode: 0755]
t/t5405-send-pack-rewind.sh [new file with mode: 0755]
t/t5406-remote-rejects.sh [new file with mode: 0755]
t/t5512-ls-remote.sh [new file with mode: 0755]
t/t5517-push-mirror.sh [new file with mode: 0755]
transport.c
transport.h
index 4a68aaba342bd1155beb2ae72fd81c701e6c80eb..b8003c63c7e51dc1e3907645b267805dfb5f38d6 100644 (file)
@@ -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.
 
index 8809b0ace6d55f4d33f820f0031fe7b4ac84e9a2..ccf522adce9fccb8b94ebc26d686a995d900ebbf 100644 (file)
--- 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 \
index be9e3ea2bcea0d0ab88a51d396aff5714822b77a..31e138eab82a4a710916ff88386bd82bcf2f11d6 100644 (file)
@@ -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 (file)
index 0000000..56f3f88
--- /dev/null
@@ -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;
+}
index 6d1da07c46704f3de837044652f0a1fd149327ad..41df717f847ecc41bf0a695fa09fbb926b42f720 100644 (file)
@@ -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 (file)
index 0000000..25ae1fe
--- /dev/null
@@ -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;
+}
index 6ecf12be328a8a9ef207999c78246420bcf81ee3..98ada7b7f705d70ad27cccfaaf3c21c13f66dcc4 100644 (file)
--- 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 a0b93770d4ceeb19dafe495248f844b385fe2003..aaa135bfde23cd2529e3707e4499be7f75917336 100644 (file)
--- 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);
index 44e423dafd5c5e6566124fd1bb05f3d419b19a40..3aefd4ace590082b85bd3c4b9b41b8d1f1c72268 100644 (file)
--- 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/contrib/examples/git-ls-remote.sh b/contrib/examples/git-ls-remote.sh
new file mode 100755 (executable)
index 0000000..fec70bb
--- /dev/null
@@ -0,0 +1,142 @@
+#!/bin/sh
+#
+
+usage () {
+    echo >&2 "usage: $0 [--heads] [--tags] [-u|--upload-pack <upload-pack>]"
+    echo >&2 "          <repository> <refs>..."
+    exit 1;
+}
+
+die () {
+    echo >&2 "$*"
+    exit 1
+}
+
+exec=
+while test $# != 0
+do
+  case "$1" in
+  -h|--h|--he|--hea|--head|--heads)
+  heads=heads; shift ;;
+  -t|--t|--ta|--tag|--tags)
+  tags=tags; shift ;;
+  -u|--u|--up|--upl|--uploa|--upload|--upload-|--upload-p|--upload-pa|\
+  --upload-pac|--upload-pack)
+       shift
+       exec="--upload-pack=$1"
+       shift;;
+  -u=*|--u=*|--up=*|--upl=*|--uplo=*|--uploa=*|--upload=*|\
+  --upload-=*|--upload-p=*|--upload-pa=*|--upload-pac=*|--upload-pack=*)
+       exec=--upload-pack=$(expr "z$1" : 'z-[^=]*=\(.*\)')
+       shift;;
+  --)
+  shift; break ;;
+  -*)
+  usage ;;
+  *)
+  break ;;
+  esac
+done
+
+case "$#" in 0) usage ;; esac
+
+case ",$heads,$tags," in
+,,,) heads=heads tags=tags other=other ;;
+esac
+
+. git-parse-remote
+peek_repo="$(get_remote_url "$@")"
+shift
+
+tmp=.ls-remote-$$
+trap "rm -fr $tmp-*" 0 1 2 3 15
+tmpdir=$tmp-d
+
+case "$peek_repo" in
+http://* | https://* | ftp://* )
+       if [ -n "$GIT_SSL_NO_VERIFY" -o \
+               "`git config --bool http.sslVerify`" = false ]; then
+               curl_extra_args="-k"
+       fi
+       if [ -n "$GIT_CURL_FTP_NO_EPSV" -o \
+               "`git config --bool http.noEPSV`" = true ]; then
+               curl_extra_args="${curl_extra_args} --disable-epsv"
+       fi
+       curl -nsf $curl_extra_args --header "Pragma: no-cache" "$peek_repo/info/refs" ||
+               echo "failed    slurping"
+       ;;
+
+rsync://* )
+       mkdir $tmpdir &&
+       rsync -rlq "$peek_repo/HEAD" $tmpdir &&
+       rsync -rq "$peek_repo/refs" $tmpdir || {
+               echo "failed    slurping"
+               exit
+       }
+       head=$(cat "$tmpdir/HEAD") &&
+       case "$head" in
+       ref:' '*)
+               head=$(expr "z$head" : 'zref: \(.*\)') &&
+               head=$(cat "$tmpdir/$head") || exit
+       esac &&
+       echo "$head     HEAD"
+       (cd $tmpdir && find refs -type f) |
+       while read path
+       do
+               tr -d '\012' <"$tmpdir/$path"
+               echo "  $path"
+       done &&
+       rm -fr $tmpdir
+       ;;
+
+* )
+       if test -f "$peek_repo" ; then
+               git bundle list-heads "$peek_repo" ||
+               echo "failed    slurping"
+       else
+               git-peek-remote $exec "$peek_repo" ||
+               echo "failed    slurping"
+       fi
+       ;;
+esac |
+sort -t '      ' -k 2 |
+while read sha1 path
+do
+       case "$sha1" in
+       failed)
+               exit 1 ;;
+       esac
+       case "$path" in
+       refs/heads/*)
+               group=heads ;;
+       refs/tags/*)
+               group=tags ;;
+       *)
+               group=other ;;
+       esac
+       case ",$heads,$tags,$other," in
+       *,$group,*)
+               ;;
+       *)
+               continue;;
+       esac
+       case "$#" in
+       0)
+               match=yes ;;
+       *)
+               match=no
+               for pat
+               do
+                       case "/$path" in
+                       */$pat )
+                               match=yes
+                               break ;;
+                       esac
+               done
+       esac
+       case "$match" in
+       no)
+               continue ;;
+       esac
+       echo "$sha1     $path"
+done
diff --git a/git-ls-remote.sh b/git-ls-remote.sh
deleted file mode 100755 (executable)
index fec70bb..0000000
+++ /dev/null
@@ -1,142 +0,0 @@
-#!/bin/sh
-#
-
-usage () {
-    echo >&2 "usage: $0 [--heads] [--tags] [-u|--upload-pack <upload-pack>]"
-    echo >&2 "          <repository> <refs>..."
-    exit 1;
-}
-
-die () {
-    echo >&2 "$*"
-    exit 1
-}
-
-exec=
-while test $# != 0
-do
-  case "$1" in
-  -h|--h|--he|--hea|--head|--heads)
-  heads=heads; shift ;;
-  -t|--t|--ta|--tag|--tags)
-  tags=tags; shift ;;
-  -u|--u|--up|--upl|--uploa|--upload|--upload-|--upload-p|--upload-pa|\
-  --upload-pac|--upload-pack)
-       shift
-       exec="--upload-pack=$1"
-       shift;;
-  -u=*|--u=*|--up=*|--upl=*|--uplo=*|--uploa=*|--upload=*|\
-  --upload-=*|--upload-p=*|--upload-pa=*|--upload-pac=*|--upload-pack=*)
-       exec=--upload-pack=$(expr "z$1" : 'z-[^=]*=\(.*\)')
-       shift;;
-  --)
-  shift; break ;;
-  -*)
-  usage ;;
-  *)
-  break ;;
-  esac
-done
-
-case "$#" in 0) usage ;; esac
-
-case ",$heads,$tags," in
-,,,) heads=heads tags=tags other=other ;;
-esac
-
-. git-parse-remote
-peek_repo="$(get_remote_url "$@")"
-shift
-
-tmp=.ls-remote-$$
-trap "rm -fr $tmp-*" 0 1 2 3 15
-tmpdir=$tmp-d
-
-case "$peek_repo" in
-http://* | https://* | ftp://* )
-       if [ -n "$GIT_SSL_NO_VERIFY" -o \
-               "`git config --bool http.sslVerify`" = false ]; then
-               curl_extra_args="-k"
-       fi
-       if [ -n "$GIT_CURL_FTP_NO_EPSV" -o \
-               "`git config --bool http.noEPSV`" = true ]; then
-               curl_extra_args="${curl_extra_args} --disable-epsv"
-       fi
-       curl -nsf $curl_extra_args --header "Pragma: no-cache" "$peek_repo/info/refs" ||
-               echo "failed    slurping"
-       ;;
-
-rsync://* )
-       mkdir $tmpdir &&
-       rsync -rlq "$peek_repo/HEAD" $tmpdir &&
-       rsync -rq "$peek_repo/refs" $tmpdir || {
-               echo "failed    slurping"
-               exit
-       }
-       head=$(cat "$tmpdir/HEAD") &&
-       case "$head" in
-       ref:' '*)
-               head=$(expr "z$head" : 'zref: \(.*\)') &&
-               head=$(cat "$tmpdir/$head") || exit
-       esac &&
-       echo "$head     HEAD"
-       (cd $tmpdir && find refs -type f) |
-       while read path
-       do
-               tr -d '\012' <"$tmpdir/$path"
-               echo "  $path"
-       done &&
-       rm -fr $tmpdir
-       ;;
-
-* )
-       if test -f "$peek_repo" ; then
-               git bundle list-heads "$peek_repo" ||
-               echo "failed    slurping"
-       else
-               git-peek-remote $exec "$peek_repo" ||
-               echo "failed    slurping"
-       fi
-       ;;
-esac |
-sort -t '      ' -k 2 |
-while read sha1 path
-do
-       case "$sha1" in
-       failed)
-               exit 1 ;;
-       esac
-       case "$path" in
-       refs/heads/*)
-               group=heads ;;
-       refs/tags/*)
-               group=tags ;;
-       *)
-               group=other ;;
-       esac
-       case ",$heads,$tags,$other," in
-       *,$group,*)
-               ;;
-       *)
-               continue;;
-       esac
-       case "$#" in
-       0)
-               match=yes ;;
-       *)
-               match=no
-               for pat
-               do
-                       case "/$path" in
-                       */$pat )
-                               match=yes
-                               break ;;
-                       esac
-               done
-       esac
-       case "$match" in
-       no)
-               continue ;;
-       esac
-       echo "$sha1     $path"
-done
diff --git a/git.c b/git.c
index 517ef414f7f9cf60136f6129b476b93f8a56ae41..01bbbc73258dc66d8d3a8253cc9328b5b18346aa 100644 (file)
--- 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 },
index 9314621a11030e66eeba7f0da6b88a813e1ba759..66b81f17266569ee47d32ec58df9d1386e4d0431 100644 (file)
@@ -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 (file)
index 8d20f7c..0000000
+++ /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;
-}
index 38e35c06b9e73376adde597c4fe28490a2e886b1..ed44b897f67a0ef051fa1bf205b2bb5747a1bd67 100644 (file)
@@ -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 ae532540cf2592aee2be7610ac811082c3a9b6be..54ec98d153889f40313dba9a5ee8f07ddd0e160a 100644 (file)
--- 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;
+}
index bec2ba1adbed02af54572be924dcfbb47bf9c423..bb010590837fd6ea188e64c5263bb1fe12ab93f4 100644 (file)
--- 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)
index 878b4ecc32a2a4b5be4e0444ae4510e1a7ab01cb..b10036cae6f89e087da56979e2248e1e5c5d42d3 100644 (file)
--- 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 (file)
index b74fd45..0000000
+++ /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 (file)
index 0000000..8ff1dc3
--- /dev/null
@@ -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 (executable)
index 0000000..1493a92
--- /dev/null
@@ -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 (executable)
index 0000000..86abc62
--- /dev/null
@@ -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 (executable)
index 0000000..46b2cb4
--- /dev/null
@@ -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 (executable)
index 0000000..6ec5f7c
--- /dev/null
@@ -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 (executable)
index 0000000..ed3fec1
--- /dev/null
@@ -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
index 43b9e7c410882d82fd6b08532e30df9b82b3ffb1..50db9807d003162c81f391e08f247bac7ce410c9 100644 (file)
@@ -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);
index 2f80ab4b03df181dc8b041b1aace5832aa84deb1..6fb4526cda5fc1ee1178fdf1d0840c90b88aa26c 100644 (file)
@@ -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);