Merge branch 'sr/vcs-helper'
authorJunio C Hamano <gitster@pobox.com>
Sat, 26 Dec 2009 22:03:16 +0000 (14:03 -0800)
committerJunio C Hamano <gitster@pobox.com>
Sat, 26 Dec 2009 22:03:16 +0000 (14:03 -0800)
* sr/vcs-helper:
tests: handle NO_PYTHON setting
builtin-push: don't access freed transport->url
Add Python support library for remote helpers
Basic build infrastructure for Python scripts
Allow helpers to report in "list" command that the ref is unchanged
Fix various memory leaks in transport-helper.c
Allow helper to map private ref names into normal names
Add support for "import" helper command
Allow specifying the remote helper in the url
Add a config option for remotes to specify a foreign vcs
Allow fetch to modify refs
Use a function to determine whether a remote is valid
Allow programs to not depend on remotes having urls
Fix memory leak in helper method for disconnect

Conflicts:
Documentation/git-remote-helpers.txt
Makefile
builtin-ls-remote.c
builtin-push.c
transport-helper.c

14 files changed:
1  2 
Documentation/config.txt
Documentation/git-remote-helpers.txt
Makefile
builtin-clone.c
builtin-fetch.c
builtin-ls-remote.c
builtin-push.c
configure.ac
remote.c
remote.h
t/test-lib.sh
transport-helper.c
transport.c
transport.h
diff --combined Documentation/config.txt
index a1e36d7e423e01de796ac5f866536280618199d1,0d9d369ca7637c0c2b9d4130a470b70b35143dae..76173dbc5deaf1fdbff316b42e0a42ce61f4f32d
@@@ -126,20 -126,12 +126,20 @@@ advice.*:
                Directions on how to stage/unstage/add shown in the
                output of linkgit:git-status[1] and the template shown
                when writing commit messages. Default: true.
 +      commitBeforeMerge::
 +              Advice shown when linkgit:git-merge[1] refuses to
 +              merge to avoid overwritting local changes.
 +              Default: true.
  --
  
  core.fileMode::
        If false, the executable bit differences between the index and
        the working copy are ignored; useful on broken filesystems like FAT.
 -      See linkgit:git-update-index[1]. True by default.
 +      See linkgit:git-update-index[1].
 ++
 +The default is true, except linkgit:git-clone[1] or linkgit:git-init[1]
 +will probe and set core.fileMode false if appropriate when the
 +repository is created.
  
  core.ignoreCygwinFSTricks::
        This option is only used by Cygwin implementation of Git. If false,
        is true, in which case ignoreCygwinFSTricks is ignored as Cygwin's
        POSIX emulation is required to support core.filemode.
  
 +core.ignorecase::
 +      If true, this option enables various workarounds to enable
 +      git to work better on filesystems that are not case sensitive,
 +      like FAT. For example, if a directory listing finds
 +      "makefile" when git expects "Makefile", git will assume
 +      it is really the same file, and continue to remember it as
 +      "Makefile".
 ++
 +The default is false, except linkgit:git-clone[1] or linkgit:git-init[1]
 +will probe and set core.ignorecase true if appropriate when the repository
 +is created.
 +
  core.trustctime::
        If false, the ctime differences between the index and the
        working copy are ignored; useful when the inode change time
@@@ -189,10 -169,9 +189,10 @@@ core.autocrlf:
        writing to the filesystem.  The variable can be set to
        'input', in which case the conversion happens only while
        reading from the filesystem but files are written out with
 -      `LF` at the end of lines.  Currently, which paths to consider
 -      "text" (i.e. be subjected to the autocrlf mechanism) is
 -      decided purely based on the contents.
 +      `LF` at the end of lines.  A file is considered
 +      "text" (i.e. be subjected to the autocrlf mechanism) based on
 +      the file's `crlf` attribute, or if `crlf` is unspecified,
 +      based on the file's contents.  See linkgit:gitattributes[5].
  
  core.safecrlf::
        If true, makes git check if converting `CRLF` as controlled by
@@@ -244,11 -223,7 +244,11 @@@ core.symlinks:
        contain the link text. linkgit:git-update-index[1] and
        linkgit:git-add[1] will not change the recorded type to regular
        file. Useful on filesystems like FAT that do not support
 -      symbolic links. True by default.
 +      symbolic links.
 ++
 +The default is true, except linkgit:git-clone[1] or linkgit:git-init[1]
 +will probe and set core.symlinks false if appropriate when the repository
 +is created.
  
  core.gitProxy::
        A "proxy command" to execute (as 'command host port') instead
@@@ -405,15 -380,16 +405,15 @@@ Common unit suffixes of 'k', 'm', or 'g
  core.excludesfile::
        In addition to '.gitignore' (per-directory) and
        '.git/info/exclude', git looks into this file for patterns
 -      of files which are not meant to be tracked.  See
 -      linkgit:gitignore[5].
 +      of files which are not meant to be tracked.  "{tilde}/" is expanded
 +      to the value of `$HOME` and "{tilde}user/" to the specified user's
 +      home directory.  See linkgit:gitignore[5].
  
  core.editor::
        Commands such as `commit` and `tag` that lets you edit
        messages by launching an editor uses the value of this
        variable when it is set, and the environment variable
 -      `GIT_EDITOR` is not set.  The order of preference is
 -      `GIT_EDITOR` environment, `core.editor`, `VISUAL` and
 -      `EDITOR` environment variables and then finally `vi`.
 +      `GIT_EDITOR` is not set.  See linkgit:git-var[1].
  
  core.pager::
        The command that git will use to paginate output.  Can
@@@ -482,19 -458,6 +482,19 @@@ On some file system/operating system co
  Set this config setting to 'rename' there; However, This will remove the
  check that makes sure that existing object files will not get overwritten.
  
 +core.notesRef::
 +      When showing commit messages, also show notes which are stored in
 +      the given ref.  This ref is expected to contain files named
 +      after the full SHA-1 of the commit they annotate.
 ++
 +If such a file exists in the given ref, the referenced blob is read, and
 +appended to the commit message, separated by a "Notes:" line.  If the
 +given ref itself does not exist, it is not an error, but means that no
 +notes should be printed.
 ++
 +This setting defaults to "refs/notes/commits", and can be overridden by
 +the `GIT_NOTES_REF` environment variable.
 +
  add.ignore-errors::
        Tells 'git-add' to continue adding files when some files cannot be
        added due to indexing errors. Equivalent to the '--ignore-errors'
@@@ -635,10 -598,10 +635,10 @@@ color.diff.<slot>:
        Use customized color for diff colorization.  `<slot>` specifies
        which part of the patch to use the specified color, and is one
        of `plain` (context text), `meta` (metainformation), `frag`
 -      (hunk header), `old` (removed lines), `new` (added lines),
 -      `commit` (commit headers), or `whitespace` (highlighting
 -      whitespace errors). The values of these variables may be specified as
 -      in color.branch.<slot>.
 +      (hunk header), 'func' (function in hunk header), `old` (removed lines),
 +      `new` (added lines), `commit` (commit headers), or `whitespace`
 +      (highlighting whitespace errors). The values of these variables may be
 +      specified as in color.branch.<slot>.
  
  color.grep::
        When set to `always`, always highlight matches.  When `false` (or
@@@ -707,8 -670,6 +707,8 @@@ color.ui:
  
  commit.template::
        Specify a file to use as the template for new commit messages.
 +      "{tilde}/" is expanded to the value of `$HOME` and "{tilde}user/" to the
 +      specified user's home directory.
  
  diff.autorefreshindex::
        When using 'git-diff' to compare with work tree
@@@ -1132,14 -1093,6 +1132,14 @@@ http.maxRequests:
        How many HTTP requests to launch in parallel. Can be overridden
        by the 'GIT_HTTP_MAX_REQUESTS' environment variable. Default is 5.
  
 +http.postBuffer::
 +      Maximum size in bytes of the buffer used by smart HTTP
 +      transports when POSTing data to the remote system.
 +      For requests larger than this buffer size, HTTP/1.1 and
 +      Transfer-Encoding: chunked is used to avoid creating a
 +      massive pack file locally.  Default is 1 MiB, which is
 +      sufficient for most requests.
 +
  http.lowSpeedLimit, http.lowSpeedTime::
        If the HTTP transfer speed is less than 'http.lowSpeedLimit'
        for longer than 'http.lowSpeedTime' seconds, the transfer is aborted.
@@@ -1407,7 -1360,7 +1407,7 @@@ receive.denyCurrentBranch:
  
  receive.denyNonFastForwards::
        If set to true, git-receive-pack will deny a ref update which is
 -      not a fast forward. Use this to prevent such an update via a push,
 +      not a fast-forward. Use this to prevent such an update via a push,
        even if that push is forced. This configuration variable is
        set when initializing a shared repository.
  
@@@ -1441,13 -1394,7 +1441,13 @@@ remote.<name>.mirror:
  
  remote.<name>.skipDefaultUpdate::
        If true, this remote will be skipped by default when updating
 -      using the update subcommand of linkgit:git-remote[1].
 +      using linkgit:git-fetch[1] or the `update` subcommand of
 +      linkgit:git-remote[1].
 +
 +remote.<name>.skipFetchAll::
 +      If true, this remote will be skipped by default when updating
 +      using linkgit:git-fetch[1] or the `update` subcommand of
 +      linkgit:git-remote[1].
  
  remote.<name>.receivepack::
        The default program to execute on the remote side when pushing.  See
@@@ -1461,6 -1408,10 +1461,10 @@@ remote.<name>.tagopt:
        Setting this value to \--no-tags disables automatic tag following when
        fetching from remote <name>
  
+ remote.<name>.vcs::
+       Setting this to a value <vcs> will cause git to interact with
+       the remote with the git-remote-<vcs> helper.
  remotes.<group>::
        The list of remotes which are fetched by "git remote update
        <group>".  See linkgit:git-remote[1].
index 8beb42dbb9952059650d51d2e7a5ca2efe9591fb,f4b6a5aea3bb3b1076877cffa8beb17936114468..5cfdc0cfc553dc01f6f1c7cb185e79449ee0b0a8
@@@ -34,51 -34,26 +34,62 @@@ Commands are given by the caller on th
        value of the ref. A space-separated list of attributes follows
        the name; unrecognized attributes are ignored. After the
        complete list, outputs a blank line.
 ++
 +If 'push' is supported this may be called as 'list for-push'
 +to obtain the current refs prior to sending one or more 'push'
 +commands to the helper.
 +
 +'option' <name> <value>::
 +      Set the transport helper option <name> to <value>.  Outputs a
 +      single line containing one of 'ok' (option successfully set),
 +      'unsupported' (option not recognized) or 'error <msg>'
 +      (option <name> is supported but <value> is not correct
 +      for it).  Options should be set before other commands,
 +      and may how those commands behave.
 ++
 +Supported if the helper has the "option" capability.
  
  'fetch' <sha1> <name>::
 -      Fetches the given object, writing the necessary objects to the
 -      database. Outputs a blank line when the fetch is
 -      complete. Only objects which were reported in the ref list
 -      with a sha1 may be fetched this way.
 +      Fetches the given object, writing the necessary objects
 +      to the database.  Fetch commands are sent in a batch, one
 +      per line, and the batch is terminated with a blank line.
 +      Outputs a single blank line when all fetch commands in the
 +      same batch are complete. Only objects which were reported
 +      in the ref list with a sha1 may be fetched this way.
 ++
 +Optionally may output a 'lock <file>' line indicating a file under
 +GIT_DIR/objects/pack which is keeping a pack until refs can be
 +suitably updated.
  +
  Supported if the helper has the "fetch" capability.
  
 +'push' +<src>:<dst>::
 +      Pushes the given <src> commit or branch locally to the
 +      remote branch described by <dst>.  A batch sequence of
 +      one or more push commands is terminated with a blank line.
 ++
 +Zero or more protocol options may be entered after the last 'push'
 +command, before the batch's terminating blank line.
 ++
 +When the push is complete, outputs one or more 'ok <dst>' or
 +'error <dst> <why>?' lines to indicate success or failure of
 +each pushed ref.  The status report output is terminated by
 +a blank line.  The option field <why> may be quoted in a C
 +style string if it contains an LF.
 ++
 +Supported if the helper has the "push" capability.
 +
+ 'import' <name>::
+       Produces a fast-import stream which imports the current value
+       of the named ref. It may additionally import other refs as
+       needed to construct the history efficiently. The script writes
+       to a helper-specific private namespace. The value of the named
+       ref should be written to a location in this namespace derived
+       by applying the refspecs from the "refspec" capability to the
+       name of the ref.
+ +
+ Supported if the helper has the "import" capability.
  If a fatal error occurs, the program writes the error message to
  stderr and exits. The caller should expect that a suitable error
  message has been printed if the child closes the connection without
@@@ -93,50 -68,26 +104,67 @@@ CAPABILITIE
  'fetch'::
        This helper supports the 'fetch' command.
  
 +'option'::
 +      This helper supports the option command.
 +
 +'push'::
 +      This helper supports the 'push' command.
 +
+ 'import'::
+       This helper supports the 'import' command.
+ 'refspec' 'spec'::
+       When using the import command, expect the source ref to have
+       been written to the destination ref. The earliest applicable
+       refspec takes precedence. For example
+       "refs/heads/*:refs/svn/origin/branches/*" means that, after an
+       "import refs/heads/name", the script has written to
+       refs/svn/origin/branches/name. If this capability is used at
+       all, it must cover all refs reported by the list command; if
+       it is not used, it is effectively "*:*"
  REF LIST ATTRIBUTES
  -------------------
  
 +'for-push'::
 +      The caller wants to use the ref list to prepare push
 +      commands.  A helper might chose to acquire the ref list by
 +      opening a different type of connection to the destination.
 +
+ 'unchanged'::
+       This ref is unchanged since the last import or fetch, although
+       the helper cannot necessarily determine what value that produced.
 +OPTIONS
 +-------
 +'option verbosity' <N>::
 +      Change the level of messages displayed by the helper.
 +      When N is 0 the end-user has asked the process to be
 +      quiet, and the helper should produce only error output.
 +      N of 1 is the default level of verbosity, higher values
 +      of N correspond to the number of -v flags passed on the
 +      command line.
 +
 +'option progress' \{'true'|'false'\}::
 +      Enable (or disable) progress messages displayed by the
 +      transport helper during a command.
 +
 +'option depth' <depth>::
 +      Deepen the history of a shallow repository.
 +
 +'option followtags' \{'true'|'false'\}::
 +      If enabled the helper should automatically fetch annotated
 +      tag objects if the object the tag points at was transferred
 +      during the fetch command.  If the tag is not fetched by
 +      the helper a second fetch command will usually be sent to
 +      ask for the tag specifically.  Some helpers may be able to
 +      use this option to avoid a second network connection.
 +
 +'option dry-run' \{'true'|'false'\}:
 +      If true, pretend the operation completed successfully,
 +      but don't actually change any repository data.  For most
 +      helpers this only applies to the 'push', if supported.
 +
  Documentation
  -------------
  Documentation by Daniel Barkalow.
diff --combined Makefile
index 4a1e5bcc4def58a5d8f05a368fea69cde8ddb74e,b43725210bf25bdbe83175a3a9d238e8cca88364..fd4919c0ad86d13617b73eddc31d7447f2f74485
+++ b/Makefile
@@@ -159,15 -159,13 +159,17 @@@ all:
  # Define ASCIIDOC_NO_ROFF if your DocBook XSL escapes raw roff directives
  # (versions 1.72 and later and 1.68.1 and earlier).
  #
 +# Define GNU_ROFF if your target system uses GNU groff.  This forces
 +# apostrophes to be ASCII so that cut&pasting examples to the shell
 +# will work.
 +#
  # Define NO_PERL_MAKEMAKER if you cannot use Makefiles generated by perl's
  # MakeMaker (e.g. using ActiveState under Cygwin).
  #
  # Define NO_PERL if you do not want Perl scripts or libraries at all.
  #
+ # Define NO_PYTHON if you do not want Python scripts or libraries at all.
+ #
  # Define NO_TCLTK if you do not want Tcl/Tk GUI.
  #
  # The TCL_PATH variable governs the location of the Tcl interpreter
  # memory allocators with the nedmalloc allocator written by Niall Douglas.
  #
  # Define NO_REGEX if you have no or inferior regex support in your C library.
 +#
 +# Define JSMIN to point to JavaScript minifier that functions as
 +# a filter to have gitweb.js minified.
 +#
 +# Define DEFAULT_PAGER to a sensible pager command (defaults to "less") if
 +# you want to use something different.  The value will be interpreted by the
 +# shell at runtime when it is used.
 +#
 +# Define DEFAULT_EDITOR to a sensible editor command (defaults to "vi") if you
 +# want to use something different.  The value will be interpreted by the shell
 +# if necessary when it is used.  Examples:
 +#
 +#   DEFAULT_EDITOR='~/bin/vi',
 +#   DEFAULT_EDITOR='$GIT_FALLBACK_EDITOR',
 +#   DEFAULT_EDITOR='"C:\Program Files\Vim\gvim.exe" --nofork'
  
  GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
        @$(SHELL_PATH) ./GIT-VERSION-GEN
@@@ -231,12 -214,6 +233,12 @@@ uname_R := $(shell sh -c 'uname -r 2>/d
  uname_P := $(shell sh -c 'uname -p 2>/dev/null || echo not')
  uname_V := $(shell sh -c 'uname -v 2>/dev/null || echo not')
  
 +ifdef MSVC
 +      # avoid the MingW and Cygwin configuration sections
 +      uname_S := Windows
 +      uname_O := Windows
 +endif
 +
  # CFLAGS and LDFLAGS are for the users to override from the command line.
  
  CFLAGS = -g -O2 -Wall
@@@ -277,9 -254,6 +279,9 @@@ lib = li
  # DESTDIR=
  pathsep = :
  
 +# JavaScript minifier invocation that can function as filter
 +JSMIN =
 +
  # default configuration for gitweb
  GITWEB_CONFIG = gitweb_config.perl
  GITWEB_CONFIG_SYSTEM = /etc/gitweb.conf
@@@ -295,11 -269,6 +297,11 @@@ GITWEB_HOMETEXT = indextext.htm
  GITWEB_CSS = gitweb.css
  GITWEB_LOGO = git-logo.png
  GITWEB_FAVICON = git-favicon.png
 +ifdef JSMIN
 +GITWEB_JS = gitweb.min.js
 +else
 +GITWEB_JS = gitweb.js
 +endif
  GITWEB_SITE_HEADER =
  GITWEB_SITE_FOOTER =
  
@@@ -341,6 -310,7 +343,7 @@@ LIB_H 
  LIB_OBJS =
  PROGRAMS =
  SCRIPT_PERL =
+ SCRIPT_PYTHON =
  SCRIPT_SH =
  TEST_PROGRAMS =
  
@@@ -354,7 -324,6 +357,7 @@@ SCRIPT_SH += git-merge-one-file.s
  SCRIPT_SH += git-merge-resolve.sh
  SCRIPT_SH += git-mergetool.sh
  SCRIPT_SH += git-mergetool--lib.sh
 +SCRIPT_SH += git-notes.sh
  SCRIPT_SH += git-parse-remote.sh
  SCRIPT_SH += git-pull.sh
  SCRIPT_SH += git-quiltimport.sh
@@@ -379,6 -348,7 +382,7 @@@ SCRIPT_PERL += git-svn.per
  
  SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
          $(patsubst %.perl,%,$(SCRIPT_PERL)) \
+         $(patsubst %.py,%,$(SCRIPT_PYTHON)) \
          git-instaweb
  
  # Empty...
@@@ -388,7 -358,6 +392,7 @@@ EXTRA_PROGRAMS 
  PROGRAMS += $(EXTRA_PROGRAMS)
  PROGRAMS += git-fast-import$X
  PROGRAMS += git-hash-object$X
 +PROGRAMS += git-imap-send$X
  PROGRAMS += git-index-pack$X
  PROGRAMS += git-merge-index$X
  PROGRAMS += git-merge-tree$X
@@@ -400,7 -369,6 +404,7 @@@ PROGRAMS += git-show-index$
  PROGRAMS += git-unpack-file$X
  PROGRAMS += git-upload-pack$X
  PROGRAMS += git-var$X
 +PROGRAMS += git-http-backend$X
  
  # List built-in command $C whose implementation cmd_$C() is not in
  # builtin-$C.o but is linked in as part of some other command.
@@@ -434,8 -402,12 +438,12 @@@ endi
  ifndef PERL_PATH
        PERL_PATH = /usr/bin/perl
  endif
+ ifndef PYTHON_PATH
+       PYTHON_PATH = /usr/bin/python
+ endif
  
  export PERL_PATH
+ export PYTHON_PATH
  
  LIB_FILE=libgit.a
  XDIFF_LIB=xdiff/lib.a
@@@ -448,7 -420,6 +456,7 @@@ LIB_H += builtin.
  LIB_H += cache.h
  LIB_H += cache-tree.h
  LIB_H += commit.h
 +LIB_H += compat/bswap.h
  LIB_H += compat/cygwin.h
  LIB_H += compat/mingw.h
  LIB_H += csum-file.h
@@@ -469,7 -440,6 +477,7 @@@ LIB_H += ll-merge.
  LIB_H += log-tree.h
  LIB_H += mailmap.h
  LIB_H += merge-recursive.h
 +LIB_H += notes.h
  LIB_H += object.h
  LIB_H += pack.h
  LIB_H += pack-refs.h
@@@ -490,7 -460,6 +498,7 @@@ LIB_H += sideband.
  LIB_H += sigchain.h
  LIB_H += strbuf.h
  LIB_H += string-list.h
 +LIB_H += submodule.h
  LIB_H += tag.h
  LIB_H += transport.h
  LIB_H += tree.h
@@@ -555,7 -524,6 +563,7 @@@ LIB_OBJS += match-trees.
  LIB_OBJS += merge-file.o
  LIB_OBJS += merge-recursive.o
  LIB_OBJS += name-hash.o
 +LIB_OBJS += notes.o
  LIB_OBJS += object.o
  LIB_OBJS += pack-check.o
  LIB_OBJS += pack-refs.o
@@@ -590,7 -558,6 +598,7 @@@ LIB_OBJS += sideband.
  LIB_OBJS += sigchain.o
  LIB_OBJS += strbuf.o
  LIB_OBJS += string-list.o
 +LIB_OBJS += submodule.o
  LIB_OBJS += symlinks.o
  LIB_OBJS += tag.o
  LIB_OBJS += trace.o
@@@ -635,6 -602,7 +643,6 @@@ BUILTIN_OBJS += builtin-diff-index.
  BUILTIN_OBJS += builtin-diff-tree.o
  BUILTIN_OBJS += builtin-diff.o
  BUILTIN_OBJS += builtin-fast-export.o
 -BUILTIN_OBJS += builtin-fetch--tool.o
  BUILTIN_OBJS += builtin-fetch-pack.o
  BUILTIN_OBJS += builtin-fetch.o
  BUILTIN_OBJS += builtin-fmt-merge-msg.o
@@@ -816,15 -784,12 +824,15 @@@ ifeq ($(uname_O),Cygwin
        NO_FAST_WORKING_DIRECTORY = UnfortunatelyYes
        NO_TRUSTABLE_FILEMODE = UnfortunatelyYes
        OLD_ICONV = UnfortunatelyYes
 +      NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease
        # There are conflicting reports about this.
        # On some boxes NO_MMAP is needed, and not so elsewhere.
        # Try commenting this out if you suspect MMAP is more efficient
        NO_MMAP = YesPlease
        NO_IPV6 = YesPlease
        X = .exe
 +      COMPAT_OBJS += compat/cygwin.o
 +      UNRELIABLE_FSTAT = UnfortunatelyYes
  endif
  ifeq ($(uname_S),FreeBSD)
        NEEDS_LIBICONV = YesPlease
@@@ -934,11 -899,15 +942,11 @@@ ifeq ($(uname_S),HP-UX
        NO_SYS_SELECT_H = YesPlease
        SNPRINTF_RETURNS_BOGUS = YesPlease
  endif
 -ifneq (,$(findstring CYGWIN,$(uname_S)))
 -      COMPAT_OBJS += compat/cygwin.o
 -      UNRELIABLE_FSTAT = UnfortunatelyYes
 -endif
 -ifdef MSVC
 +ifeq ($(uname_S),Windows)
        GIT_VERSION := $(GIT_VERSION).MSVC
        pathsep = ;
        NO_PREAD = YesPlease
 -      NO_OPENSSL = YesPlease
 +      NEEDS_CRYPTO_WITH_SSL = YesPlease
        NO_LIBGEN_H = YesPlease
        NO_SYMLINK_HEAD = YesPlease
        NO_IPV6 = YesPlease
        NO_REGEX = YesPlease
        NO_CURL = YesPlease
        NO_PTHREADS = YesPlease
 +      BLK_SHA1 = YesPlease
  
        CC = compat/vcbuild/scripts/clink.pl
        AR = compat/vcbuild/scripts/lib.pl
@@@ -987,13 -955,14 +995,13 @@@ els
        BASIC_CFLAGS += -Zi -MTd
  endif
        X = .exe
 -else
 +endif
  ifneq (,$(findstring MINGW,$(uname_S)))
        pathsep = ;
        NO_PREAD = YesPlease
 -      NO_OPENSSL = YesPlease
 +      NEEDS_CRYPTO_WITH_SSL = YesPlease
        NO_LIBGEN_H = YesPlease
        NO_SYMLINK_HEAD = YesPlease
 -      NO_IPV6 = YesPlease
        NO_SETENV = YesPlease
        NO_UNSETENV = YesPlease
        NO_STRCASESTR = YesPlease
        UNRELIABLE_FSTAT = UnfortunatelyYes
        OBJECT_CREATION_USES_RENAMES = UnfortunatelyNeedsTo
        NO_REGEX = YesPlease
 +      BLK_SHA1 = YesPlease
        COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/fnmatch
        COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\"
        COMPAT_OBJS += compat/mingw.o compat/fnmatch/fnmatch.o compat/winansi.o
@@@ -1036,6 -1004,7 +1044,6 @@@ els
        NO_PTHREADS = YesPlease
  endif
  endif
 -endif
  
  -include config.mak.autogen
  -include config.mak
@@@ -1114,6 -1083,7 +1122,6 @@@ EXTLIBS += -l
  
  ifndef NO_POSIX_ONLY_PROGRAMS
        PROGRAMS += git-daemon$X
 -      PROGRAMS += git-imap-send$X
  endif
  ifndef NO_OPENSSL
        OPENSSL_LIBSSL = -lssl
@@@ -1346,6 -1316,10 +1354,10 @@@ ifeq ($(PERL_PATH),
  NO_PERL=NoThanks
  endif
  
+ ifeq ($(PYTHON_PATH),)
+ NO_PYTHON=NoThanks
+ endif
  QUIET_SUBDIR0  = +$(MAKE) -C # space to separate -C and subdir
  QUIET_SUBDIR1  =
  
@@@ -1393,6 -1367,7 +1405,7 @@@ prefix_SQ = $(subst ','\'',$(prefix)
  
  SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
  PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
+ PYTHON_PATH_SQ = $(subst ','\'',$(PYTHON_PATH))
  TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH))
  
  LIBS = $(GITLIBS) $(EXTLIBS)
@@@ -1401,22 -1376,6 +1414,22 @@@ BASIC_CFLAGS += -DSHA1_HEADER='$(SHA1_H
        $(COMPAT_CFLAGS)
  LIB_OBJS += $(COMPAT_OBJS)
  
 +# Quote for C
 +
 +ifdef DEFAULT_EDITOR
 +DEFAULT_EDITOR_CQ = "$(subst ",\",$(subst \,\\,$(DEFAULT_EDITOR)))"
 +DEFAULT_EDITOR_CQ_SQ = $(subst ','\'',$(DEFAULT_EDITOR_CQ))
 +
 +BASIC_CFLAGS += -DDEFAULT_EDITOR='$(DEFAULT_EDITOR_CQ_SQ)'
 +endif
 +
 +ifdef DEFAULT_PAGER
 +DEFAULT_PAGER_CQ = "$(subst ",\",$(subst \,\\,$(DEFAULT_PAGER)))"
 +DEFAULT_PAGER_CQ_SQ = $(subst ','\'',$(DEFAULT_PAGER_CQ))
 +
 +BASIC_CFLAGS += -DDEFAULT_PAGER='$(DEFAULT_PAGER_CQ_SQ)'
 +endif
 +
  ALL_CFLAGS += $(BASIC_CFLAGS)
  ALL_LDFLAGS += $(BASIC_LDFLAGS)
  
@@@ -1439,6 -1398,9 +1452,9 @@@ ifndef NO_TCLT
  endif
  ifndef NO_PERL
        $(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' all
+ endif
+ ifndef NO_PYTHON
+       $(QUIET_SUBDIR0)git_remote_helpers $(QUIET_SUBDIR1) PYTHON_PATH='$(PYTHON_PATH_SQ)' prefix='$(prefix_SQ)' all
  endif
        $(QUIET_SUBDIR0)templates $(QUIET_SUBDIR1)
  
@@@ -1509,13 -1471,8 +1525,13 @@@ $(patsubst %.perl,%,$(SCRIPT_PERL)): % 
        chmod +x $@+ && \
        mv $@+ $@
  
 +ifdef JSMIN
 +OTHER_PROGRAMS += gitweb/gitweb.cgi   gitweb/gitweb.min.js
 +gitweb/gitweb.cgi: gitweb/gitweb.perl gitweb/gitweb.min.js
 +else
  OTHER_PROGRAMS += gitweb/gitweb.cgi
  gitweb/gitweb.cgi: gitweb/gitweb.perl
 +endif
        $(QUIET_GEN)$(RM) $@ $@+ && \
        sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \
            -e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \
            -e 's|++GITWEB_CSS++|$(GITWEB_CSS)|g' \
            -e 's|++GITWEB_LOGO++|$(GITWEB_LOGO)|g' \
            -e 's|++GITWEB_FAVICON++|$(GITWEB_FAVICON)|g' \
 +          -e 's|++GITWEB_JS++|$(GITWEB_JS)|g' \
            -e 's|++GITWEB_SITE_HEADER++|$(GITWEB_SITE_HEADER)|g' \
            -e 's|++GITWEB_SITE_FOOTER++|$(GITWEB_SITE_FOOTER)|g' \
            $< >$@+ && \
        chmod +x $@+ && \
        mv $@+ $@
  
 -git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css
 +git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css gitweb/gitweb.js
        $(QUIET_GEN)$(RM) $@ $@+ && \
        sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
            -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
            -e '/@@GITWEB_CGI@@/d' \
            -e '/@@GITWEB_CSS@@/r gitweb/gitweb.css' \
            -e '/@@GITWEB_CSS@@/d' \
 +          -e '/@@GITWEB_JS@@/r gitweb/gitweb.js' \
 +          -e '/@@GITWEB_JS@@/d' \
            -e 's|@@PERL@@|$(PERL_PATH_SQ)|g' \
            $@.sh > $@+ && \
        chmod +x $@+ && \
@@@ -1566,11 -1520,35 +1582,41 @@@ $(patsubst %.perl,%,$(SCRIPT_PERL)) git
        mv $@+ $@
  endif # NO_PERL
  
++
 +ifdef JSMIN
 +gitweb/gitweb.min.js: gitweb/gitweb.js
 +      $(QUIET_GEN)$(JSMIN) <$< >$@
 +endif # JSMIN
 +
+ ifndef NO_PYTHON
+ $(patsubst %.py,%,$(SCRIPT_PYTHON)): GIT-CFLAGS
+ $(patsubst %.py,%,$(SCRIPT_PYTHON)): % : %.py
+       $(QUIET_GEN)$(RM) $@ $@+ && \
+       INSTLIBDIR=`MAKEFLAGS= $(MAKE) -C git_remote_helpers -s \
+               --no-print-directory prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' \
+               instlibdir` && \
+       sed -e '1{' \
+           -e '        s|#!.*python|#!$(PYTHON_PATH_SQ)|' \
+           -e '}' \
+           -e 's|^import sys.*|&; \\\
+                  import os; \\\
+                  sys.path[0] = os.environ.has_key("GITPYTHONLIB") and \\\
+                                os.environ["GITPYTHONLIB"] or \\\
+                                "@@INSTLIBDIR@@"|' \
+           -e 's|@@INSTLIBDIR@@|'"$$INSTLIBDIR"'|g' \
+           $@.py >$@+ && \
+       chmod +x $@+ && \
+       mv $@+ $@
+ else # NO_PYTHON
+ $(patsubst %.py,%,$(SCRIPT_PYTHON)): % : unimplemented.sh
+       $(QUIET_GEN)$(RM) $@ $@+ && \
+       sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
+           -e 's|@@REASON@@|NO_PYTHON=$(NO_PYTHON)|g' \
+           unimplemented.sh >$@+ && \
+       chmod +x $@+ && \
+       mv $@+ $@
+ endif # NO_PYTHON
  configure: configure.ac
        $(QUIET_GEN)$(RM) $@ $<+ && \
        sed -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
@@@ -1693,10 -1671,10 +1739,11 @@@ GIT-CFLAGS: .FORCE-GIT-CFLAG
  # and the first level quoting from the shell that runs "echo".
  GIT-BUILD-OPTIONS: .FORCE-GIT-BUILD-OPTIONS
        @echo SHELL_PATH=\''$(subst ','\'',$(SHELL_PATH_SQ))'\' >$@
 +      @echo PERL_PATH=\''$(subst ','\'',$(PERL_PATH_SQ))'\' >>$@
        @echo TAR=\''$(subst ','\'',$(subst ','\'',$(TAR)))'\' >>$@
        @echo NO_CURL=\''$(subst ','\'',$(subst ','\'',$(NO_CURL)))'\' >>$@
        @echo NO_PERL=\''$(subst ','\'',$(subst ','\'',$(NO_PERL)))'\' >>$@
+       @echo NO_PYTHON=\''$(subst ','\'',$(subst ','\'',$(NO_PYTHON)))'\' >>$@
  
  ### Detect Tck/Tk interpreter path changes
  ifndef NO_TCLTK
@@@ -1796,6 -1774,9 +1843,9 @@@ install: al
  ifndef NO_PERL
        $(MAKE) -C perl prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install
  endif
+ ifndef NO_PYTHON
+       $(MAKE) -C git_remote_helpers prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install
+ endif
  ifndef NO_TCLTK
        $(MAKE) -C gitk-git install
        $(MAKE) -C git-gui gitexecdir='$(gitexec_instdir_SQ)' install
@@@ -1867,10 -1848,7 +1917,10 @@@ dist: git.spec git-archive$(X) configur
        gzip -f -9 $(GIT_TARNAME).tar
  
  rpm: dist
 -      $(RPMBUILD) -ta $(GIT_TARNAME).tar.gz
 +      $(RPMBUILD) \
 +              --define "_source_filedigest_algorithm md5" \
 +              --define "_binary_filedigest_algorithm md5" \
 +              -ta $(GIT_TARNAME).tar.gz
  
  htmldocs = git-htmldocs-$(GIT_VERSION)
  manpages = git-manpages-$(GIT_VERSION)
@@@ -1898,7 -1876,7 +1948,7 @@@ distclean: clea
        $(RM) configure
  
  clean:
 -      $(RM) *.o block-sha1/*.o arm/*.o ppc/*.o compat/*.o compat/*/*.o xdiff/*.o \
 +      $(RM) *.o block-sha1/*.o ppc/*.o compat/*.o compat/*/*.o xdiff/*.o \
                $(LIB_FILE) $(XDIFF_LIB)
        $(RM) $(ALL_PROGRAMS) $(BUILT_INS) git$X
        $(RM) $(TEST_PROGRAMS)
  ifndef NO_PERL
        $(RM) gitweb/gitweb.cgi
        $(MAKE) -C perl clean
+ endif
+ ifndef NO_PYTHON
+       $(MAKE) -C git_remote_helpers clean
  endif
        $(MAKE) -C templates/ clean
        $(MAKE) -C t/ clean
diff --combined builtin-clone.c
index caf3025031d83dc860b22c7798dd5f40b6b89ed2,32f22e100ed792fffe1aa3c59c3f841f2fee24ab..5df8b0f72c8bb5dfa7fa7df1edfec90405031f47
@@@ -51,9 -51,7 +51,9 @@@ static struct option builtin_clone_opti
        OPT_BOOLEAN('n', "no-checkout", &option_no_checkout,
                    "don't create a checkout"),
        OPT_BOOLEAN(0, "bare", &option_bare, "create a bare repository"),
 -      OPT_BOOLEAN(0, "naked", &option_bare, "create a bare repository"),
 +      { OPTION_BOOLEAN, 0, "naked", &option_bare, NULL,
 +              "create a bare repository",
 +              PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
        OPT_BOOLEAN(0, "mirror", &option_mirror,
                    "create a mirror repository (implies bare)"),
        OPT_BOOLEAN('l', "local", &option_local,
@@@ -63,7 -61,7 +63,7 @@@
        OPT_BOOLEAN('s', "shared", &option_shared,
                    "setup as shared repository"),
        OPT_BOOLEAN(0, "recursive", &option_recursive,
 -                  "setup as shared repository"),
 +                  "initialize submodules in the clone"),
        OPT_STRING(0, "template", &option_template, "path",
                   "path the template repository"),
        OPT_STRING(0, "reference", &option_reference, "repo",
@@@ -362,9 -360,10 +362,10 @@@ int cmd_clone(int argc, const char **ar
        const char *repo_name, *repo, *work_tree, *git_dir;
        char *path, *dir;
        int dest_exists;
-       const struct ref *refs, *remote_head, *mapped_refs;
+       const struct ref *refs, *remote_head;
        const struct ref *remote_head_points_at;
        const struct ref *our_head_points_at;
+       struct ref *mapped_refs;
        struct strbuf key = STRBUF_INIT, value = STRBUF_INIT;
        struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT;
        struct transport *transport = NULL;
        argc = parse_options(argc, argv, prefix, builtin_clone_options,
                             builtin_clone_usage, 0);
  
 +      if (argc > 2)
 +              usage_msg_opt("Too many arguments.",
 +                      builtin_clone_usage, builtin_clone_options);
 +
        if (argc == 0)
 -              die("You must specify a repository to clone.");
 +              usage_msg_opt("You must specify a repository to clone.",
 +                      builtin_clone_usage, builtin_clone_options);
  
        if (option_mirror)
                option_bare = 1;
diff --combined builtin-fetch.c
index 5b7db616dcf5cc3bb178b2c4dbb989322c6b2374,013a6ba1b9d4522863d22516a6134142bbcaaa68..8654fa7a2dbe2c1ca76a493a4322447de5219b99
@@@ -14,9 -14,6 +14,9 @@@
  
  static const char * const builtin_fetch_usage[] = {
        "git fetch [options] [<repository> <refspec>...]",
 +      "git fetch [options] <group>",
 +      "git fetch --multiple [options] [<repository> | <group>]...",
 +      "git fetch --all [options]",
        NULL
  };
  
@@@ -26,7 -23,7 +26,7 @@@ enum 
        TAGS_SET = 2
  };
  
 -static int append, force, keep, update_head_ok, verbosity;
 +static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity;
  static int tags = TAGS_DEFAULT;
  static const char *depth;
  static const char *upload_pack;
@@@ -35,24 -32,16 +35,24 @@@ static struct transport *transport
  
  static struct option builtin_fetch_options[] = {
        OPT__VERBOSITY(&verbosity),
 +      OPT_BOOLEAN(0, "all", &all,
 +                  "fetch from all remotes"),
        OPT_BOOLEAN('a', "append", &append,
                    "append to .git/FETCH_HEAD instead of overwriting"),
        OPT_STRING(0, "upload-pack", &upload_pack, "PATH",
                   "path to upload pack on remote end"),
        OPT_BOOLEAN('f', "force", &force,
                    "force overwrite of local branch"),
 +      OPT_BOOLEAN('m', "multiple", &multiple,
 +                  "fetch from multiple remotes"),
        OPT_SET_INT('t', "tags", &tags,
                    "fetch all tags and associated objects", TAGS_SET),
        OPT_SET_INT('n', NULL, &tags,
                    "do not fetch all tags (--no-tags)", TAGS_UNSET),
 +      OPT_BOOLEAN('p', "prune", &prune,
 +                  "prune tracking branches no longer on remote"),
 +      OPT_BOOLEAN(0, "dry-run", &dry_run,
 +                  "dry run"),
        OPT_BOOLEAN('k', "keep", &keep, "keep downloaded pack"),
        OPT_BOOLEAN('u', "update-head-ok", &update_head_ok,
                    "allow updating of HEAD ref"),
@@@ -189,8 -178,6 +189,8 @@@ static int s_update_ref(const char *act
        char *rla = getenv("GIT_REFLOG_ACTION");
        static struct ref_lock *lock;
  
 +      if (dry_run)
 +              return 0;
        if (!rla)
                rla = default_rla.buf;
        snprintf(msg, sizeof(msg), "%s: %s", rla, action);
@@@ -282,7 -269,7 +282,7 @@@ static int update_local_ref(struct ref 
                strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV));
                strcat(quickref, "..");
                strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV));
 -              r = s_update_ref("fast forward", ref, 1);
 +              r = s_update_ref("fast-forward", ref, 1);
                sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : ' ',
                        SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote,
                        pretty_ref, r ? "  (unable to update local ref)" : "");
                        r ? "unable to update local ref" : "forced update");
                return r;
        } else {
 -              sprintf(display, "! %-*s %-*s -> %s  (non fast forward)",
 +              sprintf(display, "! %-*s %-*s -> %s  (non-fast-forward)",
                        SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote,
                        pretty_ref);
                return 1;
@@@ -316,13 -303,16 +316,16 @@@ static int store_updated_refs(const cha
        char note[1024];
        const char *what, *kind;
        struct ref *rm;
 -      char *url, *filename = git_path("FETCH_HEAD");
 +      char *url, *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD");
  
        fp = fopen(filename, "a");
        if (!fp)
                return error("cannot open %s: %s\n", filename, strerror(errno));
  
-       url = transport_anonymize_url(raw_url);
+       if (raw_url)
+               url = transport_anonymize_url(raw_url);
+       else
+               url = xstrdup("foreign");
        for (rm = ref_map; rm; rm = rm->next) {
                struct ref *ref = NULL;
  
@@@ -498,34 -488,11 +501,34 @@@ static int fetch_refs(struct transport 
        return ret;
  }
  
 +static int prune_refs(struct transport *transport, struct ref *ref_map)
 +{
 +      int result = 0;
 +      struct ref *ref, *stale_refs = get_stale_heads(transport->remote, ref_map);
 +      const char *dangling_msg = dry_run
 +              ? "   (%s will become dangling)\n"
 +              : "   (%s has become dangling)\n";
 +
 +      for (ref = stale_refs; ref; ref = ref->next) {
 +              if (!dry_run)
 +                      result |= delete_ref(ref->name, NULL, 0);
 +              if (verbosity >= 0) {
 +                      fprintf(stderr, " x %-*s %-*s -> %s\n",
 +                              SUMMARY_WIDTH, "[deleted]",
 +                              REFCOL_WIDTH, "(none)", prettify_refname(ref->name));
 +                      warn_dangling_symref(stderr, dangling_msg, ref->name);
 +              }
 +      }
 +      free_refs(stale_refs);
 +      return result;
 +}
 +
  static int add_existing(const char *refname, const unsigned char *sha1,
                        int flag, void *cbdata)
  {
        struct string_list *list = (struct string_list *)cbdata;
 -      string_list_insert(refname, list);
 +      struct string_list_item *item = string_list_insert(refname, list);
 +      item->util = (void *)sha1;
        return 0;
  }
  
@@@ -651,14 -618,9 +654,14 @@@ static void check_not_current_branch(st
  static int do_fetch(struct transport *transport,
                    struct refspec *refs, int ref_count)
  {
 +      struct string_list existing_refs = { NULL, 0, 0, 0 };
 +      struct string_list_item *peer_item = NULL;
        struct ref *ref_map;
        struct ref *rm;
        int autotags = (transport->remote->fetch_tags == 1);
 +
 +      for_each_ref(add_existing, &existing_refs);
 +
        if (transport->remote->fetch_tags == 2 && tags != TAGS_UNSET)
                tags = TAGS_SET;
        if (transport->remote->fetch_tags == -1)
                die("Don't know how to fetch from %s", transport->url);
  
        /* if not appending, truncate FETCH_HEAD */
 -      if (!append) {
 +      if (!append && !dry_run) {
                char *filename = git_path("FETCH_HEAD");
                FILE *fp = fopen(filename, "w");
                if (!fp)
                check_not_current_branch(ref_map);
  
        for (rm = ref_map; rm; rm = rm->next) {
 -              if (rm->peer_ref)
 -                      read_ref(rm->peer_ref->name, rm->peer_ref->old_sha1);
 +              if (rm->peer_ref) {
 +                      peer_item = string_list_lookup(rm->peer_ref->name,
 +                                                     &existing_refs);
 +                      if (peer_item)
 +                              hashcpy(rm->peer_ref->old_sha1,
 +                                      peer_item->util);
 +              }
        }
  
        if (tags == TAGS_DEFAULT && autotags)
                free_refs(ref_map);
                return 1;
        }
 +      if (prune)
 +              prune_refs(transport, ref_map);
        free_refs(ref_map);
  
        /* if neither --no-tags nor --tags was specified, do automated tag
@@@ -728,100 -683,33 +731,100 @@@ static void set_option(const char *name
                        name, transport->url);
  }
  
 -int cmd_fetch(int argc, const char **argv, const char *prefix)
 +static int get_one_remote_for_fetch(struct remote *remote, void *priv)
 +{
 +      struct string_list *list = priv;
 +      if (!remote->skip_default_update)
 +              string_list_append(remote->name, list);
 +      return 0;
 +}
 +
 +struct remote_group_data {
 +      const char *name;
 +      struct string_list *list;
 +};
 +
 +static int get_remote_group(const char *key, const char *value, void *priv)
 +{
 +      struct remote_group_data *g = priv;
 +
 +      if (!prefixcmp(key, "remotes.") &&
 +                      !strcmp(key + 8, g->name)) {
 +              /* split list by white space */
 +              int space = strcspn(value, " \t\n");
 +              while (*value) {
 +                      if (space > 1) {
 +                              string_list_append(xstrndup(value, space),
 +                                                 g->list);
 +                      }
 +                      value += space + (value[space] != '\0');
 +                      space = strcspn(value, " \t\n");
 +              }
 +      }
 +
 +      return 0;
 +}
 +
 +static int add_remote_or_group(const char *name, struct string_list *list)
 +{
 +      int prev_nr = list->nr;
 +      struct remote_group_data g = { name, list };
 +
 +      git_config(get_remote_group, &g);
 +      if (list->nr == prev_nr) {
 +              struct remote *remote;
 +              if (!remote_is_configured(name))
 +                      return 0;
 +              remote = remote_get(name);
 +              string_list_append(remote->name, list);
 +      }
 +      return 1;
 +}
 +
 +static int fetch_multiple(struct string_list *list)
 +{
 +      int i, result = 0;
 +      const char *argv[] = { "fetch", NULL, NULL, NULL, NULL, NULL, NULL };
 +      int argc = 1;
 +
 +      if (dry_run)
 +              argv[argc++] = "--dry-run";
 +      if (prune)
 +              argv[argc++] = "--prune";
 +      if (verbosity >= 2)
 +              argv[argc++] = "-v";
 +      if (verbosity >= 1)
 +              argv[argc++] = "-v";
 +      else if (verbosity < 0)
 +              argv[argc++] = "-q";
 +
 +      for (i = 0; i < list->nr; i++) {
 +              const char *name = list->items[i].string;
 +              argv[argc] = name;
 +              if (verbosity >= 0)
 +                      printf("Fetching %s\n", name);
 +              if (run_command_v_opt(argv, RUN_GIT_CMD)) {
 +                      error("Could not fetch %s", name);
 +                      result = 1;
 +              }
 +      }
 +
 +      return result;
 +}
 +
 +static int fetch_one(struct remote *remote, int argc, const char **argv)
  {
 -      struct remote *remote;
        int i;
        static const char **refs = NULL;
        int ref_nr = 0;
        int exit_code;
  
 -      /* Record the command line for the reflog */
 -      strbuf_addstr(&default_rla, "fetch");
 -      for (i = 1; i < argc; i++)
 -              strbuf_addf(&default_rla, " %s", argv[i]);
 -
 -      argc = parse_options(argc, argv, prefix,
 -                           builtin_fetch_options, builtin_fetch_usage, 0);
 -
 -      if (argc == 0)
 -              remote = remote_get(NULL);
 -      else
 -              remote = remote_get(argv[0]);
 -
        if (!remote)
                die("Where do you want to fetch from today?");
  
-       transport = transport_get(remote, remote->url[0]);
+       transport = transport_get(remote, NULL);
        if (verbosity >= 2)
 -              transport->verbose = 1;
 +              transport->verbose = verbosity <= 3 ? verbosity : 3;
        if (verbosity < 0)
                transport->verbose = -1;
        if (upload_pack)
        if (depth)
                set_option(TRANS_OPT_DEPTH, depth);
  
 -      if (argc > 1) {
 +      if (argc > 0) {
                int j = 0;
                refs = xcalloc(argc + 1, sizeof(const char *));
 -              for (i = 1; i < argc; i++) {
 +              for (i = 0; i < argc; i++) {
                        if (!strcmp(argv[i], "tag")) {
                                char *ref;
                                i++;
        transport = NULL;
        return exit_code;
  }
 +
 +int cmd_fetch(int argc, const char **argv, const char *prefix)
 +{
 +      int i;
 +      struct string_list list = { NULL, 0, 0, 0 };
 +      struct remote *remote;
 +      int result = 0;
 +
 +      /* Record the command line for the reflog */
 +      strbuf_addstr(&default_rla, "fetch");
 +      for (i = 1; i < argc; i++)
 +              strbuf_addf(&default_rla, " %s", argv[i]);
 +
 +      argc = parse_options(argc, argv, prefix,
 +                           builtin_fetch_options, builtin_fetch_usage, 0);
 +
 +      if (all) {
 +              if (argc == 1)
 +                      die("fetch --all does not take a repository argument");
 +              else if (argc > 1)
 +                      die("fetch --all does not make sense with refspecs");
 +              (void) for_each_remote(get_one_remote_for_fetch, &list);
 +              result = fetch_multiple(&list);
 +      } else if (argc == 0) {
 +              /* No arguments -- use default remote */
 +              remote = remote_get(NULL);
 +              result = fetch_one(remote, argc, argv);
 +      } else if (multiple) {
 +              /* All arguments are assumed to be remotes or groups */
 +              for (i = 0; i < argc; i++)
 +                      if (!add_remote_or_group(argv[i], &list))
 +                              die("No such remote or remote group: %s", argv[i]);
 +              result = fetch_multiple(&list);
 +      } else {
 +              /* Single remote or group */
 +              (void) add_remote_or_group(argv[0], &list);
 +              if (list.nr > 1) {
 +                      /* More than one remote */
 +                      if (argc > 1)
 +                              die("Fetching a group and specifying refspecs does not make sense");
 +                      result = fetch_multiple(&list);
 +              } else {
 +                      /* Zero or one remotes */
 +                      remote = remote_get(argv[0]);
 +                      result = fetch_one(remote, argc-1, argv+1);
 +              }
 +      }
 +
 +      /* All names were strdup()ed or strndup()ed */
 +      list.strdup_strings = 1;
 +      string_list_clear(&list, 0);
 +
 +      return result;
 +}
diff --combined builtin-ls-remote.c
index b5bad0c184fc1ebc49759f211781ecd4031fd027,d625df2f4e6c44f1986b0a090d923e7fee78878e..70f5622d9d49aae4080b38ee4487cc6e403a9d2c
@@@ -86,10 -86,10 +86,10 @@@ int cmd_ls_remote(int argc, const char 
                        pattern[j - i] = p;
                }
        }
 -      remote = nongit ? NULL : remote_get(dest);
 -      if (remote && !remote->url_nr)
 +      remote = remote_get(dest);
 +      if (!remote->url_nr)
                die("remote %s has no configured URL", dest);
-       transport = transport_get(remote, remote->url[0]);
+       transport = transport_get(remote, NULL);
        if (uploadpack != NULL)
                transport_set_option(transport, TRANS_OPT_UPLOADPACK, uploadpack);
  
diff --combined builtin-push.c
index 356d7c1fd3a6fae9558e93eb4f240242a60da368,03be045baed585d4d3996b0d16ea96cf79e2a584..dcfb53f1884d2648993214b4378afbd1f83760b0
@@@ -66,6 -66,7 +66,6 @@@ static void setup_push_tracking(void
  
  static void setup_default_push_refspecs(void)
  {
 -      git_config(git_default_config, NULL);
        switch (push_default) {
        default:
        case PUSH_DEFAULT_MATCHING:
        }
  }
  
+ static int push_with_options(struct transport *transport, int flags)
+ {
+       int err;
+       int nonfastforward;
+       if (receivepack)
+               transport_set_option(transport,
+                                    TRANS_OPT_RECEIVEPACK, receivepack);
+       if (thin)
+               transport_set_option(transport, TRANS_OPT_THIN, "yes");
+       if (flags & TRANSPORT_PUSH_VERBOSE)
+               fprintf(stderr, "Pushing to %s\n", transport->url);
+       err = transport_push(transport, refspec_nr, refspec, flags,
+                            &nonfastforward);
+       if (err != 0)
+               error("failed to push some refs to '%s'", transport->url);
+       err |= transport_disconnect(transport);
+       if (!err)
+               return 0;
+       if (nonfastforward && advice_push_nonfastforward) {
+               printf("To prevent you from losing history, non-fast-forward updates were rejected\n"
+                      "Merge the remote changes before pushing again.  See the 'non-fast-forward'\n"
+                      "section of 'git push --help' for details.\n");
+       }
+       return 1;
+ }
  static int do_push(const char *repo, int flags)
  {
        int i, errs;
                url = remote->url;
                url_nr = remote->url_nr;
        }
-       for (i = 0; i < url_nr; i++) {
-               struct transport *transport =
-                       transport_get(remote, url[i]);
-               int err;
-               int nonfastforward;
-               if (receivepack)
-                       transport_set_option(transport,
-                                            TRANS_OPT_RECEIVEPACK, receivepack);
-               if (thin)
-                       transport_set_option(transport, TRANS_OPT_THIN, "yes");
-               if (flags & TRANSPORT_PUSH_VERBOSE)
-                       fprintf(stderr, "Pushing to %s\n", url[i]);
-               err = transport_push(transport, refspec_nr, refspec, flags,
-                                    &nonfastforward);
-               err |= transport_disconnect(transport);
-               if (!err)
-                       continue;
-               error("failed to push some refs to '%s'", url[i]);
-               if (nonfastforward && advice_push_nonfastforward) {
-                       printf("To prevent you from losing history, non-fast-forward updates were rejected\n"
-                              "Merge the remote changes before pushing again.  See the 'non-fast-forward'\n"
-                              "section of 'git push --help' for details.\n");
+       if (url_nr) {
+               for (i = 0; i < url_nr; i++) {
+                       struct transport *transport =
+                               transport_get(remote, url[i]);
+                       if (push_with_options(transport, flags))
+                               errs++;
                }
-               errs++;
+       } else {
+               struct transport *transport =
+                       transport_get(remote, NULL);
+               if (push_with_options(transport, flags))
+                       errs++;
        }
        return !!errs;
  }
@@@ -172,6 -190,7 +189,6 @@@ int cmd_push(int argc, const char **arg
        int tags = 0;
        int rc;
        const char *repo = NULL;        /* default repository */
 -
        struct option options[] = {
                OPT_BIT('q', "quiet", &flags, "be quiet", TRANSPORT_PUSH_QUIET),
                OPT_BIT('v', "verbose", &flags, "be verbose", TRANSPORT_PUSH_VERBOSE),
                OPT_BIT( 0 , "all", &flags, "push all refs", TRANSPORT_PUSH_ALL),
                OPT_BIT( 0 , "mirror", &flags, "mirror all refs",
                            (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE)),
 -              OPT_BOOLEAN( 0 , "tags", &tags, "push tags (can't be used with --all or --mirror"),
 +              OPT_BOOLEAN( 0 , "tags", &tags, "push tags (can't be used with --all or --mirror)"),
                OPT_BIT('n' , "dry-run", &flags, "dry run", TRANSPORT_PUSH_DRY_RUN),
                OPT_BIT( 0,  "porcelain", &flags, "machine-readable output", TRANSPORT_PUSH_PORCELAIN),
                OPT_BIT('f', "force", &flags, "force updates", TRANSPORT_PUSH_FORCE),
                OPT_END()
        };
  
 +      git_config(git_default_config, NULL);
        argc = parse_options(argc, argv, prefix, options, push_usage, 0);
  
        if (tags)
diff --combined configure.ac
index 4625b8672bf5b0dcc977737c2c5086c5c1aa54a6,84b6cf48a4f9829c32bb14c80b667193a173ae5a..78345ebb60240e6642f319467defe3e07d4aa263
@@@ -68,26 -68,6 +68,26 @@@ else 
        GIT_CONF_APPEND_LINE(${PACKAGE}DIR=$withval); \
  fi \
  ])# GIT_PARSE_WITH
 +#
 +# GIT_PARSE_WITH_SET_MAKE_VAR(WITHNAME, VAR, HELP_TEXT)
 +# ---------------------
 +# Set VAR to the value specied by --with-WITHNAME.
 +# No verification of arguments is performed, but warnings are issued
 +# if either 'yes' or 'no' is specified.
 +# HELP_TEXT is presented when --help is called.
 +# This is a direct way to allow setting variables in the Makefile.
 +AC_DEFUN([GIT_PARSE_WITH_SET_MAKE_VAR],
 +[AC_ARG_WITH([$1],
 + [AS_HELP_STRING([--with-$1=VALUE], $3)],
 + if test -n "$withval"; then \
 +  if test "$withval" = "yes" -o "$withval" = "no"; then \
 +    AC_MSG_WARN([You likely do not want either 'yes' or 'no' as]
 +                   [a value for $1 ($2).  Maybe you do...?]); \
 +  fi; \
 +  \
 +  AC_MSG_NOTICE([Setting $2 to $withval]); \
 +  GIT_CONF_APPEND_LINE($2=$withval); \
 + fi)])# GIT_PARSE_WITH_SET_MAKE_VAR
  
  dnl
  dnl GIT_CHECK_FUNC(FUNCTION, IFTRUE, IFFALSE)
@@@ -246,29 -226,6 +246,29 @@@ GIT_PARSE_WITH(iconv)
  # Define USE_STDEV below if you want git to care about the underlying device
  # change being considered an inode change from the update-index perspective.
  
 +#
 +# Allow user to set ETC_GITCONFIG variable
 +GIT_PARSE_WITH_SET_MAKE_VAR(gitconfig, ETC_GITCONFIG,
 +                      Use VALUE instead of /etc/gitconfig as the
 +                      global git configuration file.
 +                      If VALUE is not fully qualified it will be interpretted
 +                      as a path relative to the computed prefix at runtime.)
 +
 +#
 +# Allow user to set the default pager
 +GIT_PARSE_WITH_SET_MAKE_VAR(pager, DEFAULT_PAGER,
 +                      Use VALUE as the fall-back pager instead of 'less'.
 +                      This is used by things like 'git log' when the user
 +                      does not specify a pager to use through alternate
 +                      methods. eg: /usr/bin/pager)
 +#
 +# Allow user to set the default editor
 +GIT_PARSE_WITH_SET_MAKE_VAR(editor, DEFAULT_EDITOR,
 +                      Use VALUE as the fall-back editor instead of 'vi'.
 +                      This is used by things like 'git commit' when the user
 +                      does not specify a preferred editor through other
 +                      methods. eg: /usr/bin/editor)
 +
  #
  # Define SHELL_PATH to provide path to shell.
  GIT_ARG_SET_PATH(shell)
  # Define PERL_PATH to provide path to Perl.
  GIT_ARG_SET_PATH(perl)
  #
+ # Define PYTHON_PATH to provide path to Python.
+ GIT_ARG_SET_PATH(python)
+ #
  # Define ZLIB_PATH to provide path to zlib.
  GIT_ARG_SET_PATH(zlib)
  #
diff --combined remote.c
index b979a9642b81b456ab92cdf57e8eda944f01f92e,1f7870d107371af9dd4db1fb6e2e777138d682b3..e3afecdb1069a244493e906b5e16a96dc47535d3
+++ b/remote.c
@@@ -6,7 -6,6 +6,7 @@@
  #include "revision.h"
  #include "dir.h"
  #include "tag.h"
 +#include "string-list.h"
  
  static struct refspec s_tag_refspec = {
        0,
@@@ -53,6 -52,11 +53,11 @@@ static struct rewrites rewrites_push
  #define BUF_SIZE (2048)
  static char buffer[BUF_SIZE];
  
+ static int valid_remote(const struct remote *remote)
+ {
+       return (!!remote->url) || (!!remote->foreign_vcs);
+ }
  static const char *alias_url(const char *url, struct rewrites *r)
  {
        int i, j;
@@@ -397,8 -401,7 +402,8 @@@ static int handle_config(const char *ke
                remote->mirror = git_config_bool(key, value);
        else if (!strcmp(subkey, ".skipdefaultupdate"))
                remote->skip_default_update = git_config_bool(key, value);
 -
 +      else if (!strcmp(subkey, ".skipfetchall"))
 +              remote->skip_default_update = git_config_bool(key, value);
        else if (!strcmp(subkey, ".url")) {
                const char *v;
                if (git_config_string(&v, key, value))
        } else if (!strcmp(subkey, ".proxy")) {
                return git_config_string((const char **)&remote->http_proxy,
                                         key, value);
+       } else if (!strcmp(subkey, ".vcs")) {
+               return git_config_string(&remote->foreign_vcs, key, value);
        }
        return 0;
  }
@@@ -668,6 -673,16 +675,16 @@@ static struct refspec *parse_push_refsp
        return parse_refspec_internal(nr_refspec, refspec, 0, 0);
  }
  
+ void free_refspec(int nr_refspec, struct refspec *refspec)
+ {
+       int i;
+       for (i = 0; i < nr_refspec; i++) {
+               free(refspec[i].src);
+               free(refspec[i].dst);
+       }
+       free(refspec);
+ }
  static int valid_remote_nick(const char *name)
  {
        if (!name[0] || is_dot_or_dotdot(name))
@@@ -690,14 -705,14 +707,14 @@@ struct remote *remote_get(const char *n
  
        ret = make_remote(name, 0);
        if (valid_remote_nick(name)) {
-               if (!ret->url)
+               if (!valid_remote(ret))
                        read_remotes_file(ret);
-               if (!ret->url)
+               if (!valid_remote(ret))
                        read_branches_file(ret);
        }
-       if (name_given && !ret->url)
+       if (name_given && !valid_remote(ret))
                add_url_alias(ret, name);
-       if (!ret->url)
+       if (!valid_remote(ret))
                return NULL;
        ret->fetch = parse_fetch_refspec(ret->fetch_refspec_nr, ret->fetch_refspec);
        ret->push = parse_push_refspec(ret->push_refspec_nr, ret->push_refspec);
@@@ -736,33 -751,29 +753,33 @@@ int for_each_remote(each_remote_fn fn, 
  
  void ref_remove_duplicates(struct ref *ref_map)
  {
 -      struct ref **posn;
 -      struct ref *next;
 -      for (; ref_map; ref_map = ref_map->next) {
 +      struct string_list refs = { NULL, 0, 0, 0 };
 +      struct string_list_item *item = NULL;
 +      struct ref *prev = NULL, *next = NULL;
 +      for (; ref_map; prev = ref_map, ref_map = next) {
 +              next = 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;
 -                      }
 +
 +              item = string_list_lookup(ref_map->peer_ref->name, &refs);
 +              if (item) {
 +                      if (strcmp(((struct ref *)item->util)->name,
 +                                 ref_map->name))
 +                              die("%s tracks both %s and %s",
 +                                  ref_map->peer_ref->name,
 +                                  ((struct ref *)item->util)->name,
 +                                  ref_map->name);
 +                      prev->next = ref_map->next;
 +                      free(ref_map->peer_ref);
 +                      free(ref_map);
 +                      ref_map = prev; /* skip this; we freed it */
 +                      continue;
                }
 +
 +              item = string_list_insert(ref_map->peer_ref->name, &refs);
 +              item->util = ref_map;
        }
 +      string_list_clear(&refs, 0);
  }
  
  int remote_has_url(struct remote *remote, const char *url)
@@@ -810,6 -821,23 +827,23 @@@ static int match_name_with_pattern(cons
        return ret;
  }
  
+ char *apply_refspecs(struct refspec *refspecs, int nr_refspec,
+                    const char *name)
+ {
+       int i;
+       char *ret = NULL;
+       for (i = 0; i < nr_refspec; i++) {
+               struct refspec *refspec = refspecs + i;
+               if (refspec->pattern) {
+                       if (match_name_with_pattern(refspec->src, name,
+                                                   refspec->dst, &ret))
+                               return ret;
+               } else if (!strcmp(refspec->src, name))
+                       return strdup(refspec->dst);
+       }
+       return NULL;
+ }
  int remote_find_tracking(struct remote *remote, struct refspec *refspec)
  {
        int find_src = refspec->src == NULL;
@@@ -1592,42 -1620,3 +1626,42 @@@ struct ref *guess_remote_head(const str
  
        return list;
  }
 +
 +struct stale_heads_info {
 +      struct remote *remote;
 +      struct string_list *ref_names;
 +      struct ref **stale_refs_tail;
 +};
 +
 +static int get_stale_heads_cb(const char *refname,
 +      const unsigned char *sha1, int flags, void *cb_data)
 +{
 +      struct stale_heads_info *info = cb_data;
 +      struct refspec refspec;
 +      memset(&refspec, 0, sizeof(refspec));
 +      refspec.dst = (char *)refname;
 +      if (!remote_find_tracking(info->remote, &refspec)) {
 +              if (!((flags & REF_ISSYMREF) ||
 +                  string_list_has_string(info->ref_names, refspec.src))) {
 +                      struct ref *ref = make_linked_ref(refname, &info->stale_refs_tail);
 +                      hashcpy(ref->new_sha1, sha1);
 +              }
 +      }
 +      return 0;
 +}
 +
 +struct ref *get_stale_heads(struct remote *remote, struct ref *fetch_map)
 +{
 +      struct ref *ref, *stale_refs = NULL;
 +      struct string_list ref_names = { NULL, 0, 0, 0 };
 +      struct stale_heads_info info;
 +      info.remote = remote;
 +      info.ref_names = &ref_names;
 +      info.stale_refs_tail = &stale_refs;
 +      for (ref = fetch_map; ref; ref = ref->next)
 +              string_list_append(ref->name, &ref_names);
 +      sort_string_list(&ref_names);
 +      for_each_ref(get_stale_heads_cb, &info);
 +      string_list_clear(&ref_names, 0);
 +      return stale_refs;
 +}
diff --combined remote.h
index d0aba81ead1847e43a971362659abf1c1737c12f,cdc3b5b159351b550f7136594e0d9f930177e576..8b7ecf9197f2a9210299c9700cc88a92a796f714
+++ b/remote.h
@@@ -11,6 -11,8 +11,8 @@@ struct remote 
        const char *name;
        int origin;
  
+       const char *foreign_vcs;
        const char **url;
        int url_nr;
        int url_alloc;
@@@ -89,6 -91,11 +91,11 @@@ void ref_remove_duplicates(struct ref *
  int valid_fetch_refspec(const char *refspec);
  struct refspec *parse_fetch_refspec(int nr_refspec, const char **refspec);
  
+ void free_refspec(int nr_refspec, struct refspec *refspec);
+ char *apply_refspecs(struct refspec *refspecs, int nr_refspec,
+                    const char *name);
  int match_refs(struct ref *src, struct ref **dst,
               int nr_refspec, const char **refspec, int all);
  
@@@ -154,7 -161,4 +161,7 @@@ struct ref *guess_remote_head(const str
                              const struct ref *refs,
                              int all);
  
 +/* Return refs which no longer exist on remote */
 +struct ref *get_stale_heads(struct remote *remote, struct ref *fetch_map);
 +
  #endif
diff --combined t/test-lib.sh
index ec3336aba5a65a468bc6ce71f33a9cca76dbfe0f,f89e9687a720ac42fd8c7ad2356ece5f98a12792..2d523fe0f08dbbc1971f4601f24664cdbce95415
@@@ -30,7 -30,7 +30,7 @@@ TZ=UT
  TERM=dumb
  export LANG LC_ALL PAGER TERM TZ
  EDITOR=:
 -VISUAL=:
 +unset VISUAL
  unset GIT_EDITOR
  unset AUTHOR_DATE
  unset AUTHOR_EMAIL
@@@ -58,7 -58,7 +58,7 @@@ GIT_MERGE_VERBOSITY=
  export GIT_MERGE_VERBOSITY
  export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME
  export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME
 -export EDITOR VISUAL
 +export EDITOR
  GIT_TEST_CMP=${GIT_TEST_CMP:-diff -u}
  
  # Protect ourselves from common misconfiguration to export
@@@ -207,8 -207,8 +207,8 @@@ trap 'die' EXI
  test_set_editor () {
        FAKE_EDITOR="$1"
        export FAKE_EDITOR
 -      VISUAL='"$FAKE_EDITOR"'
 -      export VISUAL
 +      EDITOR='"$FAKE_EDITOR"'
 +      export EDITOR
  }
  
  test_tick () {
@@@ -632,20 -632,29 +632,29 @@@ GIT_CONFIG_NOSYSTEM=
  GIT_CONFIG_NOGLOBAL=1
  export PATH GIT_EXEC_PATH GIT_TEMPLATE_DIR GIT_CONFIG_NOSYSTEM GIT_CONFIG_NOGLOBAL
  
+ . ../GIT-BUILD-OPTIONS
  GITPERLLIB=$(pwd)/../perl/blib/lib:$(pwd)/../perl/blib/arch/auto/Git
  export GITPERLLIB
  test -d ../templates/blt || {
        error "You haven't built things yet, have you?"
  }
  
+ if test -z "$GIT_TEST_INSTALLED" && test -z "$NO_PYTHON"
+ then
+       GITPYTHONLIB="$(pwd)/../git_remote_helpers/build/lib"
+       export GITPYTHONLIB
+       test -d ../git_remote_helpers/build || {
+               error "You haven't built git_remote_helpers yet, have you?"
+       }
+ fi
  if ! test -x ../test-chmtime; then
        echo >&2 'You need to build test-chmtime:'
        echo >&2 'Run "make test-chmtime" in the source (toplevel) directory'
        exit 1
  fi
  
- . ../GIT-BUILD-OPTIONS
  # Test repository
  test="trash directory.$(basename "$0" .sh)"
  test -n "$root" && test="$root/$test"
@@@ -729,6 -738,7 +738,7 @@@ case $(uname -s) i
  esac
  
  test -z "$NO_PERL" && test_set_prereq PERL
+ test -z "$NO_PYTHON" && test_set_prereq PYTHON
  
  # test whether the filesystem supports symbolic links
  ln -s x y 2>/dev/null && test -h y 2>/dev/null && test_set_prereq SYMLINKS
diff --combined transport-helper.c
index 5078c7100f16e9d7ac08aad05e3797ad5b6626bc,c87530e87d0891a9e14c8fcddd57051b0fe1bcde..11f3d7ec52893dcdee127d8493f1897c6c091b31
@@@ -1,20 -1,21 +1,25 @@@
  #include "cache.h"
  #include "transport.h"
 -
 +#include "quote.h"
  #include "run-command.h"
  #include "commit.h"
  #include "diff.h"
  #include "revision.h"
 +#include "quote.h"
+ #include "remote.h"
  
  struct helper_data
  {
        const char *name;
        struct child_process *helper;
 -      unsigned fetch : 1;
 -      unsigned import : 1;
 +      FILE *out;
 +      unsigned fetch : 1,
++              import : 1,
 +              option : 1,
 +              push : 1;
+       /* These go from remote name (as in "list") to private name */
+       struct refspec *refspecs;
+       int refspec_nr;
  };
  
  static struct child_process *get_helper(struct transport *transport)
        struct helper_data *data = transport->data;
        struct strbuf buf = STRBUF_INIT;
        struct child_process *helper;
 -      FILE *file;
+       const char **refspecs = NULL;
+       int refspec_nr = 0;
+       int refspec_alloc = 0;
  
        if (data->helper)
                return data->helper;
  
        write_str_in_full(helper->in, "capabilities\n");
  
 -      file = xfdopen(helper->out, "r");
 +      data->out = xfdopen(helper->out, "r");
        while (1) {
 -              if (strbuf_getline(&buf, file, '\n') == EOF)
 +              if (strbuf_getline(&buf, data->out, '\n') == EOF)
                        exit(128); /* child died, message supplied already */
  
                if (!*buf.buf)
                        break;
                if (!strcmp(buf.buf, "fetch"))
                        data->fetch = 1;
 +              if (!strcmp(buf.buf, "option"))
 +                      data->option = 1;
 +              if (!strcmp(buf.buf, "push"))
 +                      data->push = 1;
+               if (!strcmp(buf.buf, "import"))
+                       data->import = 1;
+               if (!data->refspecs && !prefixcmp(buf.buf, "refspec ")) {
+                       ALLOC_GROW(refspecs,
+                                  refspec_nr + 1,
+                                  refspec_alloc);
+                       refspecs[refspec_nr++] = strdup(buf.buf + strlen("refspec "));
+               }
+       }
+       if (refspecs) {
+               int i;
+               data->refspec_nr = refspec_nr;
+               data->refspecs = parse_fetch_refspec(refspec_nr, refspecs);
+               for (i = 0; i < refspec_nr; i++) {
+                       free((char *)refspecs[i]);
+               }
+               free(refspecs);
        }
+       strbuf_release(&buf);
        return data->helper;
  }
  
@@@ -65,104 -84,33 +91,113 @@@ static int disconnect_helper(struct tra
        if (data->helper) {
                write_str_in_full(data->helper->in, "\n");
                close(data->helper->in);
 +              fclose(data->out);
                finish_command(data->helper);
                free((char *)data->helper->argv[0]);
                free(data->helper->argv);
                free(data->helper);
                data->helper = NULL;
        }
-       free(data);
        return 0;
  }
  
 +static const char *unsupported_options[] = {
 +      TRANS_OPT_UPLOADPACK,
 +      TRANS_OPT_RECEIVEPACK,
 +      TRANS_OPT_THIN,
 +      TRANS_OPT_KEEP
 +      };
 +static const char *boolean_options[] = {
 +      TRANS_OPT_THIN,
 +      TRANS_OPT_KEEP,
 +      TRANS_OPT_FOLLOWTAGS
 +      };
 +
 +static int set_helper_option(struct transport *transport,
 +                        const char *name, const char *value)
 +{
 +      struct helper_data *data = transport->data;
 +      struct child_process *helper = get_helper(transport);
 +      struct strbuf buf = STRBUF_INIT;
 +      int i, ret, is_bool = 0;
 +
 +      if (!data->option)
 +              return 1;
 +
 +      for (i = 0; i < ARRAY_SIZE(unsupported_options); i++) {
 +              if (!strcmp(name, unsupported_options[i]))
 +                      return 1;
 +      }
 +
 +      for (i = 0; i < ARRAY_SIZE(boolean_options); i++) {
 +              if (!strcmp(name, boolean_options[i])) {
 +                      is_bool = 1;
 +                      break;
 +              }
 +      }
 +
 +      strbuf_addf(&buf, "option %s ", name);
 +      if (is_bool)
 +              strbuf_addstr(&buf, value ? "true" : "false");
 +      else
 +              quote_c_style(value, &buf, NULL, 0);
 +      strbuf_addch(&buf, '\n');
 +
 +      if (write_in_full(helper->in, buf.buf, buf.len) != buf.len)
 +              die_errno("cannot send option to %s", data->name);
 +
 +      strbuf_reset(&buf);
 +      if (strbuf_getline(&buf, data->out, '\n') == EOF)
 +              exit(128); /* child died, message supplied already */
 +
 +      if (!strcmp(buf.buf, "ok"))
 +              ret = 0;
 +      else if (!prefixcmp(buf.buf, "error")) {
 +              ret = -1;
 +      } else if (!strcmp(buf.buf, "unsupported"))
 +              ret = 1;
 +      else {
 +              warning("%s unexpectedly said: '%s'", data->name, buf.buf);
 +              ret = 1;
 +      }
 +      strbuf_release(&buf);
 +      return ret;
 +}
 +
 +static void standard_options(struct transport *t)
 +{
 +      char buf[16];
 +      int n;
 +      int v = t->verbose;
 +      int no_progress = v < 0 || (!t->progress && !isatty(1));
 +
 +      set_helper_option(t, "progress", !no_progress ? "true" : "false");
 +
 +      n = snprintf(buf, sizeof(buf), "%d", v + 1);
 +      if (n >= sizeof(buf))
 +              die("impossibly large verbosity value");
 +      set_helper_option(t, "verbosity", buf);
 +}
 +
+ static int release_helper(struct transport *transport)
+ {
+       struct helper_data *data = transport->data;
+       free_refspec(data->refspec_nr, data->refspecs);
+       data->refspecs = NULL;
+       disconnect_helper(transport);
+       free(transport->data);
+       return 0;
+ }
  static int fetch_with_fetch(struct transport *transport,
-                           int nr_heads, const struct ref **to_fetch)
+                           int nr_heads, struct ref **to_fetch)
  {
 -      struct child_process *helper = get_helper(transport);
 -      FILE *file = xfdopen(helper->out, "r");
 +      struct helper_data *data = transport->data;
        int i;
        struct strbuf buf = STRBUF_INIT;
  
 +      standard_options(transport);
 +
        for (i = 0; i < nr_heads; i++) {
                const struct ref *posn = to_fetch[i];
                if (posn->status & REF_STATUS_UPTODATE)
  
                strbuf_addf(&buf, "fetch %s %s\n",
                            sha1_to_hex(posn->old_sha1), posn->name);
 -              write_in_full(helper->in, buf.buf, buf.len);
 -              strbuf_reset(&buf);
 +      }
  
 -              if (strbuf_getline(&buf, file, '\n') == EOF)
 +      strbuf_addch(&buf, '\n');
 +      if (write_in_full(data->helper->in, buf.buf, buf.len) != buf.len)
 +              die_errno("cannot send fetch to %s", data->name);
 +
 +      while (1) {
 +              strbuf_reset(&buf);
 +              if (strbuf_getline(&buf, data->out, '\n') == EOF)
                        exit(128); /* child died, message supplied already */
 +
 +              if (!prefixcmp(buf.buf, "lock ")) {
 +                      const char *name = buf.buf + 5;
 +                      if (transport->pack_lockfile)
 +                              warning("%s also locked %s", data->name, name);
 +                      else
 +                              transport->pack_lockfile = xstrdup(name);
 +              }
 +              else if (!buf.len)
 +                      break;
 +              else
 +                      warning("%s unexpectedly said: '%s'", data->name, buf.buf);
        }
 +      strbuf_release(&buf);
        return 0;
  }
  
+ static int get_importer(struct transport *transport, struct child_process *fastimport)
+ {
+       struct child_process *helper = get_helper(transport);
+       memset(fastimport, 0, sizeof(*fastimport));
+       fastimport->in = helper->out;
+       fastimport->argv = xcalloc(5, sizeof(*fastimport->argv));
+       fastimport->argv[0] = "fast-import";
+       fastimport->argv[1] = "--quiet";
+       fastimport->git_cmd = 1;
+       return start_command(fastimport);
+ }
+ static int fetch_with_import(struct transport *transport,
+                            int nr_heads, struct ref **to_fetch)
+ {
+       struct child_process fastimport;
+       struct child_process *helper = get_helper(transport);
+       struct helper_data *data = transport->data;
+       int i;
+       struct ref *posn;
+       struct strbuf buf = STRBUF_INIT;
+       if (get_importer(transport, &fastimport))
+               die("Couldn't run fast-import");
+       for (i = 0; i < nr_heads; i++) {
+               posn = to_fetch[i];
+               if (posn->status & REF_STATUS_UPTODATE)
+                       continue;
+               strbuf_addf(&buf, "import %s\n", posn->name);
+               write_in_full(helper->in, buf.buf, buf.len);
+               strbuf_reset(&buf);
+       }
+       disconnect_helper(transport);
+       finish_command(&fastimport);
+       free(fastimport.argv);
+       fastimport.argv = NULL;
+       for (i = 0; i < nr_heads; i++) {
+               char *private;
+               posn = to_fetch[i];
+               if (posn->status & REF_STATUS_UPTODATE)
+                       continue;
+               if (data->refspecs)
+                       private = apply_refspecs(data->refspecs, data->refspec_nr, posn->name);
+               else
+                       private = strdup(posn->name);
+               read_ref(private, posn->old_sha1);
+               free(private);
+       }
+       strbuf_release(&buf);
+       return 0;
+ }
  static int fetch(struct transport *transport,
-                int nr_heads, const struct ref **to_fetch)
+                int nr_heads, struct ref **to_fetch)
  {
        struct helper_data *data = transport->data;
        int i, count;
        if (data->fetch)
                return fetch_with_fetch(transport, nr_heads, to_fetch);
  
+       if (data->import)
+               return fetch_with_import(transport, nr_heads, to_fetch);
        return -1;
  }
  
 +static int push_refs(struct transport *transport,
 +              struct ref *remote_refs, int flags)
 +{
 +      int force_all = flags & TRANSPORT_PUSH_FORCE;
 +      int mirror = flags & TRANSPORT_PUSH_MIRROR;
 +      struct helper_data *data = transport->data;
 +      struct strbuf buf = STRBUF_INIT;
 +      struct child_process *helper;
 +      struct ref *ref;
 +
 +      if (!remote_refs)
 +              return 0;
 +
 +      helper = get_helper(transport);
 +      if (!data->push)
 +              return 1;
 +
 +      for (ref = remote_refs; ref; ref = ref->next) {
 +              if (ref->peer_ref)
 +                      hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
 +              else if (!mirror)
 +                      continue;
 +
 +              ref->deletion = is_null_sha1(ref->new_sha1);
 +              if (!ref->deletion &&
 +                      !hashcmp(ref->old_sha1, ref->new_sha1)) {
 +                      ref->status = REF_STATUS_UPTODATE;
 +                      continue;
 +              }
 +
 +              if (force_all)
 +                      ref->force = 1;
 +
 +              strbuf_addstr(&buf, "push ");
 +              if (!ref->deletion) {
 +                      if (ref->force)
 +                              strbuf_addch(&buf, '+');
 +                      if (ref->peer_ref)
 +                              strbuf_addstr(&buf, ref->peer_ref->name);
 +                      else
 +                              strbuf_addstr(&buf, sha1_to_hex(ref->new_sha1));
 +              }
 +              strbuf_addch(&buf, ':');
 +              strbuf_addstr(&buf, ref->name);
 +              strbuf_addch(&buf, '\n');
 +      }
 +      if (buf.len == 0)
 +              return 0;
 +
 +      transport->verbose = flags & TRANSPORT_PUSH_VERBOSE ? 1 : 0;
 +      standard_options(transport);
 +
 +      if (flags & TRANSPORT_PUSH_DRY_RUN) {
 +              if (set_helper_option(transport, "dry-run", "true") != 0)
 +                      die("helper %s does not support dry-run", data->name);
 +      }
 +
 +      strbuf_addch(&buf, '\n');
 +      if (write_in_full(helper->in, buf.buf, buf.len) != buf.len)
 +              exit(128);
 +
 +      ref = remote_refs;
 +      while (1) {
 +              char *refname, *msg;
 +              int status;
 +
 +              strbuf_reset(&buf);
 +              if (strbuf_getline(&buf, data->out, '\n') == EOF)
 +                      exit(128); /* child died, message supplied already */
 +              if (!buf.len)
 +                      break;
 +
 +              if (!prefixcmp(buf.buf, "ok ")) {
 +                      status = REF_STATUS_OK;
 +                      refname = buf.buf + 3;
 +              } else if (!prefixcmp(buf.buf, "error ")) {
 +                      status = REF_STATUS_REMOTE_REJECT;
 +                      refname = buf.buf + 6;
 +              } else
 +                      die("expected ok/error, helper said '%s'\n", buf.buf);
 +
 +              msg = strchr(refname, ' ');
 +              if (msg) {
 +                      struct strbuf msg_buf = STRBUF_INIT;
 +                      const char *end;
 +
 +                      *msg++ = '\0';
 +                      if (!unquote_c_style(&msg_buf, msg, &end))
 +                              msg = strbuf_detach(&msg_buf, NULL);
 +                      else
 +                              msg = xstrdup(msg);
 +                      strbuf_release(&msg_buf);
 +
 +                      if (!strcmp(msg, "no match")) {
 +                              status = REF_STATUS_NONE;
 +                              free(msg);
 +                              msg = NULL;
 +                      }
 +                      else if (!strcmp(msg, "up to date")) {
 +                              status = REF_STATUS_UPTODATE;
 +                              free(msg);
 +                              msg = NULL;
 +                      }
 +                      else if (!strcmp(msg, "non-fast forward")) {
 +                              status = REF_STATUS_REJECT_NONFASTFORWARD;
 +                              free(msg);
 +                              msg = NULL;
 +                      }
 +              }
 +
 +              if (ref)
 +                      ref = find_ref_by_name(ref, refname);
 +              if (!ref)
 +                      ref = find_ref_by_name(remote_refs, refname);
 +              if (!ref) {
 +                      warning("helper reported unexpected status of %s", refname);
 +                      continue;
 +              }
 +
 +              ref->status = status;
 +              ref->remote_status = msg;
 +      }
 +      strbuf_release(&buf);
 +      return 0;
 +}
 +
+ static int has_attribute(const char *attrs, const char *attr) {
+       int len;
+       if (!attrs)
+               return 0;
+       len = strlen(attr);
+       for (;;) {
+               const char *space = strchrnul(attrs, ' ');
+               if (len == space - attrs && !strncmp(attrs, attr, len))
+                       return 1;
+               if (!*space)
+                       return 0;
+               attrs = space + 1;
+       }
+ }
  static struct ref *get_refs_list(struct transport *transport, int for_push)
  {
 +      struct helper_data *data = transport->data;
        struct child_process *helper;
        struct ref *ret = NULL;
        struct ref **tail = &ret;
        struct ref *posn;
        struct strbuf buf = STRBUF_INIT;
 -      FILE *file;
  
        helper = get_helper(transport);
  
 -      write_str_in_full(helper->in, "list\n");
 +      if (data->push && for_push)
 +              write_str_in_full(helper->in, "list for-push\n");
 +      else
 +              write_str_in_full(helper->in, "list\n");
  
 -      file = xfdopen(helper->out, "r");
        while (1) {
                char *eov, *eon;
 -              if (strbuf_getline(&buf, file, '\n') == EOF)
 +              if (strbuf_getline(&buf, data->out, '\n') == EOF)
                        exit(128); /* child died, message supplied already */
  
                if (!*buf.buf)
                        (*tail)->symref = xstrdup(buf.buf + 1);
                else if (buf.buf[0] != '?')
                        get_sha1_hex(buf.buf, (*tail)->old_sha1);
+               if (eon) {
+                       if (has_attribute(eon + 1, "unchanged")) {
+                               (*tail)->status |= REF_STATUS_UPTODATE;
+                               read_ref((*tail)->name, (*tail)->old_sha1);
+                       }
+               }
                tail = &((*tail)->next);
        }
        strbuf_release(&buf);
@@@ -395,10 -278,8 +511,10 @@@ int transport_helper_init(struct transp
        data->name = name;
  
        transport->data = data;
 +      transport->set_option = set_helper_option;
        transport->get_refs_list = get_refs_list;
        transport->fetch = fetch;
-       transport->disconnect = disconnect_helper;
 +      transport->push_refs = push_refs;
+       transport->disconnect = release_helper;
        return 0;
  }
diff --combined transport.c
index 7362ec09b2cbc6752489286a8280c16d3519f163,5d814b50e4c1b52df4b1ab2c5fbebaeedc16e634..3eea836a33a56aaa99eec78bb4850b02888e377b
@@@ -204,7 -204,7 +204,7 @@@ static struct ref *get_refs_via_rsync(s
  }
  
  static int fetch_objs_via_rsync(struct transport *transport,
-                               int nr_objs, const struct ref **to_fetch)
+                               int nr_objs, struct ref **to_fetch)
  {
        struct strbuf buf = STRBUF_INIT;
        struct child_process rsync;
@@@ -349,6 -349,35 +349,6 @@@ static int rsync_transport_push(struct 
        return result;
  }
  
 -#ifndef NO_CURL
 -static int curl_transport_push(struct transport *transport, int refspec_nr, const char **refspec, int flags)
 -{
 -      const char **argv;
 -      int argc;
 -
 -      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;
 -      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";
 -      argv[argc++] = transport->url;
 -      while (refspec_nr--)
 -              argv[argc++] = *refspec++;
 -      argv[argc] = NULL;
 -      return !!run_command_v_opt(argv, RUN_GIT_CMD);
 -}
 -
 -#endif
 -
  struct bundle_transport_data {
        int fd;
        struct bundle_header header;
@@@ -379,7 -408,7 +379,7 @@@ static struct ref *get_refs_from_bundle
  }
  
  static int fetch_refs_from_bundle(struct transport *transport,
-                              int nr_heads, const struct ref **to_fetch)
+                              int nr_heads, struct ref **to_fetch)
  {
        struct bundle_transport_data *data = transport->data;
        return unbundle(&data->header, data->fd);
@@@ -457,7 -486,7 +457,7 @@@ static struct ref *get_refs_via_connect
  }
  
  static int fetch_refs_via_pack(struct transport *transport,
-                              int nr_heads, const struct ref **to_fetch)
+                              int nr_heads, struct ref **to_fetch)
  {
        struct git_transport_data *data = transport->data;
        char **heads = xmalloc(nr_heads * sizeof(*heads));
@@@ -639,7 -668,7 +639,7 @@@ static int print_one_push_status(struc
                break;
        case REF_STATUS_REJECT_NONFASTFORWARD:
                print_ref_status('!', "[rejected]", ref, ref->peer_ref,
 -                                               "non-fast forward", porcelain);
 +                                               "non-fast-forward", porcelain);
                break;
        case REF_STATUS_REMOTE_REJECT:
                print_ref_status('!', "[remote rejected]", ref,
@@@ -731,7 -760,6 +731,7 @@@ static int git_transport_push(struct tr
                                 NULL);
        }
  
 +      memset(&args, 0, sizeof(args));
        args.send_mirror = !!(flags & TRANSPORT_PUSH_MIRROR);
        args.force_update = !!(flags & TRANSPORT_PUSH_FORCE);
        args.use_thin_pack = data->thin;
@@@ -784,12 -812,27 +784,30 @@@ struct transport *transport_get(struct 
  {
        struct transport *ret = xcalloc(1, sizeof(*ret));
  
 +      if (!remote)
 +              die("No remote provided to transport_get()");
 +
        ret->remote = remote;
+       if (!url && remote && remote->url)
+               url = remote->url[0];
        ret->url = url;
  
+       /* maybe it is a foreign URL? */
+       if (url) {
+               const char *p = url;
+               while (isalnum(*p))
+                       p++;
+               if (!prefixcmp(p, "::"))
+                       remote->foreign_vcs = xstrndup(url, p - url);
+       }
+       if (remote && remote->foreign_vcs) {
+               transport_helper_init(ret, remote->foreign_vcs);
+               return ret;
+       }
        if (!prefixcmp(url, "rsync:")) {
                ret->get_refs_list = get_refs_via_rsync;
                ret->fetch = fetch_objs_via_rsync;
                transport_helper_init(ret, "curl");
  #ifdef NO_CURL
                error("git was compiled without libcurl support.");
 -#else
 -              ret->push = curl_transport_push;
  #endif
  
        } else if (is_local(url) && is_file(url)) {
                data->thin = 1;
                data->conn = NULL;
                data->uploadpack = "git-upload-pack";
 -              if (remote && remote->uploadpack)
 +              if (remote->uploadpack)
                        data->uploadpack = remote->uploadpack;
                data->receivepack = "git-receive-pack";
 -              if (remote && remote->receivepack)
 +              if (remote->receivepack)
                        data->receivepack = remote->receivepack;
        }
  
@@@ -896,16 -941,17 +914,17 @@@ const struct ref *transport_get_remote_
        return transport->remote_refs;
  }
  
- int transport_fetch_refs(struct transport *transport, const struct ref *refs)
+ int transport_fetch_refs(struct transport *transport, struct ref *refs)
  {
        int rc;
        int nr_heads = 0, nr_alloc = 0, nr_refs = 0;
-       const struct ref **heads = NULL;
-       const struct ref *rm;
+       struct ref **heads = NULL;
+       struct ref *rm;
  
        for (rm = refs; rm; rm = rm->next) {
                nr_refs++;
                if (rm->peer_ref &&
+                   !is_null_sha1(rm->old_sha1) &&
                    !hashcmp(rm->peer_ref->old_sha1, rm->old_sha1))
                        continue;
                ALLOC_GROW(heads, nr_heads + 1, nr_alloc);
diff --combined transport.h
index e4e6177e2632b4a764703c11ef0a8033eafea037,503db11701b44766716bbe435e58a69d0d36b871..9e74406fa203a59920cb24884d7893b45d876a70
@@@ -18,14 -18,51 +18,51 @@@ struct transport 
        int (*set_option)(struct transport *connection, const char *name,
                          const char *value);
  
+       /**
+        * Returns a list of the remote side's refs. In order to allow
+        * the transport to try to share connections, for_push is a
+        * hint as to whether the ultimate operation is a push or a fetch.
+        *
+        * If the transport is able to determine the remote hash for
+        * the ref without a huge amount of effort, it should store it
+        * in the ref's old_sha1 field; otherwise it should be all 0.
+        **/
        struct ref *(*get_refs_list)(struct transport *transport, int for_push);
-       int (*fetch)(struct transport *transport, int refs_nr, const struct ref **refs);
+       /**
+        * Fetch the objects for the given refs. Note that this gets
+        * an array, and should ignore the list structure.
+        *
+        * If the transport did not get hashes for refs in
+        * get_refs_list(), it should set the old_sha1 fields in the
+        * provided refs now.
+        **/
+       int (*fetch)(struct transport *transport, int refs_nr, struct ref **refs);
+       /**
+        * Push the objects and refs. Send the necessary objects, and
+        * then, for any refs where peer_ref is set and
+        * peer_ref->new_sha1 is different from old_sha1, tell the
+        * remote side to update each ref in the list from old_sha1 to
+        * peer_ref->new_sha1.
+        *
+        * Where possible, set the status for each ref appropriately.
+        *
+        * The transport must modify new_sha1 in the ref to the new
+        * value if the remote accepted the change. Note that this
+        * could be a different value from peer_ref->new_sha1 if the
+        * process involved generating new commits.
+        **/
        int (*push_refs)(struct transport *transport, struct ref *refs, int flags);
        int (*push)(struct transport *connection, int refspec_nr, const char **refspec, int flags);
  
+       /** get_refs_list(), fetch(), and push_refs() can keep
+        * resources (such as a connection) reserved for futher
+        * use. disconnect() releases these resources.
+        **/
        int (*disconnect)(struct transport *connection);
        char *pack_lockfile;
 -      signed verbose : 2;
 +      signed verbose : 3;
        /* Force progress even if the output is not a tty */
        unsigned progress : 1;
  };
@@@ -74,7 -111,7 +111,7 @@@ int transport_push(struct transport *co
  
  const struct ref *transport_get_remote_refs(struct transport *transport);
  
- int transport_fetch_refs(struct transport *transport, const struct ref *refs);
+ int transport_fetch_refs(struct transport *transport, struct ref *refs);
  void transport_unlock_pack(struct transport *transport);
  int transport_disconnect(struct transport *transport);
  char *transport_anonymize_url(const char *url);