Merge branch 'db/fetch-pack'
authorJunio C Hamano <gitster@pobox.com>
Thu, 25 Oct 2007 04:59:50 +0000 (21:59 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 25 Oct 2007 04:59:50 +0000 (21:59 -0700)
* db/fetch-pack: (60 commits)
Define compat version of mkdtemp for systems lacking it
Avoid scary errors about tagged trees/blobs during git-fetch
fetch: if not fetching from default remote, ignore default merge
Support 'push --dry-run' for http transport
Support 'push --dry-run' for rsync transport
Fix 'push --all branch...' error handling
Fix compilation when NO_CURL is defined
Added a test for fetching remote tags when there is not tags.
Fix a crash in ls-remote when refspec expands into nothing
Remove duplicate ref matches in fetch
Restore default verbosity for http fetches.
fetch/push: readd rsync support
Introduce remove_dir_recursively()
bundle transport: fix an alloc_ref() call
Allow abbreviations in the first refspec to be merged
Prevent send-pack from segfaulting when a branch doesn't match
Cleanup unnecessary break in remote.c
Cleanup style nit of 'x == NULL' in remote.c
Fix memory leaks when disconnecting transport instances
Ensure builtin-fetch honors {fetch,transfer}.unpackLimit
...

1  2 
Makefile
dir.c
git.c
receive-pack.c
remote.c
send-pack.c
diff --combined Makefile
index 9f81c73af7d58ec75c6ca448ff6f96a3ba97c48d,6287418df3d326f029341fd1055db420ffd90a0d..b7289204ebfa628195acaf8b0e087289c1468c62
+++ b/Makefile
@@@ -38,6 -38,8 +38,8 @@@ all:
  #
  # Define NO_SETENV if you don't have setenv in the C library.
  #
+ # Define NO_MKDTEMP if you don't have mkdtemp in the C library.
+ #
  # Define NO_SYMLINK_HEAD if you never want .git/HEAD to be a symbolic link.
  # Enable it on Windows.  By default, symrefs are still used.
  #
@@@ -165,7 -167,6 +167,7 @@@ GITWEB_CONFIG = gitweb_config.per
  GITWEB_HOME_LINK_STR = projects
  GITWEB_SITENAME =
  GITWEB_PROJECTROOT = /pub/git
 +GITWEB_PROJECT_MAXDEPTH = 2007
  GITWEB_EXPORT_OK =
  GITWEB_STRICT_EXPORT =
  GITWEB_BASE_URL =
@@@ -208,7 -209,6 +210,6 @@@ BASIC_LDFLAGS 
  SCRIPT_SH = \
        git-bisect.sh git-checkout.sh \
        git-clean.sh git-clone.sh git-commit.sh \
-       git-fetch.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 \
@@@ -235,14 -235,14 +236,14 @@@ SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH
  # ... and all the rest that could be moved out of bindir to gitexecdir
  PROGRAMS = \
        git-fetch-pack$X \
-       git-hash-object$X git-index-pack$X git-local-fetch$X \
+       git-hash-object$X git-index-pack$X \
        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-send-pack$X git-shell$X \
-       git-show-index$X git-ssh-fetch$X \
-       git-ssh-upload$X git-unpack-file$X \
+       git-show-index$X \
+       git-unpack-file$X \
        git-update-server-info$X \
        git-upload-pack$X \
        git-pack-redundant$X git-var$X \
@@@ -270,9 -270,6 +271,6 @@@ ifndef NO_TCLT
  OTHER_PROGRAMS += gitk-wish
  endif
  
- # Backward compatibility -- to be removed after 1.0
- PROGRAMS += git-ssh-pull$X git-ssh-push$X
  # Set paths to tools early so that they can be used for version tests.
  ifndef SHELL_PATH
        SHELL_PATH = /bin/sh
@@@ -292,7 -289,7 +290,7 @@@ LIB_H = 
        run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \
        tree-walk.h log-tree.h dir.h path-list.h unpack-trees.h builtin.h \
        utf8.h reflog-walk.h patch-ids.h attr.h decorate.h progress.h \
-       mailmap.h remote.h
+       mailmap.h remote.h transport.h
  
  DIFF_OBJS = \
        diff.o diff-lib.o diffcore-break.o diffcore-order.o \
@@@ -314,7 -311,8 +312,8 @@@ LIB_OBJS = 
        write_or_die.o trace.o list-objects.o grep.o match-trees.o \
        alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \
        color.o wt-status.o archive-zip.o archive-tar.o shallow.o utf8.o \
-       convert.o attr.o decorate.o progress.o mailmap.o symlinks.o remote.o
+       convert.o attr.o decorate.o progress.o mailmap.o symlinks.o remote.o \
+       transport.o bundle.o walker.o
  
  BUILTIN_OBJS = \
        builtin-add.o \
        builtin-diff-files.o \
        builtin-diff-index.o \
        builtin-diff-tree.o \
+       builtin-fetch.o \
+       builtin-fetch-pack.o \
        builtin-fetch--tool.o \
        builtin-fmt-merge-msg.o \
        builtin-for-each-ref.o \
@@@ -416,12 -416,14 +417,14 @@@ ifeq ($(uname_S),SunOS
                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
@@@ -518,7 -520,9 +521,9 @@@ els
        CC_LD_DYNPATH = -R
  endif
  
- ifndef NO_CURL
+ ifdef NO_CURL
+       BASIC_CFLAGS += -DNO_CURL
+ else
        ifdef CURLDIR
                # Try "-Wl,-rpath=$(CURLDIR)/$(lib)" in such a case.
                BASIC_CFLAGS += -I$(CURLDIR)/include
        else
                CURL_LIBCURL = -lcurl
        endif
-       PROGRAMS += git-http-fetch$X
+       BUILTIN_OBJS += builtin-http-fetch.o
+       EXTLIBS += $(CURL_LIBCURL)
+       LIB_OBJS += http.o http-walker.o
        curl_check := $(shell (echo 070908; curl-config --vernum) | sort -r | sed -ne 2p)
        ifeq "$(curl_check)" "070908"
                ifndef NO_EXPAT
@@@ -608,6 -614,10 +615,10 @@@ ifdef NO_SETEN
        COMPAT_CFLAGS += -DNO_SETENV
        COMPAT_OBJS += compat/setenv.o
  endif
+ ifdef NO_MKDTEMP
+       COMPAT_CFLAGS += -DNO_MKDTEMP
+       COMPAT_OBJS += compat/mkdtemp.o
+ endif
  ifdef NO_UNSETENV
        COMPAT_CFLAGS += -DNO_UNSETENV
        COMPAT_OBJS += compat/unsetenv.o
@@@ -832,7 -842,6 +843,7 @@@ gitweb/gitweb.cgi: gitweb/gitweb.per
            -e 's|++GITWEB_HOME_LINK_STR++|$(GITWEB_HOME_LINK_STR)|g' \
            -e 's|++GITWEB_SITENAME++|$(GITWEB_SITENAME)|g' \
            -e 's|++GITWEB_PROJECTROOT++|$(GITWEB_PROJECTROOT)|g' \
 +          -e 's|"++GITWEB_PROJECT_MAXDEPTH++"|$(GITWEB_PROJECT_MAXDEPTH)|g' \
            -e 's|++GITWEB_EXPORT_OK++|$(GITWEB_EXPORT_OK)|g' \
            -e 's|++GITWEB_STRICT_EXPORT++|$(GITWEB_STRICT_EXPORT)|g' \
            -e 's|++GITWEB_BASE_URL++|$(GITWEB_BASE_URL)|g' \
@@@ -889,33 -898,22 +900,22 @@@ http.o: http.c GIT-CFLAG
        $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) -DGIT_USER_AGENT='"git/$(GIT_VERSION)"' $<
  
  ifdef NO_EXPAT
- http-fetch.o: http-fetch.c http.h GIT-CFLAGS
+ http-walker.o: http-walker.c http.h GIT-CFLAGS
        $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) -DNO_EXPAT $<
  endif
  
  git-%$X: %.o $(GITLIBS)
        $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
  
- ssh-pull.o: ssh-fetch.c
- ssh-push.o: ssh-upload.c
- git-local-fetch$X: fetch.o
- git-ssh-fetch$X: rsh.o fetch.o
- git-ssh-upload$X: rsh.o
- git-ssh-pull$X: rsh.o fetch.o
- git-ssh-push$X: rsh.o
  git-imap-send$X: imap-send.o $(LIB_FILE)
  
- http.o http-fetch.o http-push.o: http.h
- git-http-fetch$X: fetch.o http.o http-fetch.o $(GITLIBS)
-       $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
-               $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
+ http.o http-walker.o http-push.o: http.h
  
  git-http-push$X: revision.o http.o http-push.o $(GITLIBS)
        $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
                $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
  
- $(LIB_OBJS) $(BUILTIN_OBJS) fetch.o: $(LIB_H)
+ $(LIB_OBJS) $(BUILTIN_OBJS): $(LIB_H)
  $(patsubst git-%$X,%.o,$(PROGRAMS)): $(LIB_H) $(wildcard */*.h)
  $(DIFF_OBJS): diffcore.h
  
@@@ -1131,8 -1129,7 +1131,7 @@@ check-docs:
                git-merge-octopus | git-merge-ours | git-merge-recursive | \
                git-merge-resolve | git-merge-stupid | \
                git-add--interactive | git-fsck-objects | git-init-db | \
-               git-repo-config | git-fetch--tool | \
-               git-ssh-pull | git-ssh-push ) continue ;; \
+               git-repo-config | git-fetch--tool ) continue ;; \
                esac ; \
                test -f "Documentation/$$v.txt" || \
                echo "no doc: $$v"; \
diff --combined dir.c
index f843c4dd208ac0f37f9c70383e522590688f1966,b18257e65ffaf440eb0944c42624e19052178374..4c17d3643eebf0d9071003065b5c120db130f9ab
--- 1/dir.c
--- 2/dir.c
+++ b/dir.c
@@@ -443,24 -443,6 +443,24 @@@ static int in_pathspec(const char *path
        return 0;
  }
  
 +static int get_dtype(struct dirent *de, const char *path)
 +{
 +      int dtype = DTYPE(de);
 +      struct stat st;
 +
 +      if (dtype != DT_UNKNOWN)
 +              return dtype;
 +      if (lstat(path, &st))
 +              return dtype;
 +      if (S_ISREG(st.st_mode))
 +              return DT_REG;
 +      if (S_ISDIR(st.st_mode))
 +              return DT_DIR;
 +      if (S_ISLNK(st.st_mode))
 +              return DT_LNK;
 +      return dtype;
 +}
 +
  /*
   * Read a directory tree. We currently ignore anything but
   * directories, regular files and symlinks. That's because git
@@@ -484,7 -466,7 +484,7 @@@ static int read_directory_recursive(str
                exclude_stk = push_exclude_per_directory(dir, base, baselen);
  
                while ((de = readdir(fdir)) != NULL) {
 -                      int len;
 +                      int len, dtype;
                        int exclude;
  
                        if ((de->d_name[0] == '.') &&
                        if (exclude && dir->collect_ignored
                            && in_pathspec(fullname, baselen + len, simplify))
                                dir_add_ignored(dir, fullname, baselen + len);
 -                      if (exclude != dir->show_ignored) {
 -                              if (!dir->show_ignored || DTYPE(de) != DT_DIR) {
 +
 +                      /*
 +                       * Excluded? If we don't explicitly want to show
 +                       * ignored files, ignore it
 +                       */
 +                      if (exclude && !dir->show_ignored)
 +                              continue;
 +
 +                      dtype = get_dtype(de, fullname);
 +
 +                      /*
 +                       * Do we want to see just the ignored files?
 +                       * We still need to recurse into directories,
 +                       * even if we don't ignore them, since the
 +                       * directory may contain files that we do..
 +                       */
 +                      if (!exclude && dir->show_ignored) {
 +                              if (dtype != DT_DIR)
                                        continue;
 -                              }
                        }
  
 -                      switch (DTYPE(de)) {
 -                      struct stat st;
 +                      switch (dtype) {
                        default:
                                continue;
 -                      case DT_UNKNOWN:
 -                              if (lstat(fullname, &st))
 -                                      continue;
 -                              if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))
 -                                      break;
 -                              if (!S_ISDIR(st.st_mode))
 -                                      continue;
 -                              /* fallthrough */
                        case DT_DIR:
                                memcpy(fullname + baselen + len, "/", 2);
                                len++;
@@@ -709,3 -685,44 +709,44 @@@ int is_inside_dir(const char *dir
        char buffer[PATH_MAX];
        return get_relative_cwd(buffer, sizeof(buffer), dir) != NULL;
  }
+ int remove_dir_recursively(struct strbuf *path, int only_empty)
+ {
+       DIR *dir = opendir(path->buf);
+       struct dirent *e;
+       int ret = 0, original_len = path->len, len;
+       if (!dir)
+               return -1;
+       if (path->buf[original_len - 1] != '/')
+               strbuf_addch(path, '/');
+       len = path->len;
+       while ((e = readdir(dir)) != NULL) {
+               struct stat st;
+               if ((e->d_name[0] == '.') &&
+                   ((e->d_name[1] == 0) ||
+                    ((e->d_name[1] == '.') && e->d_name[2] == 0)))
+                       continue; /* "." and ".." */
+               strbuf_setlen(path, len);
+               strbuf_addstr(path, e->d_name);
+               if (lstat(path->buf, &st))
+                       ; /* fall thru */
+               else if (S_ISDIR(st.st_mode)) {
+                       if (!remove_dir_recursively(path, only_empty))
+                               continue; /* happy */
+               } else if (!only_empty && !unlink(path->buf))
+                       continue; /* happy, too */
+               /* path too long, stat fails, or non-directory still exists */
+               ret = -1;
+               break;
+       }
+       closedir(dir);
+       strbuf_setlen(path, original_len);
+       if (!ret)
+               ret = rmdir(path->buf);
+       return ret;
+ }
diff --combined git.c
index 853e66cddbbcc00943cfc0af06741ca8116b7966,d7c6bcaed6596a9c9c9bccf4c2d14d076b1201b3..23a430c3690ed1f921ec22196edf1f0062bc6dcd
--- 1/git.c
--- 2/git.c
+++ b/git.c
@@@ -328,6 -328,8 +328,8 @@@ static void handle_internal_command(in
                { "diff-files", cmd_diff_files },
                { "diff-index", cmd_diff_index, RUN_SETUP },
                { "diff-tree", cmd_diff_tree, RUN_SETUP },
+               { "fetch", cmd_fetch, RUN_SETUP },
+               { "fetch-pack", cmd_fetch_pack, RUN_SETUP },
                { "fetch--tool", cmd_fetch__tool, RUN_SETUP },
                { "fmt-merge-msg", cmd_fmt_merge_msg, RUN_SETUP },
                { "for-each-ref", cmd_for_each_ref, RUN_SETUP },
                { "get-tar-commit-id", cmd_get_tar_commit_id },
                { "grep", cmd_grep, RUN_SETUP | USE_PAGER },
                { "help", cmd_help },
+ #ifndef NO_CURL
+               { "http-fetch", cmd_http_fetch, RUN_SETUP },
+ #endif
                { "init", cmd_init_db },
                { "init-db", cmd_init_db },
                { "log", cmd_log, RUN_SETUP | USE_PAGER },
@@@ -409,14 -414,13 +414,14 @@@ int main(int argc, const char **argv
        /*
         * Take the basename of argv[0] as the command
         * name, and the dirname as the default exec_path
 -       * if it's an absolute path and we don't have
 -       * anything better.
 +       * if we don't have anything better.
         */
        if (slash) {
                *slash++ = 0;
                if (*cmd == '/')
                        exec_path = cmd;
 +              else
 +                      exec_path = xstrdup(make_absolute_path(cmd));
                cmd = slash;
        }
  
diff --combined receive-pack.c
index 1521d0b2de77eaccf3db1ebf357fd7560b2de1b0,61e9929763133e9dbfb17b9c3097b2149b4fade0..38e35c06b9e73376adde597c4fe28490a2e886b1
@@@ -166,7 -166,7 +166,7 @@@ static const char *update(struct comman
        struct ref_lock *lock;
  
        if (!prefixcmp(name, "refs/") && check_ref_format(name + 5)) {
 -              error("refusing to create funny ref '%s' locally", name);
 +              error("refusing to create funny ref '%s' remotely", name);
                return "funny refname";
        }
  
@@@ -382,9 -382,8 +382,8 @@@ static const char *unpack(void
                }
        } else {
                const char *keeper[6];
-               int s, len, status;
+               int s, status;
                char keep_arg[256];
-               char packname[46];
                struct child_process ip;
  
                s = sprintf(keep_arg, "--keep=receive-pack %i on ", getpid());
                ip.git_cmd = 1;
                if (start_command(&ip))
                        return "index-pack fork failed";
-               /*
-                * The first thing we expects from index-pack's output
-                * is "pack\t%40s\n" or "keep\t%40s\n" (46 bytes) where
-                * %40s is the newly created pack SHA1 name.  In the "keep"
-                * case, we need it to remove the corresponding .keep file
-                * later on.  If we don't get that then tough luck with it.
-                */
-               for (len = 0;
-                    len < 46 && (s = xread(ip.out, packname+len, 46-len)) > 0;
-                    len += s);
-               if (len == 46 && packname[45] == '\n' &&
-                   memcmp(packname, "keep\t", 5) == 0) {
-                       char path[PATH_MAX];
-                       packname[45] = 0;
-                       snprintf(path, sizeof(path), "%s/pack/pack-%s.keep",
-                                get_object_directory(), packname + 5);
-                       pack_lockfile = xstrdup(path);
-               }
+               pack_lockfile = index_pack_lockfile(ip.out);
                status = finish_command(&ip);
                if (!status) {
                        reprepare_packed_git();
diff --combined remote.c
index cdbbdcb00dee400f4fe654a86c1dd0060a613904,b20e2be4332b3b4a17c50988f2acdcd80834a6f7..170015aabfc18d028b22a5200cbddc5a5ec3f042
+++ b/remote.c
@@@ -5,6 -5,12 +5,12 @@@
  static struct remote **remotes;
  static int allocated_remotes;
  
+ static struct branch **branches;
+ static int allocated_branches;
+ static struct branch *current_branch;
+ static const char *default_remote_name;
  #define BUF_SIZE (2048)
  static char buffer[BUF_SIZE];
  
@@@ -26,13 -32,13 +32,13 @@@ static void add_fetch_refspec(struct re
        remote->fetch_refspec_nr = nr;
  }
  
- static void add_uri(struct remote *remote, const char *uri)
+ static void add_url(struct remote *remote, const char *url)
  {
-       int nr = remote->uri_nr + 1;
-       remote->uri =
-               xrealloc(remote->uri, nr * sizeof(char *));
-       remote->uri[nr-1] = uri;
-       remote->uri_nr = nr;
+       int nr = remote->url_nr + 1;
+       remote->url =
+               xrealloc(remote->url, nr * sizeof(char *));
+       remote->url[nr-1] = url;
+       remote->url_nr = nr;
  }
  
  static struct remote *make_remote(const char *name, int len)
        return remotes[empty];
  }
  
+ static void add_merge(struct branch *branch, const char *name)
+ {
+       int nr = branch->merge_nr + 1;
+       branch->merge_name =
+               xrealloc(branch->merge_name, nr * sizeof(char *));
+       branch->merge_name[nr-1] = name;
+       branch->merge_nr = nr;
+ }
+ static struct branch *make_branch(const char *name, int len)
+ {
+       int i, empty = -1;
+       char *refname;
+       for (i = 0; i < allocated_branches; i++) {
+               if (!branches[i]) {
+                       if (empty < 0)
+                               empty = i;
+               } else {
+                       if (len ? (!strncmp(name, branches[i]->name, len) &&
+                                  !branches[i]->name[len]) :
+                           !strcmp(name, branches[i]->name))
+                               return branches[i];
+               }
+       }
+       if (empty < 0) {
+               empty = allocated_branches;
+               allocated_branches += allocated_branches ? allocated_branches : 1;
+               branches = xrealloc(branches,
+                                  sizeof(*branches) * allocated_branches);
+               memset(branches + empty, 0,
+                      (allocated_branches - empty) * sizeof(*branches));
+       }
+       branches[empty] = xcalloc(1, sizeof(struct branch));
+       if (len)
+               branches[empty]->name = xstrndup(name, len);
+       else
+               branches[empty]->name = xstrdup(name);
+       refname = malloc(strlen(name) + strlen("refs/heads/") + 1);
+       strcpy(refname, "refs/heads/");
+       strcpy(refname + strlen("refs/heads/"),
+              branches[empty]->name);
+       branches[empty]->refname = refname;
+       return branches[empty];
+ }
  static void read_remotes_file(struct remote *remote)
  {
        FILE *f = fopen(git_path("remotes/%s", remote->name), "r");
  
                switch (value_list) {
                case 0:
-                       add_uri(remote, xstrdup(s));
+                       add_url(remote, xstrdup(s));
                        break;
                case 1:
                        add_push_refspec(remote, xstrdup(s));
  static void read_branches_file(struct remote *remote)
  {
        const char *slash = strchr(remote->name, '/');
+       char *frag;
+       char *branch;
        int n = slash ? slash - remote->name : 1000;
        FILE *f = fopen(git_path("branches/%.*s", n, remote->name), "r");
        char *s, *p;
        strcpy(p, s);
        if (slash)
                strcat(p, slash);
-       add_uri(remote, p);
+       frag = strchr(p, '#');
+       if (frag) {
+               *(frag++) = '\0';
+               branch = xmalloc(strlen(frag) + 12);
+               strcpy(branch, "refs/heads/");
+               strcat(branch, frag);
+       } else {
+               branch = "refs/heads/master";
+       }
+       add_url(remote, p);
+       add_fetch_refspec(remote, branch);
+       remote->fetch_tags = 1; /* always auto-follow */
  }
  
- static char *default_remote_name = NULL;
- static const char *current_branch = NULL;
- static int current_branch_len = 0;
  static int handle_config(const char *key, const char *value)
  {
        const char *name;
        const char *subkey;
        struct remote *remote;
-       if (!prefixcmp(key, "branch.") && current_branch &&
-           !strncmp(key + 7, current_branch, current_branch_len) &&
-           !strcmp(key + 7 + current_branch_len, ".remote")) {
-               free(default_remote_name);
-               default_remote_name = xstrdup(value);
+       struct branch *branch;
+       if (!prefixcmp(key, "branch.")) {
+               name = key + 7;
+               subkey = strrchr(name, '.');
+               branch = make_branch(name, subkey - name);
+               if (!subkey)
+                       return 0;
+               if (!value)
+                       return 0;
+               if (!strcmp(subkey, ".remote")) {
+                       branch->remote_name = xstrdup(value);
+                       if (branch == current_branch)
+                               default_remote_name = branch->remote_name;
+               } else if (!strcmp(subkey, ".merge"))
+                       add_merge(branch, xstrdup(value));
+               return 0;
        }
        if (prefixcmp(key,  "remote."))
                return 0;
                return 0; /* ignore unknown booleans */
        }
        if (!strcmp(subkey, ".url")) {
-               add_uri(remote, xstrdup(value));
+               add_url(remote, xstrdup(value));
        } else if (!strcmp(subkey, ".push")) {
                add_push_refspec(remote, xstrdup(value));
        } else if (!strcmp(subkey, ".fetch")) {
                        remote->receivepack = xstrdup(value);
                else
                        error("more than one receivepack given, using the first");
+       } else if (!strcmp(subkey, ".uploadpack")) {
+               if (!remote->uploadpack)
+                       remote->uploadpack = xstrdup(value);
+               else
+                       error("more than one uploadpack given, using the first");
+       } else if (!strcmp(subkey, ".tagopt")) {
+               if (!strcmp(value, "--no-tags"))
+                       remote->fetch_tags = -1;
        }
        return 0;
  }
@@@ -212,13 -294,13 +294,13 @@@ static void read_config(void
        head_ref = resolve_ref("HEAD", sha1, 0, &flag);
        if (head_ref && (flag & REF_ISSYMREF) &&
            !prefixcmp(head_ref, "refs/heads/")) {
-               current_branch = head_ref + strlen("refs/heads/");
-               current_branch_len = strlen(current_branch);
+               current_branch =
+                       make_branch(head_ref + strlen("refs/heads/"), 0);
        }
        git_config(handle_config);
  }
  
- static struct refspec *parse_ref_spec(int nr_refspec, const char **refspec)
+ struct refspec *parse_ref_spec(int nr_refspec, const char **refspec)
  {
        int i;
        struct refspec *rs = xcalloc(sizeof(*rs), nr_refspec);
@@@ -265,14 -347,14 +347,14 @@@ struct remote *remote_get(const char *n
                name = default_remote_name;
        ret = make_remote(name, 0);
        if (name[0] != '/') {
-               if (!ret->uri)
+               if (!ret->url)
                        read_remotes_file(ret);
-               if (!ret->uri)
+               if (!ret->url)
                        read_branches_file(ret);
        }
-       if (!ret->uri)
-               add_uri(ret, name);
-       if (!ret->uri)
+       if (!ret->url)
+               add_url(ret, name);
+       if (!ret->url)
                return NULL;
        ret->fetch = parse_ref_spec(ret->fetch_refspec_nr, ret->fetch_refspec);
        ret->push = parse_ref_spec(ret->push_refspec_nr, ret->push_refspec);
@@@ -298,16 -380,62 +380,62 @@@ int for_each_remote(each_remote_fn fn, 
        return result;
  }
  
- int remote_has_uri(struct remote *remote, const char *uri)
+ void ref_remove_duplicates(struct ref *ref_map)
+ {
+       struct ref **posn;
+       struct ref *next;
+       for (; ref_map; ref_map = ref_map->next) {
+               if (!ref_map->peer_ref)
+                       continue;
+               posn = &ref_map->next;
+               while (*posn) {
+                       if ((*posn)->peer_ref &&
+                           !strcmp((*posn)->peer_ref->name,
+                                   ref_map->peer_ref->name)) {
+                               if (strcmp((*posn)->name, ref_map->name))
+                                       die("%s tracks both %s and %s",
+                                           ref_map->peer_ref->name,
+                                           (*posn)->name, ref_map->name);
+                               next = (*posn)->next;
+                               free((*posn)->peer_ref);
+                               free(*posn);
+                               *posn = next;
+                       } else {
+                               posn = &(*posn)->next;
+                       }
+               }
+       }
+ }
+ int remote_has_url(struct remote *remote, const char *url)
  {
        int i;
-       for (i = 0; i < remote->uri_nr; i++) {
-               if (!strcmp(remote->uri[i], uri))
+       for (i = 0; i < remote->url_nr; i++) {
+               if (!strcmp(remote->url[i], url))
                        return 1;
        }
        return 0;
  }
  
+ /*
+  * Returns true if, under the matching rules for fetching, name is the
+  * same as the given full name.
+  */
+ static int ref_matches_abbrev(const char *name, const char *full)
+ {
+       if (!prefixcmp(name, "refs/") || !strcmp(name, "HEAD"))
+               return !strcmp(name, full);
+       if (prefixcmp(full, "refs/"))
+               return 0;
+       if (!prefixcmp(name, "heads/") ||
+           !prefixcmp(name, "tags/") ||
+           !prefixcmp(name, "remotes/"))
+               return !strcmp(name, full + 5);
+       if (prefixcmp(full + 5, "heads/"))
+               return 0;
+       return !strcmp(full + 11, name);
+ }
  int remote_find_tracking(struct remote *remote, struct refspec *refspec)
  {
        int find_src = refspec->src == NULL;
        int i;
  
        if (find_src) {
-               if (refspec->dst == NULL)
+               if (!refspec->dst)
                        return error("find_tracking: need either src or dst");
                needle = refspec->dst;
                result = &refspec->src;
@@@ -357,6 -485,14 +485,14 @@@ struct ref *alloc_ref(unsigned namelen
        return ret;
  }
  
+ static struct ref *copy_ref(struct ref *ref)
+ {
+       struct ref *ret = xmalloc(sizeof(struct ref) + strlen(ref->name) + 1);
+       memcpy(ret, ref, sizeof(struct ref) + strlen(ref->name) + 1);
+       ret->next = NULL;
+       return ret;
+ }
  void free_refs(struct ref *ref)
  {
        struct ref *next;
@@@ -489,23 -625,23 +625,23 @@@ static int match_explicit(struct ref *s
                 * way to delete 'other' ref at the remote end.
                 */
                matched_src = try_explicit_object_name(rs->src);
-               if (matched_src)
-                       break;
-               error("src refspec %s does not match any.",
-                     rs->src);
+               if (!matched_src)
+                       error("src refspec %s does not match any.", rs->src);
                break;
        default:
                matched_src = NULL;
-               error("src refspec %s matches more than one.",
-                     rs->src);
+               error("src refspec %s matches more than one.", rs->src);
                break;
        }
  
        if (!matched_src)
                errs = 1;
  
-       if (dst_value == NULL)
+       if (!dst_value) {
+               if (!matched_src)
+                       return errs;
                dst_value = matched_src->name;
+       }
  
        switch (count_refspec_match(dst_value, dst, &matched_dst)) {
        case 1:
                      dst_value);
                break;
        }
-       if (errs || matched_dst == NULL)
+       if (errs || !matched_dst)
                return 1;
        if (matched_dst->peer_ref) {
                errs = 1;
@@@ -626,10 -762,155 +762,157 @@@ int match_refs(struct ref *src, struct 
                        hashcpy(dst_peer->new_sha1, src->new_sha1);
                }
                dst_peer->peer_ref = src;
 +              if (pat)
 +                      dst_peer->force = pat->force;
        free_name:
                free(dst_name);
        }
        return 0;
  }
+ struct branch *branch_get(const char *name)
+ {
+       struct branch *ret;
+       read_config();
+       if (!name || !*name || !strcmp(name, "HEAD"))
+               ret = current_branch;
+       else
+               ret = make_branch(name, 0);
+       if (ret && ret->remote_name) {
+               ret->remote = remote_get(ret->remote_name);
+               if (ret->merge_nr) {
+                       int i;
+                       ret->merge = xcalloc(sizeof(*ret->merge),
+                                            ret->merge_nr);
+                       for (i = 0; i < ret->merge_nr; i++) {
+                               ret->merge[i] = xcalloc(1, sizeof(**ret->merge));
+                               ret->merge[i]->src = xstrdup(ret->merge_name[i]);
+                               remote_find_tracking(ret->remote,
+                                                    ret->merge[i]);
+                       }
+               }
+       }
+       return ret;
+ }
+ int branch_has_merge_config(struct branch *branch)
+ {
+       return branch && !!branch->merge;
+ }
+ int branch_merge_matches(struct branch *branch,
+                                int i,
+                                const char *refname)
+ {
+       if (!branch || i < 0 || i >= branch->merge_nr)
+               return 0;
+       return ref_matches_abbrev(branch->merge[i]->src, refname);
+ }
+ static struct ref *get_expanded_map(struct ref *remote_refs,
+                                   const struct refspec *refspec)
+ {
+       struct ref *ref;
+       struct ref *ret = NULL;
+       struct ref **tail = &ret;
+       int remote_prefix_len = strlen(refspec->src);
+       int local_prefix_len = strlen(refspec->dst);
+       for (ref = remote_refs; ref; ref = ref->next) {
+               if (strchr(ref->name, '^'))
+                       continue; /* a dereference item */
+               if (!prefixcmp(ref->name, refspec->src)) {
+                       char *match;
+                       struct ref *cpy = copy_ref(ref);
+                       match = ref->name + remote_prefix_len;
+                       cpy->peer_ref = alloc_ref(local_prefix_len +
+                                                 strlen(match) + 1);
+                       sprintf(cpy->peer_ref->name, "%s%s",
+                               refspec->dst, match);
+                       if (refspec->force)
+                               cpy->peer_ref->force = 1;
+                       *tail = cpy;
+                       tail = &cpy->next;
+               }
+       }
+       return ret;
+ }
+ static struct ref *find_ref_by_name_abbrev(struct ref *refs, const char *name)
+ {
+       struct ref *ref;
+       for (ref = refs; ref; ref = ref->next) {
+               if (ref_matches_abbrev(name, ref->name))
+                       return ref;
+       }
+       return NULL;
+ }
+ struct ref *get_remote_ref(struct ref *remote_refs, const char *name)
+ {
+       struct ref *ref = find_ref_by_name_abbrev(remote_refs, name);
+       if (!ref)
+               die("Couldn't find remote ref %s\n", name);
+       return copy_ref(ref);
+ }
+ static struct ref *get_local_ref(const char *name)
+ {
+       struct ref *ret;
+       if (!name)
+               return NULL;
+       if (!prefixcmp(name, "refs/")) {
+               ret = alloc_ref(strlen(name) + 1);
+               strcpy(ret->name, name);
+               return ret;
+       }
+       if (!prefixcmp(name, "heads/") ||
+           !prefixcmp(name, "tags/") ||
+           !prefixcmp(name, "remotes/")) {
+               ret = alloc_ref(strlen(name) + 6);
+               sprintf(ret->name, "refs/%s", name);
+               return ret;
+       }
+       ret = alloc_ref(strlen(name) + 12);
+       sprintf(ret->name, "refs/heads/%s", name);
+       return ret;
+ }
+ int get_fetch_map(struct ref *remote_refs,
+                 const struct refspec *refspec,
+                 struct ref ***tail)
+ {
+       struct ref *ref_map, *rm;
+       if (refspec->pattern) {
+               ref_map = get_expanded_map(remote_refs, refspec);
+       } else {
+               ref_map = get_remote_ref(remote_refs,
+                                        refspec->src[0] ?
+                                        refspec->src : "HEAD");
+               ref_map->peer_ref = get_local_ref(refspec->dst);
+               if (ref_map->peer_ref && refspec->force)
+                       ref_map->peer_ref->force = 1;
+       }
+       for (rm = ref_map; rm; rm = rm->next) {
+               if (rm->peer_ref && check_ref_format(rm->peer_ref->name + 5))
+                       die("* refusing to create funny ref '%s' locally",
+                           rm->peer_ref->name);
+       }
+       if (ref_map)
+               tail_link_ref(ref_map, tail);
+       return 0;
+ }
diff --combined send-pack.c
index c1807f07946ca204bc1e8307eed04150e62c551d,7b776243e666014521c62276923b3b17fc739fe2..e9b9a39f411b6cfff1c0a4bc3f7e31274c8d2782
@@@ -205,8 -205,7 +205,8 @@@ static int send_pack(int in, int out, s
                return -1;
  
        if (!remote_refs) {
 -              fprintf(stderr, "No refs in common and none specified; doing nothing.\n");
 +              fprintf(stderr, "No refs in common and none specified; doing nothing.\n"
 +                      "Perhaps you should specify a branch such as 'master'.\n");
                return 0;
        }
  
@@@ -428,7 -427,7 +428,7 @@@ int main(int argc, char **argv
  
        if (remote_name) {
                remote = remote_get(remote_name);
-               if (!remote_has_uri(remote, dest)) {
+               if (!remote_has_url(remote, dest)) {
                        die("Destination %s is not a uri for %s",
                            dest, remote_name);
                }