Merge branch 'db/remote-builtin' into jk/send-pack
authorJunio C Hamano <gitster@pobox.com>
Wed, 14 Nov 2007 11:09:52 +0000 (03:09 -0800)
committerJunio C Hamano <gitster@pobox.com>
Wed, 14 Nov 2007 11:09:52 +0000 (03:09 -0800)
* db/remote-builtin:
Reteach builtin-ls-remote to understand remotes
Build in ls-remote
Use built-in send-pack.
Build-in send-pack, with an API for other programs to call.
Build-in peek-remote, using transport infrastructure.
Miscellaneous const changes and utilities

Conflicts:

transport.c

1  2 
Makefile
builtin-fetch.c
builtin-send-pack.c
cache.h
git.c
http-push.c
transport.c
transport.h
diff --combined Makefile
index 9c6776e5970b38d0a8de8d35680aa83d54f41c01,470e54a60d182fa3d9f119d6257d9771cfb379a9..af827f6ef63be2072dded20f33c2a0fddb76e26f
+++ b/Makefile
@@@ -98,8 -98,6 +98,8 @@@ all:
  # Define OLD_ICONV if your library has an old iconv(), where the second
  # (input buffer pointer) parameter is declared with type (const char **).
  #
 +# Define NO_DEFLATE_BOUND if your zlib does not have deflateBound.
 +#
  # Define NO_R_TO_GCC_LINKER if your gcc does not like "-R/path/lib"
  # that tells runtime paths to dynamic libraries;
  # "-Wl,-rpath=/path/lib" is used instead.
@@@ -212,7 -210,6 +212,6 @@@ BASIC_LDFLAGS 
  SCRIPT_SH = \
        git-bisect.sh git-checkout.sh \
        git-clean.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 \
@@@ -241,7 -238,7 +240,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 \
@@@ -301,7 -298,7 +300,7 @@@ DIFF_OBJS = 
  LIB_OBJS = \
        blob.o commit.o connect.o csum-file.o cache-tree.o base85.o \
        date.o diff-delta.o entry.o exec_cmd.o ident.o \
 -      interpolate.o hash.o \
 +      pretty.o interpolate.o hash.o \
        lockfile.o \
        patch-ids.o \
        object.o pack-check.o pack-write.o patch-delta.o path.o pkt-line.o \
@@@ -347,6 -344,7 +346,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 \
        builtin-push.o \
        builtin-read-tree.o \
        builtin-reflog.o \
+       builtin-send-pack.o \
        builtin-config.o \
        builtin-rerere.o \
        builtin-reset.o \
@@@ -663,10 -662,6 +664,10 @@@ ifdef OLD_ICON
        BASIC_CFLAGS += -DOLD_ICONV
  endif
  
 +ifdef NO_DEFLATE_BOUND
 +      BASIC_CFLAGS += -DNO_DEFLATE_BOUND
 +endif
 +
  ifdef PPC_SHA1
        SHA1_HEADER = "ppc/sha1.h"
        LIB_OBJS += ppc/sha1.o ppc/sha1ppc.o
@@@ -922,7 -917,6 +923,7 @@@ git-http-push$X: revision.o http.o http
  
  $(LIB_OBJS) $(BUILTIN_OBJS): $(LIB_H)
  $(patsubst git-%$X,%.o,$(PROGRAMS)): $(LIB_H) $(wildcard */*.h)
 +builtin-revert.o builtin-runstatus.o wt-status.o: wt-status.h
  
  $(LIB_FILE): $(LIB_OBJS)
        $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(LIB_OBJS)
@@@ -998,8 -992,6 +999,8 @@@ test-date$X: date.o ctype.
  
  test-delta$X: diff-delta.o patch-delta.o
  
 +test-parse-options$X: parse-options.o
 +
  .PRECIOUS: $(patsubst test-%$X,test-%.o,$(TEST_PROGRAMS))
  
  test-%$X: test-%.o $(GITLIBS)
@@@ -1128,13 -1120,12 +1129,13 @@@ endi
  ### Check documentation
  #
  check-docs::
 -      @for v in $(ALL_PROGRAMS) $(BUILT_INS) git$X gitk; \
 +      @(for v in $(ALL_PROGRAMS) $(BUILT_INS) git gitk; \
        do \
                case "$$v" in \
                git-merge-octopus | git-merge-ours | git-merge-recursive | \
 -              git-merge-resolve | git-merge-stupid | \
 +              git-merge-resolve | git-merge-stupid | git-merge-subtree | \
                git-add--interactive | git-fsck-objects | git-init-db | \
 +              git-rebase--interactive | \
                git-repo-config | git-fetch--tool ) continue ;; \
                esac ; \
                test -f "Documentation/$$v.txt" || \
                git) ;; \
                *) echo "no link: $$v";; \
                esac ; \
 -      done | sort
 +      done; \
 +      ( \
 +              sed -e '1,/^__DATA__/d' \
 +                  -e 's/[     ].*//' \
 +                  -e 's/^/listed /' Documentation/cmd-list.perl; \
 +              ls -1 Documentation/git*txt | \
 +              sed -e 's|Documentation/|documented |' \
 +                  -e 's/\.txt//'; \
 +      ) | while read how cmd; \
 +      do \
 +              case "$$how,$$cmd" in \
 +              *,git-citool | \
 +              *,git-gui | \
 +              documented,gitattributes | \
 +              documented,gitignore | \
 +              documented,gitmodules | \
 +              documented,git-tools | \
 +              sentinel,not,matching,is,ok ) continue ;; \
 +              esac; \
 +              case " $(ALL_PROGRAMS) $(BUILT_INS) git gitk " in \
 +              *" $$cmd "*)    ;; \
 +              *) echo "removed but $$how: $$cmd" ;; \
 +              esac; \
 +      done ) | sort
  
  ### Make sure built-ins do not have dups and listed in git.c
  #
diff --combined builtin-fetch.c
index 5f5b59bfdb5056dfedf692215db2f9e4207f8580,fa0af170ddeaa7e61c85787f7d283b0b67ab3a2f..6b1750d28bcc69ed5d8ac5b30b96dab29608fb28
@@@ -29,7 -29,7 +29,7 @@@ static void unlock_pack_on_signal(int s
  }
  
  static void add_merge_config(struct ref **head,
-                          struct ref *remote_refs,
+                          const struct ref *remote_refs,
                           struct branch *branch,
                           struct ref ***tail)
  {
@@@ -77,7 -77,7 +77,7 @@@ static struct ref *get_ref_map(struct t
        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++) {
        return ref_map;
  }
  
 -static void show_new(enum object_type type, unsigned char *sha1_new)
 -{
 -      fprintf(stderr, "  %s: %s\n", typename(type),
 -              find_unique_abbrev(sha1_new, DEFAULT_ABBREV));
 -}
 -
  static int s_update_ref(const char *action,
                        struct ref *ref,
                        int check_old)
        return 0;
  }
  
 +#define SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
 +
  static int update_local_ref(struct ref *ref,
 -                          const char *note,
 -                          int verbose)
 +                          const char *remote,
 +                          int verbose,
 +                          char *display)
  {
 -      char oldh[41], newh[41];
        struct commit *current = NULL, *updated;
        enum object_type type;
        struct branch *current_branch = branch_get(NULL);
 +      const char *pretty_ref = ref->name + (
 +              !prefixcmp(ref->name, "refs/heads/") ? 11 :
 +              !prefixcmp(ref->name, "refs/tags/") ? 10 :
 +              !prefixcmp(ref->name, "refs/remotes/") ? 13 :
 +              0);
  
 +      *display = 0;
        type = sha1_object_info(ref->new_sha1, NULL);
        if (type < 0)
                die("object %s not found", sha1_to_hex(ref->new_sha1));
  
        if (!*ref->name) {
                /* Not storing */
 -              if (verbose) {
 -                      fprintf(stderr, "* fetched %s\n", note);
 -                      show_new(type, ref->new_sha1);
 -              }
 +              if (verbose)
 +                      sprintf(display, "* branch %s -> FETCH_HEAD", remote);
                return 0;
        }
  
        if (!hashcmp(ref->old_sha1, ref->new_sha1)) {
 -              if (verbose) {
 -                      fprintf(stderr, "* %s: same as %s\n",
 -                              ref->name, note);
 -                      show_new(type, ref->new_sha1);
 -              }
 +              if (verbose)
 +                      sprintf(display, "= %-*s %s -> %s", SUMMARY_WIDTH,
 +                              "[up to date]", remote, pretty_ref);
                return 0;
        }
  
                 * If this is the head, and it's not okay to update
                 * the head, and the old value of the head isn't empty...
                 */
 -              fprintf(stderr,
 -                      " * %s: Cannot fetch into the current branch.\n",
 -                      ref->name);
 +              sprintf(display, "! %-*s %s -> %s  (can't  fetch in current branch)",
 +                      SUMMARY_WIDTH, "[rejected]", remote, pretty_ref);
                return 1;
        }
  
        if (!is_null_sha1(ref->old_sha1) &&
            !prefixcmp(ref->name, "refs/tags/")) {
 -              fprintf(stderr, "* %s: updating with %s\n",
 -                      ref->name, note);
 -              show_new(type, ref->new_sha1);
 +              sprintf(display, "- %-*s %s -> %s",
 +                      SUMMARY_WIDTH, "[tag update]", remote, pretty_ref);
                return s_update_ref("updating tag", ref, 0);
        }
  
        current = lookup_commit_reference_gently(ref->old_sha1, 1);
        updated = lookup_commit_reference_gently(ref->new_sha1, 1);
        if (!current || !updated) {
 -              char *msg;
 -              if (!strncmp(ref->name, "refs/tags/", 10))
 +              const char *msg;
 +              const char *what;
 +              if (!strncmp(ref->name, "refs/tags/", 10)) {
                        msg = "storing tag";
 -              else
 +                      what = "[new tag]";
 +              }
 +              else {
                        msg = "storing head";
 -              fprintf(stderr, "* %s: storing %s\n",
 -                      ref->name, note);
 -              show_new(type, ref->new_sha1);
 +                      what = "[new branch]";
 +              }
 +
 +              sprintf(display, "* %-*s %s -> %s",
 +                      SUMMARY_WIDTH, what, remote, pretty_ref);
                return s_update_ref(msg, ref, 0);
        }
  
 -      strcpy(oldh, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV));
 -      strcpy(newh, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV));
 -
        if (in_merge_bases(current, &updated, 1)) {
 -              fprintf(stderr, "* %s: fast forward to %s\n",
 -                      ref->name, note);
 -              fprintf(stderr, "  old..new: %s..%s\n", oldh, newh);
 +              char quickref[83];
 +              strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV));
 +              strcat(quickref, "..");
 +              strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV));
 +              sprintf(display, "  %-*s %s -> %s  (fast forward)",
 +                      SUMMARY_WIDTH, quickref, remote, pretty_ref);
                return s_update_ref("fast forward", ref, 1);
 -      }
 -      if (!force && !ref->force) {
 -              fprintf(stderr,
 -                      "* %s: not updating to non-fast forward %s\n",
 -                      ref->name, note);
 -              fprintf(stderr,
 -                      "  old...new: %s...%s\n", oldh, newh);
 +      } else if (force || ref->force) {
 +              char quickref[84];
 +              strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV));
 +              strcat(quickref, "...");
 +              strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV));
 +              sprintf(display, "+ %-*s %s -> %s  (forced update)",
 +                      SUMMARY_WIDTH, quickref, remote, pretty_ref);
 +              return s_update_ref("forced-update", ref, 1);
 +      } else {
 +              sprintf(display, "! %-*s %s -> %s  (non fast forward)",
 +                      SUMMARY_WIDTH, "[rejected]", remote, pretty_ref);
                return 1;
        }
 -      fprintf(stderr,
 -              "* %s: forcing update to non-fast forward %s\n",
 -              ref->name, note);
 -      fprintf(stderr, "  old...new: %s...%s\n", oldh, newh);
 -      return s_update_ref("forced-update", ref, 1);
  }
  
  static void store_updated_refs(const char *url, struct ref *ref_map)
  {
        FILE *fp;
        struct commit *commit;
 -      int url_len, i, note_len;
 +      int url_len, i, note_len, shown_url = 0;
        char note[1024];
        const char *what, *kind;
        struct ref *rm;
                        rm->merge ? "" : "not-for-merge",
                        note);
  
 -              if (ref)
 -                      update_local_ref(ref, note, verbose);
 +              if (ref) {
 +                      update_local_ref(ref, what, verbose, note);
 +                      if (*note) {
 +                              if (!shown_url) {
 +                                      fprintf(stderr, "From %.*s\n",
 +                                                      url_len, url);
 +                                      shown_url = 1;
 +                              }
 +                              fprintf(stderr, " %s\n", note);
 +                      }
 +              }
        }
        fclose(fp);
  }
@@@ -354,12 -345,12 +354,12 @@@ static struct ref *find_non_local_tags(
        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) {
                if (!path_list_has_path(&existing_refs, ref_name) &&
                    !path_list_has_path(&new_refs, ref_name) &&
                    lookup_object(ref->old_sha1)) {
 -                      fprintf(stderr, "Auto-following %s\n",
 -                              ref_name);
 -
                        path_list_insert(ref_name, &new_refs);
  
                        rm = alloc_ref(strlen(ref_name) + 1);
@@@ -523,7 -517,7 +523,7 @@@ int cmd_fetch(int argc, const char **ar
                        depth = argv[i];
                        continue;
                }
 -              if (!strcmp(arg, "--quiet")) {
 +              if (!strcmp(arg, "--quiet") || !strcmp(arg, "-q")) {
                        quiet = 1;
                        continue;
                }
diff --combined builtin-send-pack.c
index 0000000000000000000000000000000000000000,aedef09ef078db6722e0762d7c14f2d08d953945..947c42b95028a3a19cef45d7eafa2f4e92de651c
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,468 +1,468 @@@
 -                              error("remote '%s' is not a strict "
 -                                    "subset of local ref '%s'. "
 -                                    "maybe you are not up-to-date and "
+ #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] [--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)
+ {
+       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 do_send_pack(int in, int out, struct remote *remote, int nr_refspec, const 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, args.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 (args.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 (!args.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 (!args.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 && !args.dry_run)
+               ret = pack_objects(out, remote_refs);
+       close(out);
+       if (expect_status_report) {
+               if (receive_status(in))
+                       ret = -4;
+       }
+       if (!args.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, 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, "--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);
+       if (heads && args.send_all)
+               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, nr_heads, heads);
+       close(fd[0]);
+       close(fd[1]);
+       ret |= finish_connect(conn);
+       return !!ret;
+ }
diff --combined cache.h
index f0a25c7ffcd47d663f2d41cd29dbabc57c2cab93,119566bf6f55215036cc8450f0ad5a8e680be6be..e5ea637c2941550624ac49a12a7ea64e44b3c2d3
+++ b/cache.h
@@@ -7,7 -7,7 +7,7 @@@
  #include SHA1_HEADER
  #include <zlib.h>
  
 -#if ZLIB_VERNUM < 0x1200
 +#if defined(NO_DEFLATE_BOUND) || ZLIB_VERNUM < 0x1200
  #define deflateBound(c,s)  ((s) + (((s) + 7) >> 3) + (((s) + 63) >> 6) + 11)
  #endif
  
@@@ -222,7 -222,6 +222,7 @@@ extern const char *get_git_work_tree(vo
  #define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES"
  
  extern const char **get_pathspec(const char *prefix, const char **pathspec);
 +extern void setup_work_tree(void);
  extern const char *setup_git_directory_gently(int *);
  extern const char *setup_git_directory(void);
  extern const char *prefix_path(const char *prefix, int len, const char *path);
@@@ -504,7 -503,7 +504,7 @@@ struct ref 
  #define REF_TAGS      (1u << 2)
  
  #define CONNECT_VERBOSE       (1u << 0)
- extern struct child_process *git_connect(int fd[2], char *url, const char *prog, int flags);
+ extern struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags);
  extern int finish_connect(struct child_process *conn);
  extern int path_match(const char *path, int nr, char **match);
  extern int get_ack(int fd, unsigned char *result_sha1);
diff --combined git.c
index 4a250f7e8b84f2334c84daaf93baa0fe1f0ca344,b173f227f0f61f05b2ca1bffd07a1948dc66de16..6c5f9af13af5a59606008efe29291d53b3c0cdc0
--- 1/git.c
--- 2/git.c
+++ b/git.c
@@@ -249,9 -249,14 +249,9 @@@ static int run_command(struct cmd_struc
                prefix = setup_git_directory();
        if (p->option & USE_PAGER)
                setup_pager();
 -      if (p->option & NEED_WORK_TREE) {
 -              const char *work_tree = get_git_work_tree();
 -              const char *git_dir = get_git_dir();
 -              if (!is_absolute_path(git_dir))
 -                      set_git_dir(make_absolute_path(git_dir));
 -              if (!work_tree || chdir(work_tree))
 -                      die("%s must be run in a work tree", p->cmd);
 -      }
 +      if (p->option & NEED_WORK_TREE)
 +              setup_work_tree();
 +
        trace_argv_printf(argv, argc, "trace: built-in: git");
  
        status = p->fn(argc, argv, prefix);
@@@ -321,6 -326,7 +321,7 @@@ static void handle_internal_command(in
                { "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 },
                { "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 },
                { "rev-list", cmd_rev_list, RUN_SETUP },
                { "rev-parse", cmd_rev_parse, RUN_SETUP },
                { "revert", cmd_revert, RUN_SETUP | NEED_WORK_TREE },
 -              { "rm", cmd_rm, RUN_SETUP | NEED_WORK_TREE },
 +              { "rm", cmd_rm, RUN_SETUP },
                { "runstatus", cmd_runstatus, RUN_SETUP | NEED_WORK_TREE },
+               { "send-pack", cmd_send_pack, RUN_SETUP },
                { "shortlog", cmd_shortlog, RUN_SETUP | USE_PAGER },
                { "show-branch", cmd_show_branch, RUN_SETUP },
                { "show", cmd_show, RUN_SETUP | USE_PAGER },
diff --combined http-push.c
index 9314621a11030e66eeba7f0da6b88a813e1ba759,f461bb32481ca5a6e8b119ede9c517876cc4e699..99328f5909d4db247fa2453df9ac14cde0c61c9a
@@@ -2241,11 -2241,7 +2241,11 @@@ static int delete_remote_branch(char *p
  
                /* Remote branch must be an ancestor of remote HEAD */
                if (!verify_merge_base(head_sha1, remote_ref->old_sha1)) {
 -                      return error("The branch '%s' is not a strict subset of your current HEAD.\nIf you are sure you want to delete it, run:\n\t'git http-push -D %s %s'", remote_ref->name, remote->url, pattern);
 +                      return error("The branch '%s' is not an ancestor "
 +                                   "of your current HEAD.\n"
 +                                   "If you are sure you want to delete it,"
 +                                   " run:\n\t'git http-push -D %s %s'",
 +                                   remote_ref->name, remote->url, pattern);
                }
        }
  
@@@ -2393,7 -2389,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");
                        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 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 a strict "
 -                                    "subset of local ref '%s'. "
 -                                    "maybe you are not up-to-date and "
 +                              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);
diff --combined transport.c
index e8a2608372de06ef3f77d9a450776e3095057c9f,f4577b7fc6cc95260045403e075597e54dfce526..5cb809bff62a881e34942b2a26d7c04d46db2445
@@@ -6,6 -6,7 +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 +142,7 @@@ static void insert_packed_refs(const ch
        }
  }
  
- 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;
@@@ -380,13 -381,12 +381,13 @@@ static int disconnect_walker(struct tra
  }
  
  #ifndef NO_CURL
 -static int curl_transport_push(struct transport *transport, int refspec_nr, const char **refspec, int flags) {
 +static int curl_transport_push(struct transport *transport, int refspec_nr, const char **refspec, int flags)
 +{
        const char **argv;
        int argc;
        int err;
  
 -      argv = xmalloc((refspec_nr + 11) * sizeof(char *));
 +      argv = xmalloc((refspec_nr + 12) * sizeof(char *));
        argv[0] = "http-push";
        argc = 1;
        if (flags & TRANSPORT_PUSH_ALL)
                argv[argc++] = "--force";
        if (flags & TRANSPORT_PUSH_DRY_RUN)
                argv[argc++] = "--dry-run";
 +      if (flags & TRANSPORT_PUSH_VERBOSE)
 +              argv[argc++] = "--verbose";
        argv[argc++] = transport->url;
        while (refspec_nr--)
                argv[argc++] = *refspec++;
@@@ -430,7 -428,7 +431,7 @@@ static int missing__target(int code, in
  
  #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;
@@@ -527,7 -525,7 +528,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;
@@@ -599,7 -597,7 +600,7 @@@ static int set_git_option(struct transp
        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;
@@@ -649,53 -647,18 +650,19 @@@ static int fetch_refs_via_pack(struct t
        return 0;
  }
  
 -static int git_transport_push(struct transport *transport, int refspec_nr, const char **refspec, int flags) {
 +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.force_update = !!(flags & TRANSPORT_PUSH_FORCE);
+       args.use_thin_pack = data->thin;
 -      args.verbose = transport->verbose;
++      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)
@@@ -787,7 -750,7 +754,7 @@@ int transport_push(struct transport *tr
        return transport->push(transport, refspec_nr, refspec, flags);
  }
  
- struct ref *transport_get_remote_refs(struct transport *transport)
const struct ref *transport_get_remote_refs(struct transport *transport)
  {
        if (!transport->remote_refs)
                transport->remote_refs = transport->get_refs_list(transport);
diff --combined transport.h
index 2f80ab4b03df181dc8b041b1aace5832aa84deb1,d27f5629d2c0b6f8776e7c8f8b9858228f3191f5..a2a36d029e700c4ed26936aaf4c95f74d88ab5d7
@@@ -8,7 -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 +18,7 @@@
        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,6 +30,7 @@@
  #define TRANSPORT_PUSH_ALL 1
  #define TRANSPORT_PUSH_FORCE 2
  #define TRANSPORT_PUSH_DRY_RUN 4
 +#define TRANSPORT_PUSH_VERBOSE 8
  
  /* Returns a transport suitable for the url */
  struct transport *transport_get(struct remote *, const char *);
@@@ -62,7 -61,7 +62,7 @@@ int transport_set_option(struct transpo
  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);