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
...

1  2 
Makefile
builtin-fetch.c
builtin.h
cache.h
git.c
refs.c
transport.c
diff --combined Makefile
index 8809b0ace6d55f4d33f820f0031fe7b4ac84e9a2,af827f6ef63be2072dded20f33c2a0fddb76e26f..ccf522adce9fccb8b94ebc26d686a995d900ebbf
+++ b/Makefile
@@@ -115,8 -115,6 +115,8 @@@ all:
  #
  # Define ASCIIDOC8 if you want to format documentation with AsciiDoc 8
  #
 +# Define DOCBOOK_XSL_172 if you want to format man pages with DocBook XSL v1.72.
 +#
  # Define NO_PERL_MAKEMAKER if you cannot use Makefiles generated by perl's
  # MakeMaker (e.g. using ActiveState under Cygwin).
  #
@@@ -213,15 -211,14 +213,14 @@@ BASIC_LDFLAGS 
  
  SCRIPT_SH = \
        git-bisect.sh git-checkout.sh \
 -      git-clean.sh git-clone.sh git-commit.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 \
        git-sh-setup.sh \
        git-am.sh \
        git-merge.sh git-merge-stupid.sh git-merge-octopus.sh \
 -      git-merge-resolve.sh git-merge-ours.sh \
 +      git-merge-resolve.sh \
        git-lost-found.sh git-quiltimport.sh git-submodule.sh \
        git-filter-branch.sh \
        git-stash.sh
@@@ -243,7 -240,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 \
@@@ -330,7 -327,6 +329,7 @@@ BUILTIN_OBJS = 
        builtin-check-attr.o \
        builtin-checkout-index.o \
        builtin-check-ref-format.o \
 +      builtin-clean.o \
        builtin-commit-tree.o \
        builtin-count-objects.o \
        builtin-describe.o \
        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-merge-file.o \
 +      builtin-merge-ours.o \
        builtin-mv.o \
        builtin-name-rev.o \
        builtin-pack-objects.o \
        builtin-push.o \
        builtin-read-tree.o \
        builtin-reflog.o \
+       builtin-send-pack.o \
        builtin-config.o \
        builtin-rerere.o \
        builtin-reset.o \
@@@ -418,17 -415,18 +419,17 @@@ ifeq ($(uname_S),SunOS
        NO_STRCASESTR = YesPlease
        NO_MEMMEM = YesPlease
        NO_HSTRERROR = YesPlease
 +      NO_MKDTEMP = YesPlease
        ifeq ($(uname_R),5.8)
                NEEDS_LIBICONV = YesPlease
                NO_UNSETENV = YesPlease
                NO_SETENV = YesPlease
 -              NO_MKDTEMP = YesPlease
                NO_C99_FORMAT = YesPlease
                NO_STRTOUMAX = YesPlease
        endif
        ifeq ($(uname_R),5.9)
                NO_UNSETENV = YesPlease
                NO_SETENV = YesPlease
 -              NO_MKDTEMP = YesPlease
                NO_C99_FORMAT = YesPlease
                NO_STRTOUMAX = YesPlease
        endif
@@@ -755,7 -753,7 +756,7 @@@ TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_
  LIBS = $(GITLIBS) $(EXTLIBS)
  
  BASIC_CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER_SQ)' \
 -      -DETC_GITCONFIG='"$(ETC_GITCONFIG_SQ)"' $(COMPAT_CFLAGS)
 +      $(COMPAT_CFLAGS)
  LIB_OBJS += $(COMPAT_OBJS)
  
  ALL_CFLAGS += $(BASIC_CFLAGS)
@@@ -904,9 -902,6 +905,9 @@@ exec_cmd.o: exec_cmd.c GIT-CFLAG
  builtin-init-db.o: builtin-init-db.c GIT-CFLAGS
        $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) -DDEFAULT_GIT_TEMPLATE_DIR='"$(template_dir_SQ)"' $<
  
 +config.o: config.c GIT-CFLAGS
 +      $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) -DETC_GITCONFIG='"$(ETC_GITCONFIG_SQ)"' $<
 +
  http.o: http.c GIT-CFLAGS
        $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) -DGIT_USER_AGENT='"git/$(GIT_VERSION)"' $<
  
diff --combined builtin-fetch.c
index be9e3ea2bcea0d0ab88a51d396aff5714822b77a,6b1750d28bcc69ed5d8ac5b30b96dab29608fb28..31e138eab82a4a710916ff88386bd82bcf2f11d6
@@@ -8,12 -8,10 +8,12 @@@
  #include "path-list.h"
  #include "remote.h"
  #include "transport.h"
 +#include "run-command.h"
  
  static const char fetch_usage[] = "git-fetch [-a | --append] [--upload-pack <upload-pack>] [-f | --force] [--no-tags] [-t | --tags] [-k | --keep] [-u | --update-head-ok] [--depth <depth>] [-v | --verbose] [<repository> <refspec>...]";
  
  static int append, force, tags, no_tags, update_head_ok, verbose, quiet;
 +static const char *depth;
  static char *default_rla = NULL;
  static struct transport *transport;
  
@@@ -31,7 -29,7 +31,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)
  {
@@@ -79,7 -77,7 +79,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++) {
@@@ -154,7 -152,6 +154,7 @@@ static int s_update_ref(const char *act
  }
  
  #define SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
 +#define REFCOL_WIDTH  10
  
  static int update_local_ref(struct ref *ref,
                            const char *remote,
  
        if (!hashcmp(ref->old_sha1, ref->new_sha1)) {
                if (verbose)
 -                      sprintf(display, "= %-*s %s -> %s", SUMMARY_WIDTH,
 -                              "[up to date]", remote, pretty_ref);
 +                      sprintf(display, "= %-*s %-*s -> %s", SUMMARY_WIDTH,
 +                              "[up to date]", REFCOL_WIDTH, 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...
                 */
 -              sprintf(display, "! %-*s %s -> %s  (can't  fetch in current branch)",
 -                      SUMMARY_WIDTH, "[rejected]", remote, pretty_ref);
 +              sprintf(display, "! %-*s %-*s -> %s  (can't fetch in current branch)",
 +                      SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote,
 +                      pretty_ref);
                return 1;
        }
  
        if (!is_null_sha1(ref->old_sha1) &&
            !prefixcmp(ref->name, "refs/tags/")) {
 -              sprintf(display, "- %-*s %s -> %s",
 -                      SUMMARY_WIDTH, "[tag update]", remote, pretty_ref);
 +              sprintf(display, "- %-*s %-*s -> %s",
 +                      SUMMARY_WIDTH, "[tag update]", REFCOL_WIDTH, remote,
 +                      pretty_ref);
                return s_update_ref("updating tag", ref, 0);
        }
  
                        what = "[new branch]";
                }
  
 -              sprintf(display, "* %-*s %s -> %s",
 -                      SUMMARY_WIDTH, what, remote, pretty_ref);
 +              sprintf(display, "* %-*s %-*s -> %s", SUMMARY_WIDTH, what,
 +                      REFCOL_WIDTH, remote, pretty_ref);
                return s_update_ref(msg, ref, 0);
        }
  
                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);
 +              sprintf(display, "  %-*s %-*s -> %s", SUMMARY_WIDTH, quickref,
 +                      REFCOL_WIDTH, remote, pretty_ref);
                return s_update_ref("fast forward", ref, 1);
        } 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);
 +              sprintf(display, "+ %-*s %-*s -> %s  (forced update)",
 +                      SUMMARY_WIDTH, quickref, REFCOL_WIDTH, 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);
 +              sprintf(display, "! %-*s %-*s -> %s  (non fast forward)",
 +                      SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote,
 +                      pretty_ref);
                return 1;
        }
  }
@@@ -337,72 -330,9 +337,72 @@@ static void store_updated_refs(const ch
        fclose(fp);
  }
  
 +/*
 + * We would want to bypass the object transfer altogether if
 + * everything we are going to fetch already exists and connected
 + * locally.
 + *
 + * The refs we are going to fetch are in to_fetch (nr_heads in
 + * total).  If running
 + *
 + *  $ git-rev-list --objects to_fetch[0] to_fetch[1] ... --not --all
 + *
 + * does not error out, that means everything reachable from the
 + * refs we are going to fetch exists and is connected to some of
 + * our existing refs.
 + */
 +static int quickfetch(struct ref *ref_map)
 +{
 +      struct child_process revlist;
 +      struct ref *ref;
 +      char **argv;
 +      int i, err;
 +
 +      /*
 +       * If we are deepening a shallow clone we already have these
 +       * objects reachable.  Running rev-list here will return with
 +       * a good (0) exit status and we'll bypass the fetch that we
 +       * really need to perform.  Claiming failure now will ensure
 +       * we perform the network exchange to deepen our history.
 +       */
 +      if (depth)
 +              return -1;
 +
 +      for (i = 0, ref = ref_map; ref; ref = ref->next)
 +              i++;
 +      if (!i)
 +              return 0;
 +
 +      argv = xmalloc(sizeof(*argv) * (i + 6));
 +      i = 0;
 +      argv[i++] = xstrdup("rev-list");
 +      argv[i++] = xstrdup("--quiet");
 +      argv[i++] = xstrdup("--objects");
 +      for (ref = ref_map; ref; ref = ref->next)
 +              argv[i++] = xstrdup(sha1_to_hex(ref->old_sha1));
 +      argv[i++] = xstrdup("--not");
 +      argv[i++] = xstrdup("--all");
 +      argv[i++] = NULL;
 +
 +      memset(&revlist, 0, sizeof(revlist));
 +      revlist.argv = (const char**)argv;
 +      revlist.git_cmd = 1;
 +      revlist.no_stdin = 1;
 +      revlist.no_stdout = 1;
 +      revlist.no_stderr = 1;
 +      err = run_command(&revlist);
 +
 +      for (i = 0; argv[i]; i++)
 +              free(argv[i]);
 +      free(argv);
 +      return err;
 +}
 +
  static int fetch_refs(struct transport *transport, struct ref *ref_map)
  {
 -      int ret = transport_fetch_refs(transport, ref_map);
 +      int ret = quickfetch(ref_map);
 +      if (ret)
 +              ret = transport_fetch_refs(transport, ref_map);
        if (!ret)
                store_updated_refs(transport->url, ref_map);
        transport_unlock_pack(transport);
@@@ -424,12 -354,12 +424,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)) {
 +                  has_sha1_file(ref->old_sha1)) {
                        path_list_insert(ref_name, &new_refs);
  
                        rm = alloc_ref(strlen(ref_name) + 1);
@@@ -538,7 -468,7 +538,7 @@@ int cmd_fetch(int argc, const char **ar
        static const char **refs = NULL;
        int ref_nr = 0;
        int cmd_len = 0;
 -      const char *depth = NULL, *upload_pack = NULL;
 +      const char *upload_pack = NULL;
        int keep = 0;
  
        for (i = 1; i < argc; i++) {
diff --combined builtin.h
index 6ecf12be328a8a9ef207999c78246420bcf81ee3,525107f385112eb0f6a6d06f42723a73f6bae134..98ada7b7f705d70ad27cccfaaf3c21c13f66dcc4
+++ b/builtin.h
@@@ -24,7 -24,6 +24,7 @@@ extern int cmd_check_attr(int argc, con
  extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix);
  extern int cmd_cherry(int argc, const char **argv, const char *prefix);
  extern int cmd_cherry_pick(int argc, const char **argv, const char *prefix);
 +extern int cmd_clean(int argc, const char **argv, const char *prefix);
  extern int cmd_commit_tree(int argc, const char **argv, const char *prefix);
  extern int cmd_count_objects(int argc, const char **argv, const char *prefix);
  extern int cmd_describe(int argc, const char **argv, const char *prefix);
@@@ -49,10 -48,10 +49,11 @@@ extern int cmd_log(int argc, const cha
  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);
 +extern int cmd_merge_ours(int argc, const char **argv, const char *prefix);
  extern int cmd_merge_file(int argc, const char **argv, const char *prefix);
  extern int cmd_mv(int argc, const char **argv, const char *prefix);
  extern int cmd_name_rev(int argc, const char **argv, const char *prefix);
@@@ -71,6 -70,7 +72,7 @@@ extern int cmd_rev_parse(int argc, cons
  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 --combined cache.h
index a0b93770d4ceeb19dafe495248f844b385fe2003,e0c1cc3fe86944e7b45926e0fff2ba4a4377ecf2..aaa135bfde23cd2529e3707e4499be7f75917336
+++ b/cache.h
@@@ -175,8 -175,8 +175,8 @@@ extern struct index_state the_index
  #define remove_file_from_cache(path) remove_file_from_index(&the_index, (path))
  #define add_file_to_cache(path, verbose) add_file_to_index(&the_index, (path), (verbose))
  #define refresh_cache(flags) refresh_index(&the_index, (flags), NULL, NULL)
 -#define ce_match_stat(ce, st, really) ie_match_stat(&the_index, (ce), (st), (really))
 -#define ce_modified(ce, st, really) ie_modified(&the_index, (ce), (st), (really))
 +#define ce_match_stat(ce, st, options) ie_match_stat(&the_index, (ce), (st), (options))
 +#define ce_modified(ce, st, options) ie_modified(&the_index, (ce), (st), (options))
  #endif
  
  enum object_type {
@@@ -268,14 -268,8 +268,14 @@@ extern int remove_file_from_index(struc
  extern int add_file_to_index(struct index_state *, const char *path, int verbose);
  extern struct cache_entry *make_cache_entry(unsigned int mode, const unsigned char *sha1, const char *path, int stage, int refresh);
  extern int ce_same_name(struct cache_entry *a, struct cache_entry *b);
 -extern int ie_match_stat(struct index_state *, struct cache_entry *, struct stat *, int);
 -extern int ie_modified(struct index_state *, struct cache_entry *, struct stat *, int);
 +
 +/* do stat comparison even if CE_VALID is true */
 +#define CE_MATCH_IGNORE_VALID         01
 +/* do not check the contents but report dirty on racily-clean entries */
 +#define CE_MATCH_RACY_IS_DIRTY        02
 +extern int ie_match_stat(struct index_state *, struct cache_entry *, struct stat *, unsigned int);
 +extern int ie_modified(struct index_state *, struct cache_entry *, struct stat *, unsigned int);
 +
  extern int ce_path_match(const struct cache_entry *ce, const char **pathspec);
  extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, enum object_type type, const char *path);
  extern int index_pipe(unsigned char *sha1, int fd, const char *type, int write_object);
@@@ -290,7 -284,6 +290,7 @@@ extern int refresh_index(struct index_s
  
  struct lock_file {
        struct lock_file *next;
 +      int fd;
        pid_t owner;
        char on_list;
        char filename[PATH_MAX];
@@@ -500,8 -493,20 +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 */
  };
  #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);
@@@ -557,7 -564,6 +571,7 @@@ extern int git_config_bool(const char *
  extern int git_config_set(const char *, const char *);
  extern int git_config_set_multivar(const char *, const char *, const char *, int);
  extern int git_config_rename_section(const char *, const char *);
 +extern const char *git_etc_gitconfig(void);
  extern int check_repository_format_version(const char *var, const char *value);
  
  #define MAX_GITNAME (1000)
@@@ -583,7 -589,6 +597,7 @@@ extern int pager_in_use
  extern int pager_use_color;
  
  extern char *editor_program;
 +extern char *excludes_file;
  
  /* base85 */
  int decode_85(char *dst, const char *line, int linelen);
diff --combined git.c
index 517ef414f7f9cf60136f6129b476b93f8a56ae41,6c5f9af13af5a59606008efe29291d53b3c0cdc0..01bbbc73258dc66d8d3a8253cc9328b5b18346aa
--- 1/git.c
--- 2/git.c
+++ b/git.c
@@@ -256,7 -256,7 +256,7 @@@ static int run_command(struct cmd_struc
  
        status = p->fn(argc, argv, prefix);
        if (status)
 -              return status;
 +              return status & 0xff;
  
        /* Somebody closed stdout? */
        if (fstat(fileno(stdout), &st))
@@@ -293,7 -293,6 +293,7 @@@ static void handle_internal_command(in
                { "check-attr", cmd_check_attr, RUN_SETUP | NEED_WORK_TREE },
                { "cherry", cmd_cherry, RUN_SETUP },
                { "cherry-pick", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE },
 +              { "clean", cmd_clean, RUN_SETUP | NEED_WORK_TREE },
                { "commit-tree", cmd_commit_tree, RUN_SETUP },
                { "config", cmd_config },
                { "count-objects", cmd_count_objects, RUN_SETUP },
                { "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 },
                { "merge-file", cmd_merge_file },
 +              { "merge-ours", cmd_merge_ours, 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 },
                { "rerere", cmd_rerere, RUN_SETUP },
                { "reset", cmd_reset, RUN_SETUP },
                { "rev-list", cmd_rev_list, RUN_SETUP },
 -              { "rev-parse", cmd_rev_parse, RUN_SETUP },
 +              { "rev-parse", cmd_rev_parse },
                { "revert", cmd_revert, RUN_SETUP | NEED_WORK_TREE },
                { "rm", cmd_rm, RUN_SETUP },
                { "runstatus", cmd_runstatus, RUN_SETUP | NEED_WORK_TREE },
+               { "send-pack", cmd_send_pack, RUN_SETUP },
                { "shortlog", cmd_shortlog, RUN_SETUP | USE_PAGER },
                { "show-branch", cmd_show_branch, RUN_SETUP },
                { "show", cmd_show, RUN_SETUP | USE_PAGER },
diff --combined refs.c
index ae532540cf2592aee2be7610ac811082c3a9b6be,387c588c743704665756379d7ac1f0610752c0fb..54ec98d153889f40313dba9a5ee8f07ddd0e160a
--- 1/refs.c
--- 2/refs.c
+++ b/refs.c
@@@ -580,6 -580,18 +580,6 @@@ int for_each_remote_ref(each_ref_fn fn
        return do_for_each_ref("refs/remotes/", fn, 13, cb_data);
  }
  
 -/* NEEDSWORK: This is only used by ssh-upload and it should go; the
 - * caller should do resolve_ref or read_ref like everybody else.  Or
 - * maybe everybody else should use get_ref_sha1() instead of doing
 - * read_ref().
 - */
 -int get_ref_sha1(const char *ref, unsigned char *sha1)
 -{
 -      if (check_ref_format(ref))
 -              return -1;
 -      return read_ref(mkpath("refs/%s", ref), sha1);
 -}
 -
  /*
   * Make sure "ref" is something reasonable to have under ".git/refs/";
   * We do not like it if:
@@@ -1433,3 -1445,11 +1433,11 @@@ int update_ref(const char *action, cons
        }
        return 0;
  }
+ struct ref *find_ref_by_name(struct ref *list, const char *name)
+ {
+       for ( ; list; list = list->next)
+               if (!strcmp(list->name, name))
+                       return list;
+       return NULL;
+ }
diff --combined transport.c
index 43b9e7c410882d82fd6b08532e30df9b82b3ffb1,8d9bdbe8dedd1c050e017d509cbc9e87d98a6e88..50db9807d003162c81f391e08f247bac7ce410c9
@@@ -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;
@@@ -283,6 -284,9 +284,9 @@@ static int rsync_transport_push(struct 
        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);
  
  /* Generic functions for using commit walkers */
  
 +#ifndef NO_CURL /* http fetch is the only user */
  static int fetch_objs_via_walker(struct transport *transport,
                                 int nr_objs, struct ref **to_fetch)
  {
        free(dest);
        return 0;
  }
 +#endif /* NO_CURL */
  
  static int disconnect_walker(struct transport *transport)
  {
@@@ -388,6 -390,9 +392,9 @@@ static int curl_transport_push(struct t
        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 -437,7 +439,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;
@@@ -529,7 -534,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 -606,7 +608,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;
@@@ -654,50 -659,17 +661,17 @@@ static int fetch_refs_via_pack(struct t
  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 -761,7 +763,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);