Merge branch 'bw/protocol-v2' into next
authorJunio C Hamano <gitster@pobox.com>
Fri, 30 Mar 2018 01:27:21 +0000 (18:27 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 30 Mar 2018 01:27:21 +0000 (18:27 -0700)
The beginning of the next-gen transfer protocol.

* bw/protocol-v2: (35 commits)
remote-curl: don't request v2 when pushing
remote-curl: implement stateless-connect command
http: eliminate "# service" line when using protocol v2
http: don't always add Git-Protocol header
http: allow providing extra headers for http requests
remote-curl: store the protocol version the server responded with
remote-curl: create copy of the service name
pkt-line: add packet_buf_write_len function
transport-helper: introduce stateless-connect
transport-helper: refactor process_connect_service
transport-helper: remove name parameter
connect: don't request v2 when pushing
connect: refactor git_connect to only get the protocol version once
fetch-pack: support shallow requests
fetch-pack: perform a fetch using v2
upload-pack: introduce fetch server command
push: pass ref prefixes when pushing
fetch: pass ref prefixes when fetching
ls-remote: pass ref prefixes when requesting a remote's refs
transport: convert transport_get_remote_refs to take a list of ref prefixes
...

20 files changed:
1  2 
Documentation/Makefile
Documentation/gitremote-helpers.txt
Makefile
builtin/clone.c
builtin/fetch-pack.c
builtin/fetch.c
builtin/ls-remote.c
builtin/receive-pack.c
builtin/remote.c
fetch-pack.c
fetch-pack.h
git.c
http.c
refs.c
remote-curl.c
remote.h
transport-helper.c
transport.c
transport.h
upload-pack.c
diff --combined Documentation/Makefile
index 6232143cb95d105b0c33b81d16c6b7b628fadb97,b105775acd6ee0e141eaf9ac859d577225233bc2..fa9e5c0acd762d3623a5b436349b8d87e41aeb32
@@@ -72,12 -72,12 +72,13 @@@ TECH_DOCS += SubmittingPatche
  TECH_DOCS += technical/hash-function-transition
  TECH_DOCS += technical/http-protocol
  TECH_DOCS += technical/index-format
 +TECH_DOCS += technical/long-running-process-protocol
  TECH_DOCS += technical/pack-format
  TECH_DOCS += technical/pack-heuristics
  TECH_DOCS += technical/pack-protocol
  TECH_DOCS += technical/protocol-capabilities
  TECH_DOCS += technical/protocol-common
+ TECH_DOCS += technical/protocol-v2
  TECH_DOCS += technical/racy-git
  TECH_DOCS += technical/send-pack-pipeline
  TECH_DOCS += technical/shallow
index 4b8c93ec59de3db02b9914aed4955d486be5f875,cd9b34d230529094c6dd320048f3e2db7eec9433..9d1459aac6d0b12ad1a87ff25a158dca0f2bf470
@@@ -102,6 -102,14 +102,14 @@@ Capabilities for Pushin
  +
  Supported commands: 'connect'.
  
+ 'stateless-connect'::
+       Experimental; for internal use only.
+       Can attempt to connect to a remote server for communication
+       using git's wire-protocol version 2.  See the documentation
+       for the stateless-connect command for more information.
+ +
+ Supported commands: 'stateless-connect'.
  'push'::
        Can discover remote refs and push local commits and the
        history leading up to them to new or existing remote refs.
@@@ -136,6 -144,14 +144,14 @@@ Capabilities for Fetchin
  +
  Supported commands: 'connect'.
  
+ 'stateless-connect'::
+       Experimental; for internal use only.
+       Can attempt to connect to a remote server for communication
+       using git's wire-protocol version 2.  See the documentation
+       for the stateless-connect command for more information.
+ +
+ Supported commands: 'stateless-connect'.
  'fetch'::
        Can discover remote refs and transfer objects reachable from
        them to the local object store.
@@@ -375,6 -391,22 +391,22 @@@ Supported if the helper has the "export
  +
  Supported if the helper has the "connect" capability.
  
+ 'stateless-connect' <service>::
+       Experimental; for internal use only.
+       Connects to the given remote service for communication using
+       git's wire-protocol version 2.  Valid replies to this command
+       are empty line (connection established), 'fallback' (no smart
+       transport support, fall back to dumb transports) and just
+       exiting with error message printed (can't connect, don't bother
+       trying to fall back).  After line feed terminating the positive
+       (empty) response, the output of the service starts.  Messages
+       (both request and response) must consist of zero or more
+       PKT-LINEs, terminating in a flush packet. The client must not
+       expect the server to store any state in between request-response
+       pairs.  After the connection ends, the remote helper exits.
+ +
+ Supported if the helper has the "stateless-connect" 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
@@@ -466,13 -498,6 +498,13 @@@ set by Git if the remote helper has th
        Transmit <string> as a push option. As the push option
        must not contain LF or NUL characters, the string is not encoded.
  
 +'option from-promisor' {'true'|'false'}::
 +      Indicate that these objects are being fetched from a promisor.
 +
 +'option no-dependents' {'true'|'false'}::
 +      Indicate that only the objects wanted need to be fetched, not
 +      their dependents.
 +
  SEE ALSO
  --------
  linkgit:git-remote[1]
diff --combined Makefile
index 96f6138f634b6aaf009edd5026320494b27a77f5,fb2175bcf1ed909f8726b14f72e21fd36ae9a658..82b1e644c4c9dfaf6094333ad82b9ff1adff3fa6
+++ b/Makefile
@@@ -23,16 -23,17 +23,16 @@@ all:
  # it at all).
  #
  # Define NO_OPENSSL environment variable if you do not have OpenSSL.
 -# This also implies BLK_SHA1.
  #
  # Define USE_LIBPCRE if you have and want to use libpcre. Various
  # commands such as log and grep offer runtime options to use
  # Perl-compatible regular expressions instead of standard or extended
  # POSIX regular expressions.
  #
 -# Currently USE_LIBPCRE is a synonym for USE_LIBPCRE1, define
 -# USE_LIBPCRE2 instead if you'd like to use version 2 of the PCRE
 -# library. The USE_LIBPCRE flag will likely be changed to mean v2 by
 -# default in future releases.
 +# USE_LIBPCRE is a synonym for USE_LIBPCRE2, define USE_LIBPCRE1
 +# instead if you'd like to use the legacy version 1 of the PCRE
 +# library. Support for version 1 will likely be removed in some future
 +# release of Git, as upstream has all but abandoned it.
  #
  # When using USE_LIBPCRE1, define NO_LIBPCRE1_JIT if the PCRE v1
  # library is compiled without --enable-jit. We will auto-detect
  #
  # Define PERL_PATH to the path of your Perl binary (usually /usr/bin/perl).
  #
 -# 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_PERL_CPAN_FALLBACKS if you do not want to install bundled
 +# copies of CPAN modules that serve as a fallback in case the modules
 +# are not available on the system. This option is intended for
 +# distributions that want to use their packaged versions of Perl
 +# modules, instead of the fallbacks shipped with Git.
 +#
  # Define PYTHON_PATH to the path of your Python binary (often /usr/bin/python
  # but /usr/bin/python2.7 on some platforms).
  #
  # when hardlinking a file to another name and unlinking the original file right
  # away (some NTFS drivers seem to zero the contents in that scenario).
  #
 +# Define INSTALL_SYMLINKS if you prefer to have everything that can be
 +# symlinked between bin/ and libexec/ to use relative symlinks between
 +# the two. This option overrides NO_CROSS_DIRECTORY_HARDLINKS and
 +# NO_INSTALL_HARDLINKS which will also use symlinking by indirection
 +# within the same directory in some cases, INSTALL_SYMLINKS will
 +# always symlink to the final target directly.
 +#
  # Define NO_CROSS_DIRECTORY_HARDLINKS if you plan to distribute the installed
  # programs as a tar, where bin/ and libexec/ might be on different file systems.
  #
  # to say "export LESS=FRX (and LV=-c) if the environment variable
  # LESS (and LV) is not set, respectively".
  #
 +# Define TEST_SHELL_PATH if you want to use a shell besides SHELL_PATH for
 +# running the test scripts (e.g., bash has better support for "set -x"
 +# tracing).
 +#
  # When cross-compiling, define HOST_CPU as the canonical name of the CPU on
  # which the built Git will run (for instance "x86_64").
  
@@@ -481,14 -468,14 +481,14 @@@ ARFLAGS = rc
  # This can help installing the suite in a relocatable way.
  
  prefix = $(HOME)
 -bindir_relative = bin
 -bindir = $(prefix)/$(bindir_relative)
 +bindir = $(prefix)/bin
  mandir = $(prefix)/share/man
  infodir = $(prefix)/share/info
  gitexecdir = libexec/git-core
  mergetoolsdir = $(gitexecdir)/mergetools
  sharedir = $(prefix)/share
  gitwebdir = $(sharedir)/gitweb
 +perllibdir = $(sharedir)/perl5
  localedir = $(sharedir)/locale
  template_dir = share/git-core/templates
  htmldir = $(prefix)/share/doc/git-doc
@@@ -498,13 -485,11 +498,13 @@@ lib = li
  # DESTDIR =
  pathsep = :
  
 +bindir_relative = $(patsubst $(prefix)/%,%,$(bindir))
  mandir_relative = $(patsubst $(prefix)/%,%,$(mandir))
  infodir_relative = $(patsubst $(prefix)/%,%,$(infodir))
 +gitexecdir_relative = $(patsubst $(prefix)/%,%,$(gitexecdir))
  htmldir_relative = $(patsubst $(prefix)/%,%,$(htmldir))
  
 -export prefix bindir sharedir sysconfdir gitwebdir localedir
 +export prefix bindir sharedir sysconfdir gitwebdir perllibdir localedir
  
  CC = cc
  AR = ar
@@@ -651,7 -636,6 +651,6 @@@ PROGRAM_OBJS += imap-send.
  PROGRAM_OBJS += sh-i18n--envsubst.o
  PROGRAM_OBJS += shell.o
  PROGRAM_OBJS += show-index.o
- PROGRAM_OBJS += upload-pack.o
  PROGRAM_OBJS += remote-testsvn.o
  
  # Binary suffix, set to .exe for Windows builds
@@@ -682,6 -666,7 +681,7 @@@ TEST_PROGRAMS_NEED_X += test-mktem
  TEST_PROGRAMS_NEED_X += test-online-cpus
  TEST_PROGRAMS_NEED_X += test-parse-options
  TEST_PROGRAMS_NEED_X += test-path-utils
+ TEST_PROGRAMS_NEED_X += test-pkt-line
  TEST_PROGRAMS_NEED_X += test-prio-queue
  TEST_PROGRAMS_NEED_X += test-read-cache
  TEST_PROGRAMS_NEED_X += test-write-cache
@@@ -748,8 -733,6 +748,8 @@@ endi
  export PERL_PATH
  export PYTHON_PATH
  
 +TEST_SHELL_PATH = $(SHELL_PATH)
 +
  LIB_FILE = libgit.a
  XDIFF_LIB = xdiff/lib.a
  VCSSVN_LIB = vcs-svn/lib.a
@@@ -816,7 -799,6 +816,7 @@@ LIB_OBJS += ewah/ewah_bitmap.
  LIB_OBJS += ewah/ewah_io.o
  LIB_OBJS += ewah/ewah_rlw.o
  LIB_OBJS += exec_cmd.o
 +LIB_OBJS += fetch-object.o
  LIB_OBJS += fetch-pack.o
  LIB_OBJS += fsck.o
  LIB_OBJS += fsmonitor.o
@@@ -838,6 -820,7 +838,7 @@@ LIB_OBJS += list-objects-filter-options
  LIB_OBJS += ll-merge.o
  LIB_OBJS += lockfile.o
  LIB_OBJS += log-tree.o
+ LIB_OBJS += ls-refs.o
  LIB_OBJS += mailinfo.o
  LIB_OBJS += mailmap.o
  LIB_OBJS += match-trees.o
@@@ -845,6 -828,7 +846,6 @@@ LIB_OBJS += merge.
  LIB_OBJS += merge-blobs.o
  LIB_OBJS += merge-recursive.o
  LIB_OBJS += mergesort.o
 -LIB_OBJS += mru.o
  LIB_OBJS += name-hash.o
  LIB_OBJS += notes.o
  LIB_OBJS += notes-cache.o
@@@ -893,6 -877,7 +894,7 @@@ LIB_OBJS += revision.
  LIB_OBJS += run-command.o
  LIB_OBJS += send-pack.o
  LIB_OBJS += sequencer.o
+ LIB_OBJS += serve.o
  LIB_OBJS += server-info.o
  LIB_OBJS += setup.o
  LIB_OBJS += sha1-array.o
@@@ -921,6 -906,7 +923,7 @@@ LIB_OBJS += tree-diff.
  LIB_OBJS += tree.o
  LIB_OBJS += tree-walk.o
  LIB_OBJS += unpack-trees.o
+ LIB_OBJS += upload-pack.o
  LIB_OBJS += url.o
  LIB_OBJS += urlmatch.o
  LIB_OBJS += usage.o
@@@ -1025,6 -1011,7 +1028,7 @@@ BUILTIN_OBJS += builtin/rev-parse.
  BUILTIN_OBJS += builtin/revert.o
  BUILTIN_OBJS += builtin/rm.o
  BUILTIN_OBJS += builtin/send-pack.o
+ BUILTIN_OBJS += builtin/serve.o
  BUILTIN_OBJS += builtin/shortlog.o
  BUILTIN_OBJS += builtin/show-branch.o
  BUILTIN_OBJS += builtin/show-ref.o
@@@ -1038,6 -1025,7 +1042,7 @@@ BUILTIN_OBJS += builtin/update-index.
  BUILTIN_OBJS += builtin/update-ref.o
  BUILTIN_OBJS += builtin/update-server-info.o
  BUILTIN_OBJS += builtin/upload-archive.o
+ BUILTIN_OBJS += builtin/upload-pack.o
  BUILTIN_OBJS += builtin/var.o
  BUILTIN_OBJS += builtin/verify-commit.o
  BUILTIN_OBJS += builtin/verify-pack.o
@@@ -1178,18 -1166,13 +1183,18 @@@ ifdef NO_LIBGEN_
        COMPAT_OBJS += compat/basename.o
  endif
  
 -USE_LIBPCRE1 ?= $(USE_LIBPCRE)
 +USE_LIBPCRE2 ?= $(USE_LIBPCRE)
  
 -ifneq (,$(USE_LIBPCRE1))
 -      ifdef USE_LIBPCRE2
 -$(error Only set USE_LIBPCRE1 (or its alias USE_LIBPCRE) or USE_LIBPCRE2, not both!)
 +ifneq (,$(USE_LIBPCRE2))
 +      ifdef USE_LIBPCRE1
 +$(error Only set USE_LIBPCRE2 (or its alias USE_LIBPCRE) or USE_LIBPCRE1, not both!)
        endif
  
 +      BASIC_CFLAGS += -DUSE_LIBPCRE2
 +      EXTLIBS += -lpcre2-8
 +endif
 +
 +ifdef USE_LIBPCRE1
        BASIC_CFLAGS += -DUSE_LIBPCRE1
        EXTLIBS += -lpcre
  
@@@ -1198,6 -1181,11 +1203,6 @@@ ifdef NO_LIBPCRE1_JI
  endif
  endif
  
 -ifdef USE_LIBPCRE2
 -      BASIC_CFLAGS += -DUSE_LIBPCRE2
 -      EXTLIBS += -lpcre2-8
 -endif
 -
  ifdef LIBPCREDIR
        BASIC_CFLAGS += -I$(LIBPCREDIR)/include
        EXTLIBS += -L$(LIBPCREDIR)/$(lib) $(CC_LD_DYNPATH)$(LIBPCREDIR)/$(lib)
@@@ -1286,6 -1274,7 +1291,6 @@@ ifndef NO_OPENSS
        endif
  else
        BASIC_CFLAGS += -DNO_OPENSSL
 -      BLK_SHA1 = 1
        OPENSSL_LIBSSL =
  endif
  ifdef NO_OPENSSL
@@@ -1527,9 -1516,7 +1532,9 @@@ els
        LIB_OBJS += sha1dc_git.o
  ifdef DC_SHA1_EXTERNAL
        ifdef DC_SHA1_SUBMODULE
 +              ifneq ($(DC_SHA1_SUBMODULE),auto)
  $(error Only set DC_SHA1_EXTERNAL or DC_SHA1_SUBMODULE, not both)
 +              endif
        endif
        BASIC_CFLAGS += -DDC_SHA1_EXTERNAL
        EXTLIBS += -lsha1detectcoll
@@@ -1557,6 -1544,9 +1562,6 @@@ ifdef SHA1_MAX_BLOCK_SIZ
        LIB_OBJS += compat/sha1-chunked.o
        BASIC_CFLAGS += -DSHA1_MAX_BLOCK_SIZE="$(SHA1_MAX_BLOCK_SIZE)"
  endif
 -ifdef NO_PERL_MAKEMAKER
 -      export NO_PERL_MAKEMAKER
 -endif
  ifdef NO_HSTRERROR
        COMPAT_CFLAGS += -DNO_HSTRERROR
        COMPAT_OBJS += compat/hstrerror.o
@@@ -1743,20 -1733,16 +1748,20 @@@ ETC_GITATTRIBUTES_SQ = $(subst ','\'',$
  DESTDIR_SQ = $(subst ','\'',$(DESTDIR))
  bindir_SQ = $(subst ','\'',$(bindir))
  bindir_relative_SQ = $(subst ','\'',$(bindir_relative))
 +mandir_SQ = $(subst ','\'',$(mandir))
  mandir_relative_SQ = $(subst ','\'',$(mandir_relative))
  infodir_relative_SQ = $(subst ','\'',$(infodir_relative))
 +perllibdir_SQ = $(subst ','\'',$(perllibdir))
  localedir_SQ = $(subst ','\'',$(localedir))
  gitexecdir_SQ = $(subst ','\'',$(gitexecdir))
 +gitexecdir_relative_SQ = $(subst ','\'',$(gitexecdir_relative))
  template_dir_SQ = $(subst ','\'',$(template_dir))
  htmldir_relative_SQ = $(subst ','\'',$(htmldir_relative))
  prefix_SQ = $(subst ','\'',$(prefix))
  gitwebdir_SQ = $(subst ','\'',$(gitwebdir))
  
  SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
 +TEST_SHELL_PATH_SQ = $(subst ','\'',$(TEST_SHELL_PATH))
  PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
  PYTHON_PATH_SQ = $(subst ','\'',$(PYTHON_PATH))
  TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH))
@@@ -1857,6 -1843,9 +1862,6 @@@ all:
  ifndef NO_TCLTK
        $(QUIET_SUBDIR0)git-gui $(QUIET_SUBDIR1) gitexecdir='$(gitexec_instdir_SQ)' all
        $(QUIET_SUBDIR0)gitk-git $(QUIET_SUBDIR1) all
 -endif
 -ifndef NO_PERL
 -      $(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' localedir='$(localedir_SQ)' all
  endif
        $(QUIET_SUBDIR0)templates $(QUIET_SUBDIR1) SHELL_PATH='$(SHELL_PATH_SQ)' PERL_PATH='$(PERL_PATH_SQ)'
  
@@@ -1939,8 -1928,7 +1944,8 @@@ common-cmds.h: $(wildcard Documentation
  
  SCRIPT_DEFINES = $(SHELL_PATH_SQ):$(DIFF_SQ):$(GIT_VERSION):\
        $(localedir_SQ):$(NO_CURL):$(USE_GETTEXT_SCHEME):$(SANE_TOOL_PATH_SQ):\
 -      $(gitwebdir_SQ):$(PERL_PATH_SQ):$(SANE_TEXT_GREP):$(PAGER_ENV)
 +      $(gitwebdir_SQ):$(PERL_PATH_SQ):$(SANE_TEXT_GREP):$(PAGER_ENV):\
 +      $(perllibdir_SQ)
  define cmd_munge_script
  $(RM) $@ $@+ && \
  sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
@@@ -1984,12 -1972,23 +1989,12 @@@ git.res: git.rc GIT-VERSION-FIL
  $(SCRIPT_PERL_GEN): GIT-BUILD-OPTIONS
  
  ifndef NO_PERL
 -$(SCRIPT_PERL_GEN): perl/perl.mak
 +$(SCRIPT_PERL_GEN):
  
 -perl/perl.mak: perl/PM.stamp
 -
 -perl/PM.stamp: FORCE
 -      @$(FIND) perl -type f -name '*.pm' | sort >$@+ && \
 -      $(PERL_PATH) -V >>$@+ && \
 -      { cmp $@+ $@ >/dev/null 2>/dev/null || mv $@+ $@; } && \
 -      $(RM) $@+
 -
 -perl/perl.mak: GIT-CFLAGS GIT-PREFIX perl/Makefile perl/Makefile.PL
 -      $(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' $(@F)
 -
 -PERL_DEFINES = $(PERL_PATH_SQ):$(PERLLIB_EXTRA_SQ)
 -$(SCRIPT_PERL_GEN): % : %.perl perl/perl.mak GIT-PERL-DEFINES GIT-VERSION-FILE
 +PERL_DEFINES = $(PERL_PATH_SQ):$(PERLLIB_EXTRA_SQ):$(perllibdir_SQ)
 +$(SCRIPT_PERL_GEN): % : %.perl GIT-PERL-DEFINES GIT-VERSION-FILE
        $(QUIET_GEN)$(RM) $@ $@+ && \
 -      INSTLIBDIR=`MAKEFLAGS= $(MAKE) -C perl -s --no-print-directory instlibdir` && \
 +      INSTLIBDIR='$(perllibdir_SQ)' && \
        INSTLIBDIR_EXTRA='$(PERLLIB_EXTRA_SQ)' && \
        INSTLIBDIR="$$INSTLIBDIR$${INSTLIBDIR_EXTRA:+:$$INSTLIBDIR_EXTRA}" && \
        sed -e '1{' \
@@@ -2177,8 -2176,6 +2182,8 @@@ gettext.sp gettext.s gettext.o: EXTRA_C
  http-push.sp http.sp http-walker.sp remote-curl.sp imap-send.sp: SPARSE_FLAGS += \
        -DCURL_DISABLE_TYPECHECK
  
 +pack-revindex.sp: SPARSE_FLAGS += -Wno-memcpy-max-count
 +
  ifdef NO_EXPAT
  http-walker.sp http-walker.s http-walker.o: EXTRA_CPPFLAGS = -DNO_EXPAT
  endif
@@@ -2233,15 -2230,13 +2238,15 @@@ $(VCSSVN_LIB): $(VCSSVN_OBJS
  
  export DEFAULT_EDITOR DEFAULT_PAGER
  
 -.PHONY: doc man html info pdf
 -doc:
 +.PHONY: doc man man-perl html info pdf
 +doc: man-perl
        $(MAKE) -C Documentation all
  
 -man:
 +man: man-perl
        $(MAKE) -C Documentation man
  
 +man-perl: perl/build/man/man3/Git.3pm
 +
  html:
        $(MAKE) -C Documentation html
  
@@@ -2317,29 -2312,6 +2322,29 @@@ endi
  po/build/locale/%/LC_MESSAGES/git.mo: po/%.po
        $(QUIET_MSGFMT)mkdir -p $(dir $@) && $(MSGFMT) -o $@ $<
  
 +LIB_PERL := $(wildcard perl/Git.pm perl/Git/*.pm perl/Git/*/*.pm perl/Git/*/*/*.pm)
 +LIB_PERL_GEN := $(patsubst perl/%.pm,perl/build/lib/%.pm,$(LIB_PERL))
 +LIB_CPAN := $(wildcard perl/FromCPAN/*.pm perl/FromCPAN/*/*.pm)
 +LIB_CPAN_GEN := $(patsubst perl/%.pm,perl/build/lib/%.pm,$(LIB_CPAN))
 +
 +ifndef NO_PERL
 +all:: $(LIB_PERL_GEN)
 +ifndef NO_PERL_CPAN_FALLBACKS
 +all:: $(LIB_CPAN_GEN)
 +endif
 +NO_PERL_CPAN_FALLBACKS_SQ = $(subst ','\'',$(NO_PERL_CPAN_FALLBACKS))
 +endif
 +
 +perl/build/lib/%.pm: perl/%.pm
 +      $(QUIET_GEN)mkdir -p $(dir $@) && \
 +      sed -e 's|@@LOCALEDIR@@|$(localedir_SQ)|g' \
 +          -e 's|@@NO_PERL_CPAN_FALLBACKS@@|$(NO_PERL_CPAN_FALLBACKS_SQ)|g' \
 +      < $< > $@
 +
 +perl/build/man/man3/Git.3pm: perl/Git.pm
 +      $(QUIET_GEN)mkdir -p $(dir $@) && \
 +      pod2man $< $@
 +
  FIND_SOURCE_FILES = ( \
        git ls-files \
                '*.[hcS]' \
@@@ -2403,7 -2375,6 +2408,7 @@@ GIT-LDFLAGS: FORC
  # and the first level quoting from the shell that runs "echo".
  GIT-BUILD-OPTIONS: FORCE
        @echo SHELL_PATH=\''$(subst ','\'',$(SHELL_PATH_SQ))'\' >$@+
 +      @echo TEST_SHELL_PATH=\''$(subst ','\'',$(TEST_SHELL_PATH_SQ))'\' >>$@+
        @echo PERL_PATH=\''$(subst ','\'',$(PERL_PATH_SQ))'\' >>$@+
        @echo DIFF=\''$(subst ','\'',$(subst ','\'',$(DIFF)))'\' >>$@+
        @echo PYTHON_PATH=\''$(subst ','\'',$(PYTHON_PATH_SQ))'\' >>$@+
@@@ -2600,9 -2571,7 +2605,9 @@@ ifndef NO_GETTEX
        (cd '$(DESTDIR_SQ)$(localedir_SQ)' && umask 022 && $(TAR) xof -)
  endif
  ifndef NO_PERL
 -      $(MAKE) -C perl prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install
 +      $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(perllibdir_SQ)'
 +      (cd perl/build/lib && $(TAR) cf - .) | \
 +      (cd '$(DESTDIR_SQ)$(perllibdir_SQ)' && umask 022 && $(TAR) xof -)
        $(MAKE) -C gitweb install
  endif
  ifndef NO_TCLTK
@@@ -2615,63 -2584,49 +2620,63 @@@ endi
  
        bindir=$$(cd '$(DESTDIR_SQ)$(bindir_SQ)' && pwd) && \
        execdir=$$(cd '$(DESTDIR_SQ)$(gitexec_instdir_SQ)' && pwd) && \
 +      destdir_from_execdir_SQ=$$(echo '$(gitexecdir_relative_SQ)' | sed -e 's|[^/][^/]*|..|g') && \
        { test "$$bindir/" = "$$execdir/" || \
          for p in git$X $(filter $(install_bindir_programs),$(ALL_PROGRAMS)); do \
                $(RM) "$$execdir/$$p" && \
 -              test -z "$(NO_INSTALL_HARDLINKS)$(NO_CROSS_DIRECTORY_HARDLINKS)" && \
 -              ln "$$bindir/$$p" "$$execdir/$$p" 2>/dev/null || \
 -              cp "$$bindir/$$p" "$$execdir/$$p" || exit; \
 +              test -n "$(INSTALL_SYMLINKS)" && \
 +              ln -s "$$destdir_from_execdir_SQ/$(bindir_relative_SQ)/$$p" "$$execdir/$$p" || \
 +              { test -z "$(NO_INSTALL_HARDLINKS)$(NO_CROSS_DIRECTORY_HARDLINKS)" && \
 +                ln "$$bindir/$$p" "$$execdir/$$p" 2>/dev/null || \
 +                cp "$$bindir/$$p" "$$execdir/$$p" || exit; } \
          done; \
        } && \
        for p in $(filter $(install_bindir_programs),$(BUILT_INS)); do \
                $(RM) "$$bindir/$$p" && \
 -              test -z "$(NO_INSTALL_HARDLINKS)" && \
 -              ln "$$bindir/git$X" "$$bindir/$$p" 2>/dev/null || \
 -              ln -s "git$X" "$$bindir/$$p" 2>/dev/null || \
 -              cp "$$bindir/git$X" "$$bindir/$$p" || exit; \
 +              test -n "$(INSTALL_SYMLINKS)" && \
 +              ln -s "git$X" "$$bindir/$$p" || \
 +              { test -z "$(NO_INSTALL_HARDLINKS)" && \
 +                ln "$$bindir/git$X" "$$bindir/$$p" 2>/dev/null || \
 +                ln -s "git$X" "$$bindir/$$p" 2>/dev/null || \
 +                cp "$$bindir/git$X" "$$bindir/$$p" || exit; } \
        done && \
        for p in $(BUILT_INS); do \
                $(RM) "$$execdir/$$p" && \
 -              test -z "$(NO_INSTALL_HARDLINKS)" && \
 -              ln "$$execdir/git$X" "$$execdir/$$p" 2>/dev/null || \
 -              ln -s "git$X" "$$execdir/$$p" 2>/dev/null || \
 -              cp "$$execdir/git$X" "$$execdir/$$p" || exit; \
 +              test -n "$(INSTALL_SYMLINKS)" && \
 +              ln -s "$$destdir_from_execdir_SQ/$(bindir_relative_SQ)/git$X" "$$execdir/$$p" || \
 +              { test -z "$(NO_INSTALL_HARDLINKS)" && \
 +                ln "$$execdir/git$X" "$$execdir/$$p" 2>/dev/null || \
 +                ln -s "git$X" "$$execdir/$$p" 2>/dev/null || \
 +                cp "$$execdir/git$X" "$$execdir/$$p" || exit; } \
        done && \
        remote_curl_aliases="$(REMOTE_CURL_ALIASES)" && \
        for p in $$remote_curl_aliases; do \
                $(RM) "$$execdir/$$p" && \
 -              test -z "$(NO_INSTALL_HARDLINKS)" && \
 -              ln "$$execdir/git-remote-http$X" "$$execdir/$$p" 2>/dev/null || \
 -              ln -s "git-remote-http$X" "$$execdir/$$p" 2>/dev/null || \
 -              cp "$$execdir/git-remote-http$X" "$$execdir/$$p" || exit; \
 +              test -n "$(INSTALL_SYMLINKS)" && \
 +              ln -s "git-remote-http$X" "$$execdir/$$p" || \
 +              { test -z "$(NO_INSTALL_HARDLINKS)" && \
 +                ln "$$execdir/git-remote-http$X" "$$execdir/$$p" 2>/dev/null || \
 +                ln -s "git-remote-http$X" "$$execdir/$$p" 2>/dev/null || \
 +                cp "$$execdir/git-remote-http$X" "$$execdir/$$p" || exit; } \
        done && \
        ./check_bindir "z$$bindir" "z$$execdir" "$$bindir/git-add$X"
  
 -.PHONY: install-gitweb install-doc install-man install-html install-info install-pdf
 +.PHONY: install-gitweb install-doc install-man install-man-perl install-html install-info install-pdf
  .PHONY: quick-install-doc quick-install-man quick-install-html
  install-gitweb:
        $(MAKE) -C gitweb install
  
 -install-doc:
 +install-doc: install-man-perl
        $(MAKE) -C Documentation install
  
 -install-man:
 +install-man: install-man-perl
        $(MAKE) -C Documentation install-man
  
 +install-man-perl: man-perl
 +      $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(mandir_SQ)/man3'
 +      (cd perl/build/man/man3 && $(TAR) cf - .) | \
 +      (cd '$(DESTDIR_SQ)$(mandir_SQ)/man3' && umask 022 && $(TAR) xof -)
 +
  install-html:
        $(MAKE) -C Documentation install-html
  
@@@ -2706,21 -2661,6 +2711,21 @@@ dist: git-archive$(X) configur
                $(GIT_TARNAME)/configure \
                $(GIT_TARNAME)/version \
                $(GIT_TARNAME)/git-gui/version
 +ifdef DC_SHA1_SUBMODULE
 +      @mkdir -p $(GIT_TARNAME)/sha1collisiondetection/lib
 +      @cp sha1collisiondetection/LICENSE.txt \
 +              $(GIT_TARNAME)/sha1collisiondetection/
 +      @cp sha1collisiondetection/LICENSE.txt \
 +              $(GIT_TARNAME)/sha1collisiondetection/
 +      @cp sha1collisiondetection/lib/sha1.[ch] \
 +              $(GIT_TARNAME)/sha1collisiondetection/lib/
 +      @cp sha1collisiondetection/lib/ubc_check.[ch] \
 +              $(GIT_TARNAME)/sha1collisiondetection/lib/
 +      $(TAR) rf $(GIT_TARNAME).tar \
 +              $(GIT_TARNAME)/sha1collisiondetection/LICENSE.txt \
 +              $(GIT_TARNAME)/sha1collisiondetection/lib/sha1.[ch] \
 +              $(GIT_TARNAME)/sha1collisiondetection/lib/ubc_check.[ch]
 +endif
        @$(RM) -r $(GIT_TARNAME)
        gzip -f -9 $(GIT_TARNAME).tar
  
@@@ -2770,7 -2710,7 +2775,7 @@@ clean: profile-clean coverage-clea
        $(RM) $(TEST_PROGRAMS) $(NO_INSTALL)
        $(RM) -r bin-wrappers $(dep_dirs)
        $(RM) -r po/build/
 -      $(RM) *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h $(ETAGS_TARGET) tags cscope*
 +      $(RM) *.pyc *.pyo */*.pyc */*.pyo common-cmds.h $(ETAGS_TARGET) tags cscope*
        $(RM) -r $(GIT_TARNAME) .doc-tmp-dir
        $(RM) $(GIT_TARNAME).tar.gz git-core_$(GIT_VERSION)-*.tar.gz
        $(RM) $(htmldocs).tar.gz $(manpages).tar.gz
        $(MAKE) -C Documentation/ clean
  ifndef NO_PERL
        $(MAKE) -C gitweb clean
 -      $(MAKE) -C perl clean
 +      $(RM) -r perl/build/
  endif
        $(MAKE) -C templates/ clean
        $(MAKE) -C t/ clean
diff --combined builtin/clone.c
index 101c27a593f4c64a735410f18bfcb46489728696,4db3079acc03a2da3bf528ed545bbf465abad767..d4dd3b8f7337b65cf45706e3419040ba2a1c4dd5
@@@ -26,7 -26,6 +26,7 @@@
  #include "run-command.h"
  #include "connected.h"
  #include "packfile.h"
 +#include "list-objects-filter-options.h"
  
  /*
   * Overall FIXMEs:
@@@ -61,7 -60,6 +61,7 @@@ static struct string_list option_option
  static int option_dissociate;
  static int max_jobs = -1;
  static struct string_list option_recurse_submodules = STRING_LIST_INIT_NODUP;
 +static struct list_objects_filter_options filter_options;
  
  static int recurse_submodules_cb(const struct option *opt,
                                 const char *arg, int unset)
@@@ -137,7 -135,6 +137,7 @@@ static struct option builtin_clone_opti
                        TRANSPORT_FAMILY_IPV4),
        OPT_SET_INT('6', "ipv6", &family, N_("use IPv6 addresses only"),
                        TRANSPORT_FAMILY_IPV6),
 +      OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options),
        OPT_END()
  };
  
@@@ -476,9 -473,7 +476,9 @@@ static void clone_local(const char *src
  }
  
  static const char *junk_work_tree;
 +static int junk_work_tree_flags;
  static const char *junk_git_dir;
 +static int junk_git_dir_flags;
  static enum {
        JUNK_LEAVE_NONE,
        JUNK_LEAVE_REPO,
@@@ -507,12 -502,12 +507,12 @@@ static void remove_junk(void
  
        if (junk_git_dir) {
                strbuf_addstr(&sb, junk_git_dir);
 -              remove_dir_recursively(&sb, 0);
 +              remove_dir_recursively(&sb, junk_git_dir_flags);
                strbuf_reset(&sb);
        }
        if (junk_work_tree) {
                strbuf_addstr(&sb, junk_work_tree);
 -              remove_dir_recursively(&sb, 0);
 +              remove_dir_recursively(&sb, junk_work_tree_flags);
        }
        strbuf_release(&sb);
  }
@@@ -868,15 -863,10 +868,15 @@@ static void dissociate_from_references(
        free(alternates);
  }
  
 +static int dir_exists(const char *path)
 +{
 +      struct stat sb;
 +      return !stat(path, &sb);
 +}
 +
  int cmd_clone(int argc, const char **argv, const char *prefix)
  {
        int is_bundle = 0, is_local;
 -      struct stat buf;
        const char *repo_name, *repo, *work_tree, *git_dir;
        char *path, *dir;
        int dest_exists;
        struct refspec *refspec;
        const char *fetch_pattern;
  
 +      fetch_if_missing = 0;
 +
        packet_trace_identity("clone");
        argc = parse_options(argc, argv, prefix, builtin_clone_options,
                             builtin_clone_usage, 0);
                dir = guess_dir_name(repo_name, is_bundle, option_bare);
        strip_trailing_slashes(dir);
  
 -      dest_exists = !stat(dir, &buf);
 +      dest_exists = dir_exists(dir);
        if (dest_exists && !is_empty_dir(dir))
                die(_("destination path '%s' already exists and is not "
                        "an empty directory."), dir);
                work_tree = NULL;
        else {
                work_tree = getenv("GIT_WORK_TREE");
 -              if (work_tree && !stat(work_tree, &buf))
 +              if (work_tree && dir_exists(work_tree))
                        die(_("working tree '%s' already exists."), work_tree);
        }
  
                if (safe_create_leading_directories_const(work_tree) < 0)
                        die_errno(_("could not create leading directories of '%s'"),
                                  work_tree);
 -              if (!dest_exists && mkdir(work_tree, 0777))
 +              if (dest_exists)
 +                      junk_work_tree_flags |= REMOVE_DIR_KEEP_TOPLEVEL;
 +              else if (mkdir(work_tree, 0777))
                        die_errno(_("could not create work tree dir '%s'"),
                                  work_tree);
                junk_work_tree = work_tree;
                set_git_work_tree(work_tree);
        }
  
 -      junk_git_dir = real_git_dir ? real_git_dir : git_dir;
 +      if (real_git_dir) {
 +              if (dir_exists(real_git_dir))
 +                      junk_git_dir_flags |= REMOVE_DIR_KEEP_TOPLEVEL;
 +              junk_git_dir = real_git_dir;
 +      } else {
 +              if (dest_exists)
 +                      junk_git_dir_flags |= REMOVE_DIR_KEEP_TOPLEVEL;
 +              junk_git_dir = git_dir;
 +      }
        if (safe_create_leading_directories_const(git_dir) < 0)
                die(_("could not create leading directories of '%s'"), git_dir);
  
                        warning(_("--shallow-since is ignored in local clones; use file:// instead."));
                if (option_not.nr)
                        warning(_("--shallow-exclude is ignored in local clones; use file:// instead."));
 +              if (filter_options.choice)
 +                      warning(_("--filter is ignored in local clones; use file:// instead."));
                if (!access(mkpath("%s/shallow", path), F_OK)) {
                        if (option_local > 0)
                                warning(_("source repository is shallow, ignoring --local"));
                transport_set_option(transport, TRANS_OPT_UPLOADPACK,
                                     option_upload_pack);
  
 -      if (transport->smart_options && !deepen)
 +      if (filter_options.choice) {
 +              transport_set_option(transport, TRANS_OPT_LIST_OBJECTS_FILTER,
 +                                   filter_options.filter_spec);
 +              transport_set_option(transport, TRANS_OPT_FROM_PROMISOR, "1");
 +      }
 +
 +      if (transport->smart_options && !deepen && !filter_options.choice)
                transport->smart_options->check_self_contained_and_connected = 1;
  
-       refs = transport_get_remote_refs(transport);
+       refs = transport_get_remote_refs(transport, NULL);
  
        if (refs) {
                mapped_refs = wanted_peer_refs(refs, refspec);
        write_refspec_config(src_ref_prefix, our_head_points_at,
                        remote_head_points_at, &branch_top);
  
 +      if (filter_options.choice)
 +              partial_clone_register("origin", &filter_options);
 +
        if (is_local)
                clone_local(path, git_dir);
        else if (refs && complete_refs_before_fetch)
                transport_fetch_refs(transport, mapped_refs);
  
        update_remote_refs(refs, mapped_refs, remote_head_points_at,
 -                         branch_top.buf, reflog_msg.buf, transport, !is_local);
 +                         branch_top.buf, reflog_msg.buf, transport,
 +                         !is_local && !filter_options.choice);
  
        update_head(our_head_points_at, remote_head, reflog_msg.buf);
  
        }
  
        junk_mode = JUNK_LEAVE_REPO;
 +      fetch_if_missing = 1;
        err = checkout(submodule_progress);
  
        strbuf_release(&reflog_msg);
diff --combined builtin/fetch-pack.c
index a7bc1366ab375765c41014640743ef9d77c84c42,f9d7d0b5a519a94317326e0b1baa562aade639a2..1a1bc63566b44bc83c8429463104615d1b2117ff
@@@ -4,6 -4,7 +4,7 @@@
  #include "remote.h"
  #include "connect.h"
  #include "sha1-array.h"
+ #include "protocol.h"
  
  static const char fetch_pack_usage[] =
  "git fetch-pack [--all] [--stdin] [--quiet | -q] [--keep | -k] [--thin] "
@@@ -52,9 -53,8 +53,10 @@@ int cmd_fetch_pack(int argc, const cha
        struct fetch_pack_args args;
        struct oid_array shallow = OID_ARRAY_INIT;
        struct string_list deepen_not = STRING_LIST_INIT_DUP;
+       struct packet_reader reader;
  
 +      fetch_if_missing = 0;
 +
        packet_trace_identity("fetch-pack");
  
        memset(&args, 0, sizeof(args));
                        args.update_shallow = 1;
                        continue;
                }
 +              if (!strcmp("--from-promisor", arg)) {
 +                      args.from_promisor = 1;
 +                      continue;
 +              }
 +              if (!strcmp("--no-dependents", arg)) {
 +                      args.no_dependents = 1;
 +                      continue;
 +              }
 +              if (skip_prefix(arg, ("--" CL_ARG__FILTER "="), &arg)) {
 +                      parse_list_objects_filter(&args.filter_options, arg);
 +                      continue;
 +              }
 +              if (!strcmp(arg, ("--no-" CL_ARG__FILTER))) {
 +                      list_objects_filter_set_no_filter(&args.filter_options);
 +                      continue;
 +              }
                usage(fetch_pack_usage);
        }
        if (deepen_not.nr)
                if (!conn)
                        return args.diag_url ? 0 : 1;
        }
-       get_remote_heads(fd[0], NULL, 0, &ref, 0, NULL, &shallow);
+       packet_reader_init(&reader, fd[0], NULL, 0,
+                          PACKET_READ_CHOMP_NEWLINE |
+                          PACKET_READ_GENTLE_ON_EOF);
+       switch (discover_version(&reader)) {
+       case protocol_v2:
+               die("support for protocol v2 not implemented yet");
+       case protocol_v1:
+       case protocol_v0:
+               get_remote_heads(&reader, &ref, 0, NULL, &shallow);
+               break;
+       case protocol_unknown_version:
+               BUG("unknown protocol version");
+       }
  
        ref = fetch_pack(&args, fd, conn, ref, dest, sought, nr_sought,
-                        &shallow, pack_lockfile_ptr);
+                        &shallow, pack_lockfile_ptr, protocol_v0);
        if (pack_lockfile) {
                printf("lock %s\n", pack_lockfile);
                fflush(stdout);
diff --combined builtin/fetch.c
index 8295f92b3e63d2675b8238d6bc17e9b1fc81319a,8258bbf9501184eb592c6ea04e44c9f4cac5ef14..1613dc8bc1ad7ea27139da46844d7f4ffdc27199
@@@ -19,7 -19,6 +19,7 @@@
  #include "argv-array.h"
  #include "utf8.h"
  #include "packfile.h"
 +#include "list-objects-filter-options.h"
  
  static const char * const builtin_fetch_usage[] = {
        N_("git fetch [<options>] [<repository> [<refspec>...]]"),
@@@ -39,10 -38,6 +39,10 @@@ static int fetch_prune_config = -1; /* 
  static int prune = -1; /* unspecified */
  #define PRUNE_BY_DEFAULT 0 /* do we prune by default? */
  
 +static int fetch_prune_tags_config = -1; /* unspecified */
 +static int prune_tags = -1; /* unspecified */
 +#define PRUNE_TAGS_BY_DEFAULT 0 /* do we prune tags by default? */
 +
  static int all, append, dry_run, force, keep, multiple, update_head_ok, verbosity, deepen_relative;
  static int progress = -1;
  static int tags = TAGS_DEFAULT, unshallow, update_shallow, deepen;
@@@ -61,7 -56,6 +61,7 @@@ static int recurse_submodules_default 
  static int shown_url = 0;
  static int refmap_alloc, refmap_nr;
  static const char **refmap_array;
 +static struct list_objects_filter_options filter_options;
  
  static int git_fetch_config(const char *k, const char *v, void *cb)
  {
                return 0;
        }
  
 +      if (!strcmp(k, "fetch.prunetags")) {
 +              fetch_prune_tags_config = git_config_bool(k, v);
 +              return 0;
 +      }
 +
        if (!strcmp(k, "submodule.recurse")) {
                int r = git_config_bool(k, v) ?
                        RECURSE_SUBMODULES_ON : RECURSE_SUBMODULES_OFF;
@@@ -126,7 -115,7 +126,7 @@@ static struct option builtin_fetch_opti
                 N_("append to .git/FETCH_HEAD instead of overwriting")),
        OPT_STRING(0, "upload-pack", &upload_pack, N_("path"),
                   N_("path to upload pack on remote end")),
 -      OPT__FORCE(&force, N_("force overwrite of local branch")),
 +      OPT__FORCE(&force, N_("force overwrite of local branch"), 0),
        OPT_BOOL('m', "multiple", &multiple,
                 N_("fetch from multiple remotes")),
        OPT_SET_INT('t', "tags", &tags,
                    N_("number of submodules fetched in parallel")),
        OPT_BOOL('p', "prune", &prune,
                 N_("prune remote-tracking branches no longer on remote")),
 +      OPT_BOOL('P', "prune-tags", &prune_tags,
 +               N_("prune local tags no longer on remote and clobber changed tags")),
        { OPTION_CALLBACK, 0, "recurse-submodules", &recurse_submodules, N_("on-demand"),
                    N_("control recursive fetching of submodules"),
                    PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules },
                        TRANSPORT_FAMILY_IPV4),
        OPT_SET_INT('6', "ipv6", &family, N_("use IPv6 addresses only"),
                        TRANSPORT_FAMILY_IPV6),
 +      OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options),
        OPT_END()
  };
  
@@@ -264,7 -250,7 +264,7 @@@ static void find_non_local_tags(struct 
        struct string_list_item *item = NULL;
  
        for_each_ref(add_existing, &existing_refs);
-       for (ref = transport_get_remote_refs(transport); ref; ref = ref->next) {
+       for (ref = transport_get_remote_refs(transport, NULL); ref; ref = ref->next) {
                if (!starts_with(ref->name, "refs/tags/"))
                        continue;
  
@@@ -346,11 -332,28 +346,28 @@@ static struct ref *get_ref_map(struct t
        struct ref *rm;
        struct ref *ref_map = NULL;
        struct ref **tail = &ref_map;
+       struct argv_array ref_prefixes = ARGV_ARRAY_INIT;
  
        /* opportunistically-updated references: */
        struct ref *orefs = NULL, **oref_tail = &orefs;
  
-       const struct ref *remote_refs = transport_get_remote_refs(transport);
+       const struct ref *remote_refs;
+       for (i = 0; i < refspec_count; i++) {
+               if (!refspecs[i].exact_sha1) {
+                       const char *glob = strchr(refspecs[i].src, '*');
+                       if (glob)
+                               argv_array_pushf(&ref_prefixes, "%.*s",
+                                                (int)(glob - refspecs[i].src),
+                                                refspecs[i].src);
+                       else
+                               expand_ref_prefix(&ref_prefixes, refspecs[i].src);
+               }
+       }
+       remote_refs = transport_get_remote_refs(transport, &ref_prefixes);
+       argv_array_clear(&ref_prefixes);
  
        if (refspec_count) {
                struct refspec *fetch_refspec;
@@@ -637,7 -640,7 +654,7 @@@ static int update_local_ref(struct ref 
        struct branch *current_branch = branch_get(NULL);
        const char *pretty_ref = prettify_refname(ref->name);
  
 -      type = sha1_object_info(ref->new_oid.hash, NULL);
 +      type = oid_object_info(&ref->new_oid, NULL);
        if (type < 0)
                die(_("object %s not found"), oid_to_hex(&ref->new_oid));
  
        if (in_merge_bases(current, updated)) {
                struct strbuf quickref = STRBUF_INIT;
                int r;
 -              strbuf_add_unique_abbrev(&quickref, current->object.oid.hash, DEFAULT_ABBREV);
 +              strbuf_add_unique_abbrev(&quickref, &current->object.oid, DEFAULT_ABBREV);
                strbuf_addstr(&quickref, "..");
 -              strbuf_add_unique_abbrev(&quickref, ref->new_oid.hash, DEFAULT_ABBREV);
 +              strbuf_add_unique_abbrev(&quickref, &ref->new_oid, DEFAULT_ABBREV);
                if ((recurse_submodules != RECURSE_SUBMODULES_OFF) &&
                    (recurse_submodules != RECURSE_SUBMODULES_ON))
                        check_for_new_submodule_commits(&ref->new_oid);
        } else if (force || ref->force) {
                struct strbuf quickref = STRBUF_INIT;
                int r;
 -              strbuf_add_unique_abbrev(&quickref, current->object.oid.hash, DEFAULT_ABBREV);
 +              strbuf_add_unique_abbrev(&quickref, &current->object.oid, DEFAULT_ABBREV);
                strbuf_addstr(&quickref, "...");
 -              strbuf_add_unique_abbrev(&quickref, ref->new_oid.hash, DEFAULT_ABBREV);
 +              strbuf_add_unique_abbrev(&quickref, &ref->new_oid, DEFAULT_ABBREV);
                if ((recurse_submodules != RECURSE_SUBMODULES_OFF) &&
                    (recurse_submodules != RECURSE_SUBMODULES_ON))
                        check_for_new_submodule_commits(&ref->new_oid);
@@@ -1059,11 -1062,6 +1076,11 @@@ static struct transport *prepare_transp
                set_option(transport, TRANS_OPT_DEEPEN_RELATIVE, "yes");
        if (update_shallow)
                set_option(transport, TRANS_OPT_UPDATE_SHALLOW, "yes");
 +      if (filter_options.choice) {
 +              set_option(transport, TRANS_OPT_LIST_OBJECTS_FILTER,
 +                         filter_options.filter_spec);
 +              set_option(transport, TRANS_OPT_FROM_PROMISOR, "1");
 +      }
        return transport;
  }
  
@@@ -1231,8 -1229,6 +1248,8 @@@ static void add_options_to_argv(struct 
                argv_array_push(argv, "--dry-run");
        if (prune != -1)
                argv_array_push(argv, prune ? "--prune" : "--no-prune");
 +      if (prune_tags != -1)
 +              argv_array_push(argv, prune_tags ? "--prune-tags" : "--no-prune-tags");
        if (update_head_ok)
                argv_array_push(argv, "--update-head-ok");
        if (force)
@@@ -1286,65 -1282,12 +1303,65 @@@ static int fetch_multiple(struct string
        return result;
  }
  
 -static int fetch_one(struct remote *remote, int argc, const char **argv)
 +/*
 + * Fetching from the promisor remote should use the given filter-spec
 + * or inherit the default filter-spec from the config.
 + */
 +static inline void fetch_one_setup_partial(struct remote *remote)
 +{
 +      /*
 +       * Explicit --no-filter argument overrides everything, regardless
 +       * of any prior partial clones and fetches.
 +       */
 +      if (filter_options.no_filter)
 +              return;
 +
 +      /*
 +       * If no prior partial clone/fetch and the current fetch DID NOT
 +       * request a partial-fetch, do a normal fetch.
 +       */
 +      if (!repository_format_partial_clone && !filter_options.choice)
 +              return;
 +
 +      /*
 +       * If this is the FIRST partial-fetch request, we enable partial
 +       * on this repo and remember the given filter-spec as the default
 +       * for subsequent fetches to this remote.
 +       */
 +      if (!repository_format_partial_clone && filter_options.choice) {
 +              partial_clone_register(remote->name, &filter_options);
 +              return;
 +      }
 +
 +      /*
 +       * We are currently limited to only ONE promisor remote and only
 +       * allow partial-fetches from the promisor remote.
 +       */
 +      if (strcmp(remote->name, repository_format_partial_clone)) {
 +              if (filter_options.choice)
 +                      die(_("--filter can only be used with the remote configured in core.partialClone"));
 +              return;
 +      }
 +
 +      /*
 +       * Do a partial-fetch from the promisor remote using either the
 +       * explicitly given filter-spec or inherit the filter-spec from
 +       * the config.
 +       */
 +      if (!filter_options.choice)
 +              partial_clone_get_default_filter_spec(&filter_options);
 +      return;
 +}
 +
 +static int fetch_one(struct remote *remote, int argc, const char **argv, int prune_tags_ok)
  {
        static const char **refs = NULL;
        struct refspec *refspec;
        int ref_nr = 0;
 +      int j = 0;
        int exit_code;
 +      int maybe_prune_tags;
 +      int remote_via_config = remote_is_configured(remote, 0);
  
        if (!remote)
                die(_("No remote repository specified.  Please, specify either a URL or a\n"
  
        if (prune < 0) {
                /* no command line request */
 -              if (0 <= gtransport->remote->prune)
 -                      prune = gtransport->remote->prune;
 +              if (0 <= remote->prune)
 +                      prune = remote->prune;
                else if (0 <= fetch_prune_config)
                        prune = fetch_prune_config;
                else
                        prune = PRUNE_BY_DEFAULT;
        }
  
 +      if (prune_tags < 0) {
 +              /* no command line request */
 +              if (0 <= remote->prune_tags)
 +                      prune_tags = remote->prune_tags;
 +              else if (0 <= fetch_prune_tags_config)
 +                      prune_tags = fetch_prune_tags_config;
 +              else
 +                      prune_tags = PRUNE_TAGS_BY_DEFAULT;
 +      }
 +
 +      maybe_prune_tags = prune_tags_ok && prune_tags;
 +      if (maybe_prune_tags && remote_via_config)
 +              add_prune_tags_to_fetch_refspec(remote);
 +
 +      if (argc > 0 || (maybe_prune_tags && !remote_via_config)) {
 +              size_t nr_alloc = st_add3(argc, maybe_prune_tags, 1);
 +              refs = xcalloc(nr_alloc, sizeof(const char *));
 +              if (maybe_prune_tags) {
 +                      refs[j++] = xstrdup("refs/tags/*:refs/tags/*");
 +                      ref_nr++;
 +              }
 +      }
 +
        if (argc > 0) {
 -              int j = 0;
                int i;
 -              refs = xcalloc(st_add(argc, 1), sizeof(const char *));
                for (i = 0; i < argc; i++) {
                        if (!strcmp(argv[i], "tag")) {
                                i++;
                                                    argv[i], argv[i]);
                        } else
                                refs[j++] = argv[i];
 +                      ref_nr++;
                }
 -              refs[j] = NULL;
 -              ref_nr = j;
        }
  
        sigchain_push_common(unlock_pack_on_signal);
@@@ -1414,15 -1337,12 +1431,15 @@@ int cmd_fetch(int argc, const char **ar
  {
        int i;
        struct string_list list = STRING_LIST_INIT_DUP;
 -      struct remote *remote;
 +      struct remote *remote = NULL;
        int result = 0;
 +      int prune_tags_ok = 1;
        struct argv_array argv_gc_auto = ARGV_ARRAY_INIT;
  
        packet_trace_identity("fetch");
  
 +      fetch_if_missing = 0;
 +
        /* Record the command line for the reflog */
        strbuf_addstr(&default_rla, "fetch");
        for (i = 1; i < argc; i++)
        if (depth || deepen_since || deepen_not.nr)
                deepen = 1;
  
 +      if (filter_options.choice && !repository_format_partial_clone)
 +              die("--filter can only be used when extensions.partialClone is set");
 +
        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);
                        /* 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);
 +                      prune_tags_ok = (argc == 1);
 +                      argc--;
 +                      argv++;
                }
        }
  
 +      if (remote) {
 +              if (filter_options.choice || repository_format_partial_clone)
 +                      fetch_one_setup_partial(remote);
 +              result = fetch_one(remote, argc, argv, prune_tags_ok);
 +      } else {
 +              if (filter_options.choice)
 +                      die(_("--filter can only be used with the remote configured in core.partialClone"));
 +              /* TODO should this also die if we have a previous partial-clone? */
 +              result = fetch_multiple(&list);
 +      }
 +
        if (!result && (recurse_submodules != RECURSE_SUBMODULES_OFF)) {
                struct argv_array options = ARGV_ARRAY_INIT;
  
diff --combined builtin/ls-remote.c
index 540d56429f5cec4ace8655dd9a870089fc872d2c,4276bf97d5f2b12e29c3a888a7eae3f37bfc24ae..380c180270e263ed6701aa5a003ac999dfcfcec0
@@@ -2,6 -2,7 +2,7 @@@
  #include "cache.h"
  #include "transport.h"
  #include "remote.h"
+ #include "refs.h"
  
  static const char * const ls_remote_usage[] = {
        N_("git ls-remote [--heads] [--tags] [--refs] [--upload-pack=<exec>]\n"
@@@ -43,6 -44,7 +44,7 @@@ int cmd_ls_remote(int argc, const char 
        int show_symref_target = 0;
        const char *uploadpack = NULL;
        const char **pattern = NULL;
+       struct argv_array ref_prefixes = ARGV_ARRAY_INIT;
  
        struct remote *remote;
        struct transport *transport;
@@@ -60,9 -62,8 +62,9 @@@
                OPT_BIT(0, "refs", &flags, N_("do not show peeled tags"), REF_NORMAL),
                OPT_BOOL(0, "get-url", &get_url,
                         N_("take url.<base>.insteadOf into account")),
 -              OPT_SET_INT(0, "exit-code", &status,
 -                          N_("exit with exit code 2 if no matching refs are found"), 2),
 +              OPT_SET_INT_F(0, "exit-code", &status,
 +                            N_("exit with exit code 2 if no matching refs are found"),
 +                            2, PARSE_OPT_NOCOMPLETE),
                OPT_BOOL(0, "symref", &show_symref_target,
                         N_("show underlying ref in addition to the object pointed by it")),
                OPT_END()
        if (argc > 1) {
                int i;
                pattern = xcalloc(argc, sizeof(const char *));
-               for (i = 1; i < argc; i++)
+               for (i = 1; i < argc; i++) {
+                       const char *glob;
                        pattern[i - 1] = xstrfmt("*/%s", argv[i]);
+                       glob = strchr(argv[i], '*');
+                       if (glob)
+                               argv_array_pushf(&ref_prefixes, "%.*s",
+                                                (int)(glob - argv[i]), argv[i]);
+                       else
+                               expand_ref_prefix(&ref_prefixes, argv[i]);
+               }
        }
  
        remote = remote_get(dest);
        if (uploadpack != NULL)
                transport_set_option(transport, TRANS_OPT_UPLOADPACK, uploadpack);
  
-       ref = transport_get_remote_refs(transport);
+       ref = transport_get_remote_refs(transport, &ref_prefixes);
        if (transport_disconnect(transport))
                return 1;
  
diff --combined builtin/receive-pack.c
index 2bf7f2d1a3e589f982400fef1aa5bec6e97ccfbd,3656e94fdb79da622dad773e0681a7438ce25504..79e82f9c1cf6fb4bff005cc38ea33a34819c64a7
@@@ -69,7 -69,7 +69,7 @@@ static int sent_capabilities
  static int shallow_update;
  static const char *alt_shallow_file;
  static struct strbuf push_cert = STRBUF_INIT;
 -static unsigned char push_cert_sha1[20];
 +static struct object_id push_cert_oid;
  static struct signature_check sigcheck;
  static const char *push_cert_nonce;
  static const char *cert_nonce_seed;
@@@ -633,9 -633,8 +633,9 @@@ static void prepare_push_cert_sha1(stru
                int bogs /* beginning_of_gpg_sig */;
  
                already_done = 1;
 -              if (write_sha1_file(push_cert.buf, push_cert.len, "blob", push_cert_sha1))
 -                      hashclr(push_cert_sha1);
 +              if (write_object_file(push_cert.buf, push_cert.len, "blob",
 +                                    &push_cert_oid))
 +                      oidclr(&push_cert_oid);
  
                memset(&sigcheck, '\0', sizeof(sigcheck));
                sigcheck.result = 'N';
                strbuf_release(&gpg_status);
                nonce_status = check_nonce(push_cert.buf, bogs);
        }
 -      if (!is_null_sha1(push_cert_sha1)) {
 +      if (!is_null_oid(&push_cert_oid)) {
                argv_array_pushf(&proc->env_array, "GIT_PUSH_CERT=%s",
 -                               sha1_to_hex(push_cert_sha1));
 +                               oid_to_hex(&push_cert_oid));
                argv_array_pushf(&proc->env_array, "GIT_PUSH_CERT_SIGNER=%s",
                                 sigcheck.signer ? sigcheck.signer : "");
                argv_array_pushf(&proc->env_array, "GIT_PUSH_CERT_KEY=%s",
@@@ -1242,11 -1241,11 +1242,11 @@@ static void check_aliased_update(struc
        rp_error("refusing inconsistent update between symref '%s' (%s..%s) and"
                 " its target '%s' (%s..%s)",
                 cmd->ref_name,
 -               find_unique_abbrev(cmd->old_oid.hash, DEFAULT_ABBREV),
 -               find_unique_abbrev(cmd->new_oid.hash, DEFAULT_ABBREV),
 +               find_unique_abbrev(&cmd->old_oid, DEFAULT_ABBREV),
 +               find_unique_abbrev(&cmd->new_oid, DEFAULT_ABBREV),
                 dst_cmd->ref_name,
 -               find_unique_abbrev(dst_cmd->old_oid.hash, DEFAULT_ABBREV),
 -               find_unique_abbrev(dst_cmd->new_oid.hash, DEFAULT_ABBREV));
 +               find_unique_abbrev(&dst_cmd->old_oid, DEFAULT_ABBREV),
 +               find_unique_abbrev(&dst_cmd->new_oid, DEFAULT_ABBREV));
  
        cmd->error_string = dst_cmd->error_string =
                "inconsistent aliased update";
@@@ -1964,6 -1963,12 +1964,12 @@@ int cmd_receive_pack(int argc, const ch
                unpack_limit = receive_unpack_limit;
  
        switch (determine_protocol_version_server()) {
+       case protocol_v2:
+               /*
+                * push support for protocol v2 has not been implemented yet,
+                * so ignore the request to use v2 and fallback to using v0.
+                */
+               break;
        case protocol_v1:
                /*
                 * v1 is just the original protocol with a version string,
diff --combined builtin/remote.c
index 805ffc05cdb80e4a69de4134e757f9c71e8033dc,d0b6ff6e29a5f907dd9ca012e7f5f66ae97729a8..8708e584e9e8793f3a5e44861d9fb9f049027fa3
@@@ -168,7 -168,7 +168,7 @@@ static int add(int argc, const char **a
                OPT_STRING('m', "master", &master, N_("branch"), N_("master branch")),
                { OPTION_CALLBACK, 0, "mirror", &mirror, N_("push|fetch"),
                        N_("set up remote as a mirror to push to or fetch from"),
 -                      PARSE_OPT_OPTARG, parse_mirror_opt },
 +                      PARSE_OPT_OPTARG | PARSE_OPT_COMP_ARG, parse_mirror_opt },
                OPT_END()
        };
  
@@@ -322,7 -322,7 +322,7 @@@ static void read_branches(void
  
  struct ref_states {
        struct remote *remote;
 -      struct string_list new, stale, tracked, heads, push;
 +      struct string_list new_refs, stale, tracked, heads, push;
        int queried;
  };
  
@@@ -337,12 -337,12 +337,12 @@@ static int get_ref_states(const struct 
                        die(_("Could not get fetch map for refspec %s"),
                                states->remote->fetch_refspec[i]);
  
 -      states->new.strdup_strings = 1;
 +      states->new_refs.strdup_strings = 1;
        states->tracked.strdup_strings = 1;
        states->stale.strdup_strings = 1;
        for (ref = fetch_map; ref; ref = ref->next) {
                if (!ref->peer_ref || !ref_exists(ref->peer_ref->name))
 -                      string_list_append(&states->new, abbrev_branch(ref->name));
 +                      string_list_append(&states->new_refs, abbrev_branch(ref->name));
                else
                        string_list_append(&states->tracked, abbrev_branch(ref->name));
        }
        free_refs(stale_refs);
        free_refs(fetch_map);
  
 -      string_list_sort(&states->new);
 +      string_list_sort(&states->new_refs);
        string_list_sort(&states->tracked);
        string_list_sort(&states->stale);
  
@@@ -546,8 -546,8 +546,8 @@@ static int add_branch_for_removal(cons
  }
  
  struct rename_info {
 -      const char *old;
 -      const char *new;
 +      const char *old_name;
 +      const char *new_name;
        struct string_list *remote_branches;
  };
  
@@@ -560,7 -560,7 +560,7 @@@ static int read_remote_branches(const c
        int flag;
        const char *symref;
  
 -      strbuf_addf(&buf, "refs/remotes/%s/", rename->old);
 +      strbuf_addf(&buf, "refs/remotes/%s/", rename->old_name);
        if (starts_with(refname, buf.buf)) {
                item = string_list_append(rename->remote_branches, xstrdup(refname));
                symref = resolve_ref_unsafe(refname, RESOLVE_REF_READING,
@@@ -615,36 -615,36 +615,36 @@@ static int mv(int argc, const char **ar
        if (argc != 3)
                usage_with_options(builtin_remote_rename_usage, options);
  
 -      rename.old = argv[1];
 -      rename.new = argv[2];
 +      rename.old_name = argv[1];
 +      rename.new_name = argv[2];
        rename.remote_branches = &remote_branches;
  
 -      oldremote = remote_get(rename.old);
 +      oldremote = remote_get(rename.old_name);
        if (!remote_is_configured(oldremote, 1))
 -              die(_("No such remote: %s"), rename.old);
 +              die(_("No such remote: %s"), rename.old_name);
  
 -      if (!strcmp(rename.old, rename.new) && oldremote->origin != REMOTE_CONFIG)
 +      if (!strcmp(rename.old_name, rename.new_name) && oldremote->origin != REMOTE_CONFIG)
                return migrate_file(oldremote);
  
 -      newremote = remote_get(rename.new);
 +      newremote = remote_get(rename.new_name);
        if (remote_is_configured(newremote, 1))
 -              die(_("remote %s already exists."), rename.new);
 +              die(_("remote %s already exists."), rename.new_name);
  
 -      strbuf_addf(&buf, "refs/heads/test:refs/remotes/%s/test", rename.new);
 +      strbuf_addf(&buf, "refs/heads/test:refs/remotes/%s/test", rename.new_name);
        if (!valid_fetch_refspec(buf.buf))
 -              die(_("'%s' is not a valid remote name"), rename.new);
 +              die(_("'%s' is not a valid remote name"), rename.new_name);
  
        strbuf_reset(&buf);
 -      strbuf_addf(&buf, "remote.%s", rename.old);
 -      strbuf_addf(&buf2, "remote.%s", rename.new);
 +      strbuf_addf(&buf, "remote.%s", rename.old_name);
 +      strbuf_addf(&buf2, "remote.%s", rename.new_name);
        if (git_config_rename_section(buf.buf, buf2.buf) < 1)
                return error(_("Could not rename config section '%s' to '%s'"),
                                buf.buf, buf2.buf);
  
        strbuf_reset(&buf);
 -      strbuf_addf(&buf, "remote.%s.fetch", rename.new);
 +      strbuf_addf(&buf, "remote.%s.fetch", rename.new_name);
        git_config_set_multivar(buf.buf, NULL, NULL, 1);
 -      strbuf_addf(&old_remote_context, ":refs/remotes/%s/", rename.old);
 +      strbuf_addf(&old_remote_context, ":refs/remotes/%s/", rename.old_name);
        for (i = 0; i < oldremote->fetch_refspec_nr; i++) {
                char *ptr;
  
                        refspec_updated = 1;
                        strbuf_splice(&buf2,
                                      ptr-buf2.buf + strlen(":refs/remotes/"),
 -                                    strlen(rename.old), rename.new,
 -                                    strlen(rename.new));
 +                                    strlen(rename.old_name), rename.new_name,
 +                                    strlen(rename.new_name));
                } else
                        warning(_("Not updating non-default fetch refspec\n"
                                  "\t%s\n"
        for (i = 0; i < branch_list.nr; i++) {
                struct string_list_item *item = branch_list.items + i;
                struct branch_info *info = item->util;
 -              if (info->remote_name && !strcmp(info->remote_name, rename.old)) {
 +              if (info->remote_name && !strcmp(info->remote_name, rename.old_name)) {
                        strbuf_reset(&buf);
                        strbuf_addf(&buf, "branch.%s.remote", item->string);
 -                      git_config_set(buf.buf, rename.new);
 +                      git_config_set(buf.buf, rename.new_name);
                }
        }
  
                        continue;
                strbuf_reset(&buf);
                strbuf_addstr(&buf, item->string);
 -              strbuf_splice(&buf, strlen("refs/remotes/"), strlen(rename.old),
 -                              rename.new, strlen(rename.new));
 +              strbuf_splice(&buf, strlen("refs/remotes/"), strlen(rename.old_name),
 +                              rename.new_name, strlen(rename.new_name));
                strbuf_reset(&buf2);
                strbuf_addf(&buf2, "remote: renamed %s to %s",
                                item->string, buf.buf);
                        continue;
                strbuf_reset(&buf);
                strbuf_addstr(&buf, item->string);
 -              strbuf_splice(&buf, strlen("refs/remotes/"), strlen(rename.old),
 -                              rename.new, strlen(rename.new));
 +              strbuf_splice(&buf, strlen("refs/remotes/"), strlen(rename.old_name),
 +                              rename.new_name, strlen(rename.new_name));
                strbuf_reset(&buf2);
                strbuf_addstr(&buf2, item->util);
 -              strbuf_splice(&buf2, strlen("refs/remotes/"), strlen(rename.old),
 -                              rename.new, strlen(rename.new));
 +              strbuf_splice(&buf2, strlen("refs/remotes/"), strlen(rename.old_name),
 +                              rename.new_name, strlen(rename.new_name));
                strbuf_reset(&buf3);
                strbuf_addf(&buf3, "remote: renamed %s to %s",
                                item->string, buf.buf);
@@@ -822,7 -822,7 +822,7 @@@ static void clear_push_info(void *util
  
  static void free_remote_ref_states(struct ref_states *states)
  {
 -      string_list_clear(&states->new, 0);
 +      string_list_clear(&states->new_refs, 0);
        string_list_clear(&states->stale, 1);
        string_list_clear(&states->tracked, 0);
        string_list_clear(&states->heads, 0);
@@@ -862,7 -862,7 +862,7 @@@ static int get_remote_ref_states(const 
        if (query) {
                transport = transport_get(states->remote, states->remote->url_nr > 0 ?
                        states->remote->url[0] : NULL);
-               remote_refs = transport_get_remote_refs(transport);
+               remote_refs = transport_get_remote_refs(transport, NULL);
                transport_disconnect(transport);
  
                states->queried = 1;
@@@ -907,7 -907,7 +907,7 @@@ static int show_remote_info_item(struc
        if (states->queried) {
                const char *fmt = "%s";
                const char *arg = "";
 -              if (string_list_has_string(&states->new, name)) {
 +              if (string_list_has_string(&states->new_refs, name)) {
                        fmt = _(" new (next fetch will store in remotes/%s)");
                        arg = states->remote->name;
                } else if (string_list_has_string(&states->tracked, name))
@@@ -1176,7 -1176,7 +1176,7 @@@ static int show(int argc, const char **
  
                /* remote branch info */
                info.width = 0;
 -              for_each_string_list(&states.new, add_remote_to_show_info, &info);
 +              for_each_string_list(&states.new_refs, add_remote_to_show_info, &info);
                for_each_string_list(&states.tracked, add_remote_to_show_info, &info);
                for_each_string_list(&states.stale, add_remote_to_show_info, &info);
                if (info.list->nr)
diff --combined fetch-pack.c
index 52932b37f8dce61296c37d6fa821d564f0f4a38b,837e1fd21d1a9947b209b778d3d2ceccde912454..373dc90bbec365384bbd02fcd4573744cef30634
@@@ -29,7 -29,6 +29,7 @@@ static int deepen_not_ok
  static int fetch_fsck_objects = -1;
  static int transfer_fsck_objects = -1;
  static int agent_supported;
 +static int server_supports_filtering;
  static struct lock_file shallow_lock;
  static const char *alternate_shallow_file;
  
@@@ -261,8 -260,8 +261,8 @@@ static enum ack_type get_ack(int fd, st
        char *line = packet_read_line(fd, &len);
        const char *arg;
  
 -      if (!len)
 -              die(_("git fetch-pack: expected ACK/NAK, got EOF"));
 +      if (!line)
 +              die(_("git fetch-pack: expected ACK/NAK, got a flush packet"));
        if (!strcmp(line, "NAK"))
                return NAK;
        if (skip_prefix(line, "ACK ", &arg)) {
@@@ -304,9 -303,9 +304,9 @@@ static void insert_one_alternate_object
  #define PIPESAFE_FLUSH 32
  #define LARGE_FLUSH 16384
  
- static int next_flush(struct fetch_pack_args *args, int count)
+ static int next_flush(int stateless_rpc, int count)
  {
-       if (args->stateless_rpc) {
+       if (stateless_rpc) {
                if (count < LARGE_FLUSH)
                        count <<= 1;
                else
@@@ -380,8 -379,6 +380,8 @@@ static int find_common(struct fetch_pac
                        if (deepen_not_ok)      strbuf_addstr(&c, " deepen-not");
                        if (agent_supported)    strbuf_addf(&c, " agent=%s",
                                                            git_user_agent_sanitized());
 +                      if (args->filter_options.choice)
 +                              strbuf_addstr(&c, " filter");
                        packet_buf_write(&req_buf, "want %s%s\n", remote_hex, c.buf);
                        strbuf_release(&c);
                } else
                        packet_buf_write(&req_buf, "deepen-not %s", s->string);
                }
        }
 +      if (server_supports_filtering && args->filter_options.choice)
 +              packet_buf_write(&req_buf, "filter %s",
 +                               args->filter_options.filter_spec);
        packet_buf_flush(&req_buf);
        state_len = req_buf.len;
  
  
        flushes = 0;
        retval = -1;
 +      if (args->no_dependents)
 +              goto done;
        while ((oid = get_rev())) {
                packet_buf_write(&req_buf, "have %s\n", oid_to_hex(oid));
                print_verbose(args, "have %s", oid_to_hex(oid));
                        send_request(args, fd[1], &req_buf);
                        strbuf_setlen(&req_buf, state_len);
                        flushes++;
-                       flush_at = next_flush(args, count);
+                       flush_at = next_flush(args->stateless_rpc, count);
  
                        /*
                         * We keep one window "ahead" of the other side, and
@@@ -711,61 -703,23 +711,61 @@@ static void mark_alternate_complete(str
        mark_complete(&obj->oid);
  }
  
 +struct loose_object_iter {
 +      struct oidset *loose_object_set;
 +      struct ref *refs;
 +};
 +
 +/*
 + *  If the number of refs is not larger than the number of loose objects,
 + *  this function stops inserting.
 + */
 +static int add_loose_objects_to_set(const struct object_id *oid,
 +                                  const char *path,
 +                                  void *data)
 +{
 +      struct loose_object_iter *iter = data;
 +      oidset_insert(iter->loose_object_set, oid);
 +      if (iter->refs == NULL)
 +              return 1;
 +
 +      iter->refs = iter->refs->next;
 +      return 0;
 +}
 +
  static int everything_local(struct fetch_pack_args *args,
                            struct ref **refs,
                            struct ref **sought, int nr_sought)
  {
        struct ref *ref;
        int retval;
 +      int old_save_commit_buffer = save_commit_buffer;
        timestamp_t cutoff = 0;
 +      struct oidset loose_oid_set = OIDSET_INIT;
 +      int use_oidset = 0;
 +      struct loose_object_iter iter = {&loose_oid_set, *refs};
 +
 +      /* Enumerate all loose objects or know refs are not so many. */
 +      use_oidset = !for_each_loose_object(add_loose_objects_to_set,
 +                                          &iter, 0);
  
        save_commit_buffer = 0;
  
        for (ref = *refs; ref; ref = ref->next) {
                struct object *o;
 +              unsigned int flags = OBJECT_INFO_QUICK;
  
 -              if (!has_object_file_with_flags(&ref->old_oid,
 -                                              OBJECT_INFO_QUICK))
 -                      continue;
 +              if (use_oidset &&
 +                  !oidset_contains(&loose_oid_set, &ref->old_oid)) {
 +                      /*
 +                       * I know this does not exist in the loose form,
 +                       * so check if it exists in a non-loose form.
 +                       */
 +                      flags |= OBJECT_INFO_IGNORE_LOOSE;
 +              }
  
 +              if (!has_object_file_with_flags(&ref->old_oid, flags))
 +                      continue;
                o = parse_object(&ref->old_oid);
                if (!o)
                        continue;
                }
        }
  
 -      if (!args->deepen) {
 -              for_each_ref(mark_complete_oid, NULL);
 -              for_each_cached_alternate(mark_alternate_complete);
 -              commit_list_sort_by_date(&complete);
 -              if (cutoff)
 -                      mark_recent_complete_commits(args, cutoff);
 -      }
 +      oidset_clear(&loose_oid_set);
  
 -      /*
 -       * Mark all complete remote refs as common refs.
 -       * Don't mark them common yet; the server has to be told so first.
 -       */
 -      for (ref = *refs; ref; ref = ref->next) {
 -              struct object *o = deref_tag(lookup_object(ref->old_oid.hash),
 -                                           NULL, 0);
 +      if (!args->no_dependents) {
 +              if (!args->deepen) {
 +                      for_each_ref(mark_complete_oid, NULL);
 +                      for_each_cached_alternate(mark_alternate_complete);
 +                      commit_list_sort_by_date(&complete);
 +                      if (cutoff)
 +                              mark_recent_complete_commits(args, cutoff);
 +              }
  
 -              if (!o || o->type != OBJ_COMMIT || !(o->flags & COMPLETE))
 -                      continue;
 +              /*
 +               * Mark all complete remote refs as common refs.
 +               * Don't mark them common yet; the server has to be told so first.
 +               */
 +              for (ref = *refs; ref; ref = ref->next) {
 +                      struct object *o = deref_tag(lookup_object(ref->old_oid.hash),
 +                                                   NULL, 0);
 +
 +                      if (!o || o->type != OBJ_COMMIT || !(o->flags & COMPLETE))
 +                              continue;
  
 -              if (!(o->flags & SEEN)) {
 -                      rev_list_push((struct commit *)o, COMMON_REF | SEEN);
 +                      if (!(o->flags & SEEN)) {
 +                              rev_list_push((struct commit *)o, COMMON_REF | SEEN);
  
 -                      mark_common((struct commit *)o, 1, 1);
 +                              mark_common((struct commit *)o, 1, 1);
 +                      }
                }
        }
  
                print_verbose(args, _("already have %s (%s)"), oid_to_hex(remote),
                              ref->name);
        }
 +
 +      save_commit_buffer = old_save_commit_buffer;
 +
        return retval;
  }
  
@@@ -886,7 -833,7 +886,7 @@@ static int get_pack(struct fetch_pack_a
                argv_array_push(&cmd.args, alternate_shallow_file);
        }
  
 -      if (do_keep) {
 +      if (do_keep || args->from_promisor) {
                if (pack_lockfile)
                        cmd.out = -1;
                cmd_name = "index-pack";
                        argv_array_push(&cmd.args, "-v");
                if (args->use_thin_pack)
                        argv_array_push(&cmd.args, "--fix-thin");
 -              if (args->lock_pack || unpack_limit) {
 +              if (do_keep && (args->lock_pack || unpack_limit)) {
                        char hostname[HOST_NAME_MAX + 1];
                        if (xgethostname(hostname, sizeof(hostname)))
                                xsnprintf(hostname, sizeof(hostname), "localhost");
                }
                if (args->check_self_contained_and_connected)
                        argv_array_push(&cmd.args, "--check-self-contained-and-connected");
 +              if (args->from_promisor)
 +                      argv_array_push(&cmd.args, "--promisor");
        }
        else {
                cmd_name = "unpack-objects";
            ? fetch_fsck_objects
            : transfer_fsck_objects >= 0
            ? transfer_fsck_objects
 -          : 0)
 -              argv_array_push(&cmd.args, "--strict");
 +          : 0) {
 +              if (args->from_promisor)
 +                      /*
 +                       * We cannot use --strict in index-pack because it
 +                       * checks both broken objects and links, but we only
 +                       * want to check for broken objects.
 +                       */
 +                      argv_array_push(&cmd.args, "--fsck-objects");
 +              else
 +                      argv_array_push(&cmd.args, "--strict");
 +      }
  
        cmd.in = demux.out;
        cmd.git_cmd = 1;
@@@ -1028,13 -964,6 +1028,13 @@@ static struct ref *do_fetch_pack(struc
        else
                prefer_ofs_delta = 0;
  
 +      if (server_supports("filter")) {
 +              server_supports_filtering = 1;
 +              print_verbose(args, _("Server supports filter"));
 +      } else if (args->filter_options.choice) {
 +              warning("filtering not recognized by server, ignoring");
 +      }
 +
        if ((agent_feature = server_feature_value("agent", &agent_len))) {
                agent_supported = 1;
                if (agent_len)
        return ref;
  }
  
+ static void add_shallow_requests(struct strbuf *req_buf,
+                                const struct fetch_pack_args *args)
+ {
+       if (is_repository_shallow())
+               write_shallow_commits(req_buf, 1, NULL);
+       if (args->depth > 0)
+               packet_buf_write(req_buf, "deepen %d", args->depth);
+       if (args->deepen_since) {
+               timestamp_t max_age = approxidate(args->deepen_since);
+               packet_buf_write(req_buf, "deepen-since %"PRItime, max_age);
+       }
+       if (args->deepen_not) {
+               int i;
+               for (i = 0; i < args->deepen_not->nr; i++) {
+                       struct string_list_item *s = args->deepen_not->items + i;
+                       packet_buf_write(req_buf, "deepen-not %s", s->string);
+               }
+       }
+ }
+ static void add_wants(const struct ref *wants, struct strbuf *req_buf)
+ {
+       for ( ; wants ; wants = wants->next) {
+               const struct object_id *remote = &wants->old_oid;
+               const char *remote_hex;
+               struct object *o;
+               /*
+                * If that object is complete (i.e. it is an ancestor of a
+                * local ref), we tell them we have it but do not have to
+                * tell them about its ancestors, which they already know
+                * about.
+                *
+                * We use lookup_object here because we are only
+                * interested in the case we *know* the object is
+                * reachable and we have already scanned it.
+                */
+               if (((o = lookup_object(remote->hash)) != NULL) &&
+                   (o->flags & COMPLETE)) {
+                       continue;
+               }
+               remote_hex = oid_to_hex(remote);
+               packet_buf_write(req_buf, "want %s\n", remote_hex);
+       }
+ }
+ static void add_common(struct strbuf *req_buf, struct oidset *common)
+ {
+       struct oidset_iter iter;
+       const struct object_id *oid;
+       oidset_iter_init(common, &iter);
+       while ((oid = oidset_iter_next(&iter))) {
+               packet_buf_write(req_buf, "have %s\n", oid_to_hex(oid));
+       }
+ }
+ static int add_haves(struct strbuf *req_buf, int *haves_to_send, int *in_vain)
+ {
+       int ret = 0;
+       int haves_added = 0;
+       const struct object_id *oid;
+       while ((oid = get_rev())) {
+               packet_buf_write(req_buf, "have %s\n", oid_to_hex(oid));
+               if (++haves_added >= *haves_to_send)
+                       break;
+       }
+       *in_vain += haves_added;
+       if (!haves_added || *in_vain >= MAX_IN_VAIN) {
+               /* Send Done */
+               packet_buf_write(req_buf, "done\n");
+               ret = 1;
+       }
+       /* Increase haves to send on next round */
+       *haves_to_send = next_flush(1, *haves_to_send);
+       return ret;
+ }
+ static int send_fetch_request(int fd_out, const struct fetch_pack_args *args,
+                             const struct ref *wants, struct oidset *common,
+                             int *haves_to_send, int *in_vain)
+ {
+       int ret = 0;
+       struct strbuf req_buf = STRBUF_INIT;
+       if (server_supports_v2("fetch", 1))
+               packet_buf_write(&req_buf, "command=fetch");
+       if (server_supports_v2("agent", 0))
+               packet_buf_write(&req_buf, "agent=%s", git_user_agent_sanitized());
+       packet_buf_delim(&req_buf);
+       if (args->use_thin_pack)
+               packet_buf_write(&req_buf, "thin-pack");
+       if (args->no_progress)
+               packet_buf_write(&req_buf, "no-progress");
+       if (args->include_tag)
+               packet_buf_write(&req_buf, "include-tag");
+       if (prefer_ofs_delta)
+               packet_buf_write(&req_buf, "ofs-delta");
+       /* Add shallow-info and deepen request */
+       if (server_supports_feature("fetch", "shallow", 0))
+               add_shallow_requests(&req_buf, args);
+       else if (is_repository_shallow() || args->deepen)
+               die(_("Server does not support shallow requests"));
+       /* add wants */
+       add_wants(wants, &req_buf);
+       /* Add all of the common commits we've found in previous rounds */
+       add_common(&req_buf, common);
+       /* Add initial haves */
+       ret = add_haves(&req_buf, haves_to_send, in_vain);
+       /* Send request */
+       packet_buf_flush(&req_buf);
+       write_or_die(fd_out, req_buf.buf, req_buf.len);
+       strbuf_release(&req_buf);
+       return ret;
+ }
+ /*
+  * Processes a section header in a server's response and checks if it matches
+  * `section`.  If the value of `peek` is 1, the header line will be peeked (and
+  * not consumed); if 0, the line will be consumed and the function will die if
+  * the section header doesn't match what was expected.
+  */
+ static int process_section_header(struct packet_reader *reader,
+                                 const char *section, int peek)
+ {
+       int ret;
+       if (packet_reader_peek(reader) != PACKET_READ_NORMAL)
+               die("error reading section header '%s'", section);
+       ret = !strcmp(reader->line, section);
+       if (!peek) {
+               if (!ret)
+                       die("expected '%s', received '%s'",
+                           section, reader->line);
+               packet_reader_read(reader);
+       }
+       return ret;
+ }
+ static int process_acks(struct packet_reader *reader, struct oidset *common)
+ {
+       /* received */
+       int received_ready = 0;
+       int received_ack = 0;
+       process_section_header(reader, "acknowledgments", 0);
+       while (packet_reader_read(reader) == PACKET_READ_NORMAL) {
+               const char *arg;
+               if (!strcmp(reader->line, "NAK"))
+                       continue;
+               if (skip_prefix(reader->line, "ACK ", &arg)) {
+                       struct object_id oid;
+                       if (!get_oid_hex(arg, &oid)) {
+                               struct commit *commit;
+                               oidset_insert(common, &oid);
+                               commit = lookup_commit(&oid);
+                               mark_common(commit, 0, 1);
+                       }
+                       continue;
+               }
+               if (!strcmp(reader->line, "ready")) {
+                       clear_prio_queue(&rev_list);
+                       received_ready = 1;
+                       continue;
+               }
+               die("unexpected acknowledgment line: '%s'", reader->line);
+       }
+       if (reader->status != PACKET_READ_FLUSH &&
+           reader->status != PACKET_READ_DELIM)
+               die("error processing acks: %d", reader->status);
+       /* return 0 if no common, 1 if there are common, or 2 if ready */
+       return received_ready ? 2 : (received_ack ? 1 : 0);
+ }
+ static void receive_shallow_info(struct fetch_pack_args *args,
+                                struct packet_reader *reader)
+ {
+       process_section_header(reader, "shallow-info", 0);
+       while (packet_reader_read(reader) == PACKET_READ_NORMAL) {
+               const char *arg;
+               struct object_id oid;
+               if (skip_prefix(reader->line, "shallow ", &arg)) {
+                       if (get_oid_hex(arg, &oid))
+                               die(_("invalid shallow line: %s"), reader->line);
+                       register_shallow(&oid);
+                       continue;
+               }
+               if (skip_prefix(reader->line, "unshallow ", &arg)) {
+                       if (get_oid_hex(arg, &oid))
+                               die(_("invalid unshallow line: %s"), reader->line);
+                       if (!lookup_object(oid.hash))
+                               die(_("object not found: %s"), reader->line);
+                       /* make sure that it is parsed as shallow */
+                       if (!parse_object(&oid))
+                               die(_("error in object: %s"), reader->line);
+                       if (unregister_shallow(&oid))
+                               die(_("no shallow found: %s"), reader->line);
+                       continue;
+               }
+               die(_("expected shallow/unshallow, got %s"), reader->line);
+       }
+       if (reader->status != PACKET_READ_FLUSH &&
+           reader->status != PACKET_READ_DELIM)
+               die("error processing shallow info: %d", reader->status);
+       setup_alternate_shallow(&shallow_lock, &alternate_shallow_file, NULL);
+       args->deepen = 1;
+ }
+ enum fetch_state {
+       FETCH_CHECK_LOCAL = 0,
+       FETCH_SEND_REQUEST,
+       FETCH_PROCESS_ACKS,
+       FETCH_GET_PACK,
+       FETCH_DONE,
+ };
+ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
+                                   int fd[2],
+                                   const struct ref *orig_ref,
+                                   struct ref **sought, int nr_sought,
+                                   char **pack_lockfile)
+ {
+       struct ref *ref = copy_ref_list(orig_ref);
+       enum fetch_state state = FETCH_CHECK_LOCAL;
+       struct oidset common = OIDSET_INIT;
+       struct packet_reader reader;
+       int in_vain = 0;
+       int haves_to_send = INITIAL_FLUSH;
+       packet_reader_init(&reader, fd[0], NULL, 0,
+                          PACKET_READ_CHOMP_NEWLINE);
+       while (state != FETCH_DONE) {
+               switch (state) {
+               case FETCH_CHECK_LOCAL:
+                       sort_ref_list(&ref, ref_compare_name);
+                       QSORT(sought, nr_sought, cmp_ref_by_name);
+                       /* v2 supports these by default */
+                       allow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;
+                       use_sideband = 2;
+                       if (args->depth > 0 || args->deepen_since || args->deepen_not)
+                               args->deepen = 1;
+                       if (marked)
+                               for_each_ref(clear_marks, NULL);
+                       marked = 1;
+                       for_each_ref(rev_list_insert_ref_oid, NULL);
+                       for_each_cached_alternate(insert_one_alternate_object);
+                       /* Filter 'ref' by 'sought' and those that aren't local */
+                       if (everything_local(args, &ref, sought, nr_sought))
+                               state = FETCH_DONE;
+                       else
+                               state = FETCH_SEND_REQUEST;
+                       break;
+               case FETCH_SEND_REQUEST:
+                       if (send_fetch_request(fd[1], args, ref, &common,
+                                              &haves_to_send, &in_vain))
+                               state = FETCH_GET_PACK;
+                       else
+                               state = FETCH_PROCESS_ACKS;
+                       break;
+               case FETCH_PROCESS_ACKS:
+                       /* Process ACKs/NAKs */
+                       switch (process_acks(&reader, &common)) {
+                       case 2:
+                               state = FETCH_GET_PACK;
+                               break;
+                       case 1:
+                               in_vain = 0;
+                               /* fallthrough */
+                       default:
+                               state = FETCH_SEND_REQUEST;
+                               break;
+                       }
+                       break;
+               case FETCH_GET_PACK:
+                       /* Check for shallow-info section */
+                       if (process_section_header(&reader, "shallow-info", 1))
+                               receive_shallow_info(args, &reader);
+                       /* get the pack */
+                       process_section_header(&reader, "packfile", 0);
+                       if (get_pack(args, fd, pack_lockfile))
+                               die(_("git fetch-pack: fetch failed."));
+                       state = FETCH_DONE;
+                       break;
+               case FETCH_DONE:
+                       continue;
+               }
+       }
+       oidset_clear(&common);
+       return ref;
+ }
  static void fetch_pack_config(void)
  {
        git_config_get_int("fetch.unpacklimit", &fetch_unpack_limit);
@@@ -1224,7 -1475,8 +1546,8 @@@ struct ref *fetch_pack(struct fetch_pac
                       const char *dest,
                       struct ref **sought, int nr_sought,
                       struct oid_array *shallow,
-                      char **pack_lockfile)
+                      char **pack_lockfile,
+                      enum protocol_version version)
  {
        struct ref *ref_cpy;
        struct shallow_info si;
                die(_("no matching remote head"));
        }
        prepare_shallow_info(&si, shallow);
-       ref_cpy = do_fetch_pack(args, fd, ref, sought, nr_sought,
-                               &si, pack_lockfile);
+       if (version == protocol_v2)
+               ref_cpy = do_fetch_pack_v2(args, fd, ref, sought, nr_sought,
+                                          pack_lockfile);
+       else
+               ref_cpy = do_fetch_pack(args, fd, ref, sought, nr_sought,
+                                       &si, pack_lockfile);
        reprepare_packed_git();
        update_shallow(args, sought, nr_sought, &si);
        clear_shallow_info(&si);
diff --combined fetch-pack.h
index 3e224a18226ec6219b09387704baf178821c4c23,7afca730560b7fb62ff7111a044a29d7caac0b8c..6afa08b48bb9f7cc2db1b2fb4644c576fd8d8f3e
@@@ -3,7 -3,7 +3,8 @@@
  
  #include "string-list.h"
  #include "run-command.h"
+ #include "protocol.h"
 +#include "list-objects-filter-options.h"
  
  struct oid_array;
  
@@@ -13,7 -13,6 +14,7 @@@ struct fetch_pack_args 
        int depth;
        const char *deepen_since;
        const struct string_list *deepen_not;
 +      struct list_objects_filter_options filter_options;
        unsigned deepen_relative:1;
        unsigned quiet:1;
        unsigned keep_pack:1;
        unsigned cloning:1;
        unsigned update_shallow:1;
        unsigned deepen:1;
 +      unsigned from_promisor:1;
 +
 +      /*
 +       * If 1, fetch_pack() will also not modify any object flags.
 +       * This allows fetch_pack() to safely be called by any function,
 +       * regardless of which object flags it uses (if any).
 +       */
 +      unsigned no_dependents:1;
  };
  
  /*
@@@ -53,7 -44,8 +54,8 @@@ struct ref *fetch_pack(struct fetch_pac
                       struct ref **sought,
                       int nr_sought,
                       struct oid_array *shallow,
-                      char **pack_lockfile);
+                      char **pack_lockfile,
+                      enum protocol_version version);
  
  /*
   * Print an appropriate error message for each sought ref that wasn't
diff --combined git.c
index ceaa58ef40e536f1290cce3ad1223004063e41a6,f85d682b62d1ee24e1526576c41982204c5f73c9..2a5840f37d60df8fc44b05d83e08aba0aa11d3f2
--- 1/git.c
--- 2/git.c
+++ b/git.c
@@@ -5,11 -5,11 +5,11 @@@
  #include "run-command.h"
  
  const char git_usage_string[] =
 -      "git [--version] [--help] [-C <path>] [-c name=value]\n"
 -      "           [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]\n"
 -      "           [-p | --paginate | --no-pager] [--no-replace-objects] [--bare]\n"
 -      "           [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]\n"
 -      "           <command> [<args>]";
 +      N_("git [--version] [--help] [-C <path>] [-c <name>=<value>]\n"
 +         "           [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]\n"
 +         "           [-p | --paginate | --no-pager] [--no-replace-objects] [--bare]\n"
 +         "           [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]\n"
 +         "           <command> [<args>]");
  
  const char git_more_info_string[] =
        N_("'git help -a' and 'git help -g' list available subcommands and some\n"
@@@ -92,7 -92,7 +92,7 @@@ static int handle_options(const char **
                                *envchanged = 1;
                } else if (!strcmp(cmd, "--git-dir")) {
                        if (*argc < 2) {
 -                              fprintf(stderr, "No directory given for --git-dir.\n" );
 +                              fprintf(stderr, _("no directory given for --git-dir\n" ));
                                usage(git_usage_string);
                        }
                        setenv(GIT_DIR_ENVIRONMENT, (*argv)[1], 1);
                                *envchanged = 1;
                } else if (!strcmp(cmd, "--namespace")) {
                        if (*argc < 2) {
 -                              fprintf(stderr, "No namespace given for --namespace.\n" );
 +                              fprintf(stderr, _("no namespace given for --namespace\n" ));
                                usage(git_usage_string);
                        }
                        setenv(GIT_NAMESPACE_ENVIRONMENT, (*argv)[1], 1);
                                *envchanged = 1;
                } else if (!strcmp(cmd, "--work-tree")) {
                        if (*argc < 2) {
 -                              fprintf(stderr, "No directory given for --work-tree.\n" );
 +                              fprintf(stderr, _("no directory given for --work-tree\n" ));
                                usage(git_usage_string);
                        }
                        setenv(GIT_WORK_TREE_ENVIRONMENT, (*argv)[1], 1);
                                *envchanged = 1;
                } else if (!strcmp(cmd, "--super-prefix")) {
                        if (*argc < 2) {
 -                              fprintf(stderr, "No prefix given for --super-prefix.\n" );
 +                              fprintf(stderr, _("no prefix given for --super-prefix\n" ));
                                usage(git_usage_string);
                        }
                        setenv(GIT_SUPER_PREFIX_ENVIRONMENT, (*argv)[1], 1);
                                *envchanged = 1;
                } else if (!strcmp(cmd, "-c")) {
                        if (*argc < 2) {
 -                              fprintf(stderr, "-c expects a configuration string\n" );
 +                              fprintf(stderr, _("-c expects a configuration string\n" ));
                                usage(git_usage_string);
                        }
                        git_config_push_parameter((*argv)[1]);
                                *envchanged = 1;
                } else if (!strcmp(cmd, "-C")) {
                        if (*argc < 2) {
 -                              fprintf(stderr, "No directory given for -C.\n" );
 +                              fprintf(stderr, _("no directory given for -C\n" ));
                                usage(git_usage_string);
                        }
                        if ((*argv)[1][0]) {
                                if (chdir((*argv)[1]))
 -                                      die_errno("Cannot change to '%s'", (*argv)[1]);
 +                                      die_errno("cannot change to '%s'", (*argv)[1]);
                                if (envchanged)
                                        *envchanged = 1;
                        }
                        list_builtins();
                        exit(0);
                } else {
 -                      fprintf(stderr, "Unknown option: %s\n", cmd);
 +                      fprintf(stderr, _("unknown option: %s\n"), cmd);
                        usage(git_usage_string);
                }
  
@@@ -247,7 -247,7 +247,7 @@@ static int handle_alias(int *argcp, con
                        if (ret >= 0)   /* normal exit */
                                exit(ret);
  
 -                      die_errno("While expanding alias '%s': '%s'",
 +                      die_errno("while expanding alias '%s': '%s'",
                            alias_command, alias_string + 1);
                }
                count = split_cmdline(alias_string, &new_argv);
                            split_cmdline_strerror(count));
                option_count = handle_options(&new_argv, &count, &envchanged);
                if (envchanged)
 -                      die("alias '%s' changes environment variables\n"
 -                               "You can use '!git' in the alias to do this.",
 +                      die("alias '%s' changes environment variables.\n"
 +                               "You can use '!git' in the alias to do this",
                                 alias_command);
                memmove(new_argv - option_count, new_argv,
                                count * sizeof(char *));
@@@ -389,7 -389,7 +389,7 @@@ static struct cmd_struct commands[] = 
        { "column", cmd_column, RUN_SETUP_GENTLY },
        { "commit", cmd_commit, RUN_SETUP | NEED_WORK_TREE },
        { "commit-tree", cmd_commit_tree, RUN_SETUP },
 -      { "config", cmd_config, RUN_SETUP_GENTLY },
 +      { "config", cmd_config, RUN_SETUP_GENTLY | DELAY_PAGER_CONFIG },
        { "count-objects", cmd_count_objects, RUN_SETUP },
        { "credential", cmd_credential, RUN_SETUP_GENTLY },
        { "describe", cmd_describe, RUN_SETUP },
        { "revert", cmd_revert, RUN_SETUP | NEED_WORK_TREE },
        { "rm", cmd_rm, RUN_SETUP },
        { "send-pack", cmd_send_pack, RUN_SETUP },
+       { "serve", cmd_serve, RUN_SETUP },
        { "shortlog", cmd_shortlog, RUN_SETUP_GENTLY | USE_PAGER },
        { "show", cmd_show, RUN_SETUP },
        { "show-branch", cmd_show_branch, RUN_SETUP },
        { "update-server-info", cmd_update_server_info, RUN_SETUP },
        { "upload-archive", cmd_upload_archive },
        { "upload-archive--writer", cmd_upload_archive_writer },
+       { "upload-pack", cmd_upload_pack },
        { "var", cmd_var, RUN_SETUP_GENTLY },
        { "verify-commit", cmd_verify_commit, RUN_SETUP },
        { "verify-pack", cmd_verify_pack },
@@@ -684,8 -686,8 +686,8 @@@ int cmd_main(int argc, const char **arg
                if (errno != ENOENT)
                        break;
                if (was_alias) {
 -                      fprintf(stderr, "Expansion of alias '%s' failed; "
 -                              "'%s' is not a git command\n",
 +                      fprintf(stderr, _("expansion of alias '%s' failed; "
 +                                        "'%s' is not a git command\n"),
                                cmd, argv[0]);
                        exit(1);
                }
                        break;
        }
  
 -      fprintf(stderr, "Failed to run command '%s': %s\n",
 +      fprintf(stderr, _("failed to run command '%s': %s\n"),
                cmd, strerror(errno));
  
        return 1;
diff --combined http.c
index a5bd5d62c22c054f82b9971fc1f320c643f1d6fb,531c59d21e3e99f3b6c14b0bd0967285393b4359..d058b75be4ae621f56dfbe433b91d72284d282cb
--- 1/http.c
--- 2/http.c
+++ b/http.c
  #include "transport.h"
  #include "packfile.h"
  #include "protocol.h"
 +#include "string-list.h"
  
  static struct trace_key trace_curl = TRACE_KEY_INIT(CURL);
 +static int trace_curl_data = 1;
 +static struct string_list cookies_to_redact = STRING_LIST_INIT_DUP;
  #if LIBCURL_VERSION_NUM >= 0x070a08
  long int git_curl_ipresolve = CURL_IPRESOLVE_WHATEVER;
  #else
@@@ -69,9 -66,6 +69,9 @@@ static const char *ssl_key
  #if LIBCURL_VERSION_NUM >= 0x070908
  static const char *ssl_capath;
  #endif
 +#if LIBCURL_VERSION_NUM >= 0x071304
 +static const char *curl_no_proxy;
 +#endif
  #if LIBCURL_VERSION_NUM >= 0x072c00
  static const char *ssl_pinnedkey;
  #endif
@@@ -80,6 -74,7 +80,6 @@@ static long curl_low_speed_limit = -1
  static long curl_low_speed_time = -1;
  static int curl_ftp_no_epsv;
  static const char *curl_http_proxy;
 -static const char *curl_no_proxy;
  static const char *http_proxy_authmethod;
  static struct {
        const char *name;
@@@ -580,54 -575,6 +580,54 @@@ static void redact_sensitive_header(str
                /* Everything else is opaque and possibly sensitive */
                strbuf_setlen(header,  sensitive_header - header->buf);
                strbuf_addstr(header, " <redacted>");
 +      } else if (cookies_to_redact.nr &&
 +                 skip_prefix(header->buf, "Cookie:", &sensitive_header)) {
 +              struct strbuf redacted_header = STRBUF_INIT;
 +              char *cookie;
 +
 +              while (isspace(*sensitive_header))
 +                      sensitive_header++;
 +
 +              /*
 +               * The contents of header starting from sensitive_header will
 +               * subsequently be overridden, so it is fine to mutate this
 +               * string (hence the assignment to "char *").
 +               */
 +              cookie = (char *) sensitive_header;
 +
 +              while (cookie) {
 +                      char *equals;
 +                      char *semicolon = strstr(cookie, "; ");
 +                      if (semicolon)
 +                              *semicolon = 0;
 +                      equals = strchrnul(cookie, '=');
 +                      if (!equals) {
 +                              /* invalid cookie, just append and continue */
 +                              strbuf_addstr(&redacted_header, cookie);
 +                              continue;
 +                      }
 +                      *equals = 0; /* temporarily set to NUL for lookup */
 +                      if (string_list_lookup(&cookies_to_redact, cookie)) {
 +                              strbuf_addstr(&redacted_header, cookie);
 +                              strbuf_addstr(&redacted_header, "=<redacted>");
 +                      } else {
 +                              *equals = '=';
 +                              strbuf_addstr(&redacted_header, cookie);
 +                      }
 +                      if (semicolon) {
 +                              /*
 +                               * There are more cookies. (Or, for some
 +                               * reason, the input string ends in "; ".)
 +                               */
 +                              strbuf_addstr(&redacted_header, "; ");
 +                              cookie = semicolon + strlen("; ");
 +                      } else {
 +                              cookie = NULL;
 +                      }
 +              }
 +
 +              strbuf_setlen(header, sensitive_header - header->buf);
 +              strbuf_addbuf(header, &redacted_header);
        }
  }
  
@@@ -698,32 -645,24 +698,32 @@@ static int curl_trace(CURL *handle, cur
                curl_dump_header(text, (unsigned char *)data, size, DO_FILTER);
                break;
        case CURLINFO_DATA_OUT:
 -              text = "=> Send data";
 -              curl_dump_data(text, (unsigned char *)data, size);
 +              if (trace_curl_data) {
 +                      text = "=> Send data";
 +                      curl_dump_data(text, (unsigned char *)data, size);
 +              }
                break;
        case CURLINFO_SSL_DATA_OUT:
 -              text = "=> Send SSL data";
 -              curl_dump_data(text, (unsigned char *)data, size);
 +              if (trace_curl_data) {
 +                      text = "=> Send SSL data";
 +                      curl_dump_data(text, (unsigned char *)data, size);
 +              }
                break;
        case CURLINFO_HEADER_IN:
                text = "<= Recv header";
                curl_dump_header(text, (unsigned char *)data, size, NO_FILTER);
                break;
        case CURLINFO_DATA_IN:
 -              text = "<= Recv data";
 -              curl_dump_data(text, (unsigned char *)data, size);
 +              if (trace_curl_data) {
 +                      text = "<= Recv data";
 +                      curl_dump_data(text, (unsigned char *)data, size);
 +              }
                break;
        case CURLINFO_SSL_DATA_IN:
 -              text = "<= Recv SSL data";
 -              curl_dump_data(text, (unsigned char *)data, size);
 +              if (trace_curl_data) {
 +                      text = "<= Recv SSL data";
 +                      curl_dump_data(text, (unsigned char *)data, size);
 +              }
                break;
  
        default:                /* we ignore unknown types by default */
@@@ -868,13 -807,6 +868,13 @@@ static CURL *get_curl_handle(void
        if (getenv("GIT_CURL_VERBOSE"))
                curl_easy_setopt(result, CURLOPT_VERBOSE, 1L);
        setup_curl_trace(result);
 +      if (getenv("GIT_TRACE_CURL_NO_DATA"))
 +              trace_curl_data = 0;
 +      if (getenv("GIT_REDACT_COOKIES")) {
 +              string_list_split(&cookies_to_redact,
 +                                getenv("GIT_REDACT_COOKIES"), ',', -1);
 +              string_list_sort(&cookies_to_redact);
 +      }
  
        curl_easy_setopt(result, CURLOPT_USERAGENT,
                user_agent ? user_agent : git_user_agent());
@@@ -972,21 -904,6 +972,6 @@@ static void set_from_env(const char **v
                *var = val;
  }
  
- static void protocol_http_header(void)
- {
-       if (get_protocol_version_config() > 0) {
-               struct strbuf protocol_header = STRBUF_INIT;
-               strbuf_addf(&protocol_header, GIT_PROTOCOL_HEADER ": version=%d",
-                           get_protocol_version_config());
-               extra_http_headers = curl_slist_append(extra_http_headers,
-                                                      protocol_header.buf);
-               strbuf_release(&protocol_header);
-       }
- }
  void http_init(struct remote *remote, const char *url, int proactive_auth)
  {
        char *low_speed_limit;
        if (remote)
                var_override(&http_proxy_authmethod, remote->http_proxy_authmethod);
  
-       protocol_http_header();
        pragma_header = curl_slist_append(http_copy_default_headers(),
                "Pragma: no-cache");
        no_pragma_header = curl_slist_append(http_copy_default_headers(),
@@@ -1262,14 -1177,14 +1245,14 @@@ static struct fill_chain *fill_cfg
  
  void add_fill_function(void *data, int (*fill)(void *))
  {
 -      struct fill_chain *new = xmalloc(sizeof(*new));
 +      struct fill_chain *new_fill = xmalloc(sizeof(*new_fill));
        struct fill_chain **linkp = &fill_cfg;
 -      new->data = data;
 -      new->fill = fill;
 -      new->next = NULL;
 +      new_fill->data = data;
 +      new_fill->fill = fill;
 +      new_fill->next = NULL;
        while (*linkp)
                linkp = &(*linkp)->next;
 -      *linkp = new;
 +      *linkp = new_fill;
  }
  
  void fill_active_slots(void)
@@@ -1791,6 -1706,14 +1774,14 @@@ static int http_request(const char *url
  
        headers = curl_slist_append(headers, buf.buf);
  
+       /* Add additional headers here */
+       if (options && options->extra_headers) {
+               const struct string_list_item *item;
+               for_each_string_list_item(item, options->extra_headers) {
+                       headers = curl_slist_append(headers, item->string);
+               }
+       }
        curl_easy_setopt(slot->curl, CURLOPT_URL, url);
        curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, headers);
        curl_easy_setopt(slot->curl, CURLOPT_ENCODING, "gzip");
@@@ -2098,6 -2021,7 +2089,6 @@@ int finish_http_pack_request(struct htt
        char *tmp_idx;
        size_t len;
        struct child_process ip = CHILD_PROCESS_INIT;
 -      const char *ip_argv[8];
  
        close_pack_index(p);
  
                die("BUG: pack tmpfile does not end in .pack.temp?");
        tmp_idx = xstrfmt("%.*s.idx.temp", (int)len, preq->tmpfile);
  
 -      ip_argv[0] = "index-pack";
 -      ip_argv[1] = "-o";
 -      ip_argv[2] = tmp_idx;
 -      ip_argv[3] = preq->tmpfile;
 -      ip_argv[4] = NULL;
 -
 -      ip.argv = ip_argv;
 +      argv_array_push(&ip.args, "index-pack");
 +      argv_array_pushl(&ip.args, "-o", tmp_idx, NULL);
 +      argv_array_push(&ip.args, preq->tmpfile);
        ip.git_cmd = 1;
        ip.no_stdin = 1;
        ip.no_stdout = 1;
@@@ -2236,7 -2164,7 +2227,7 @@@ struct http_object_request *new_http_ob
        unsigned char *sha1)
  {
        char *hex = sha1_to_hex(sha1);
 -      const char *filename;
 +      struct strbuf filename = STRBUF_INIT;
        char prevfile[PATH_MAX];
        int prevlocal;
        char prev_buf[PREV_BUF_SIZE];
        hashcpy(freq->sha1, sha1);
        freq->localfile = -1;
  
 -      filename = sha1_file_name(sha1);
 +      sha1_file_name(&filename, sha1);
        snprintf(freq->tmpfile, sizeof(freq->tmpfile),
 -               "%s.temp", filename);
 +               "%s.temp", filename.buf);
  
 -      snprintf(prevfile, sizeof(prevfile), "%s.prev", filename);
 +      snprintf(prevfile, sizeof(prevfile), "%s.prev", filename.buf);
        unlink_or_warn(prevfile);
        rename(freq->tmpfile, prevfile);
        unlink_or_warn(freq->tmpfile);
 +      strbuf_release(&filename);
  
        if (freq->localfile != -1)
                error("fd leakage in start: %d", freq->localfile);
@@@ -2371,7 -2298,6 +2362,7 @@@ void process_http_object_request(struc
  int finish_http_object_request(struct http_object_request *freq)
  {
        struct stat st;
 +      struct strbuf filename = STRBUF_INIT;
  
        close(freq->localfile);
        freq->localfile = -1;
                unlink_or_warn(freq->tmpfile);
                return -1;
        }
 -      freq->rename =
 -              finalize_object_file(freq->tmpfile, sha1_file_name(freq->sha1));
 +
 +      sha1_file_name(&filename, freq->sha1);
 +      freq->rename = finalize_object_file(freq->tmpfile, filename.buf);
 +      strbuf_release(&filename);
  
        return freq->rename;
  }
diff --combined refs.c
index 8b7a77fe5eedb08c0b034b1cf3bb4ef40efa9834,cefbad20763ef346cb697c0b84f947f494f8a90a..10f69da2b859140e5d9bc61b395f737b2458dc6b
--- 1/refs.c
--- 2/refs.c
+++ b/refs.c
@@@ -13,6 -13,7 +13,7 @@@
  #include "tag.h"
  #include "submodule.h"
  #include "worktree.h"
+ #include "argv-array.h"
  
  /*
   * List of all available backends
@@@ -301,7 -302,7 +302,7 @@@ enum peel_status peel_object(const stru
        struct object *o = lookup_unknown_object(name->hash);
  
        if (o->type == OBJ_NONE) {
 -              int type = sha1_object_info(name->hash, NULL);
 +              int type = oid_object_info(name, NULL);
                if (type < 0 || !object_as_type(o, type, 0))
                        return PEEL_INVALID;
        }
@@@ -501,6 -502,19 +502,19 @@@ int refname_match(const char *abbrev_na
        return 0;
  }
  
+ /*
+  * Given a 'prefix' expand it by the rules in 'ref_rev_parse_rules' and add
+  * the results to 'prefixes'
+  */
+ void expand_ref_prefix(struct argv_array *prefixes, const char *prefix)
+ {
+       const char **p;
+       int len = strlen(prefix);
+       for (p = ref_rev_parse_rules; *p; p++)
+               argv_array_pushf(prefixes, *p, len, prefix);
+ }
  /*
   * *string and *len will only be substituted, and *string returned (for
   * later free()ing) if the string passed in is a magic short-hand form
diff --combined remote-curl.c
index a7c4c9b5ff4822e36bfc43a59d113c624537297e,595447b16eb8b175f481b1c8b6d0704d4fa3d650..4f779eee54b0836a19131c22d53d143259bd83c2
@@@ -1,6 -1,7 +1,7 @@@
  #include "cache.h"
  #include "config.h"
  #include "remote.h"
+ #include "connect.h"
  #include "strbuf.h"
  #include "walker.h"
  #include "http.h"
@@@ -13,7 -14,7 +14,8 @@@
  #include "credential.h"
  #include "sha1-array.h"
  #include "send-pack.h"
+ #include "protocol.h"
 +#include "quote.h"
  
  static struct remote *remote;
  /* always ends with a trailing slash */
@@@ -25,7 -26,6 +27,7 @@@ struct options 
        char *deepen_since;
        struct string_list deepen_not;
        struct string_list push_options;
 +      char *filter;
        unsigned progress : 1,
                check_self_contained_and_connected : 1,
                cloning : 1,
@@@ -35,9 -35,7 +37,9 @@@
                thin : 1,
                /* One of the SEND_PACK_PUSH_CERT_* constants. */
                push_cert : 2,
 -              deepen_relative : 1;
 +              deepen_relative : 1,
 +              from_promisor : 1,
 +              no_dependents : 1;
  };
  static struct options options;
  static struct string_list cas_options = STRING_LIST_INIT_DUP;
@@@ -146,15 -144,7 +148,15 @@@ static int set_option(const char *name
                        return -1;
                return 0;
        } else if (!strcmp(name, "push-option")) {
 -              string_list_append(&options.push_options, value);
 +              if (*value != '"')
 +                      string_list_append(&options.push_options, value);
 +              else {
 +                      struct strbuf unquoted = STRBUF_INIT;
 +                      if (unquote_c_style(&unquoted, value, NULL) < 0)
 +                              die("invalid quoting in push-option value");
 +                      string_list_append_nodup(&options.push_options,
 +                                               strbuf_detach(&unquoted, NULL));
 +              }
                return 0;
  
  #if LIBCURL_VERSION_NUM >= 0x070a08
                        return -1;
                return 0;
  #endif /* LIBCURL_VERSION_NUM >= 0x070a08 */
 +      } else if (!strcmp(name, "from-promisor")) {
 +              options.from_promisor = 1;
 +              return 0;
 +      } else if (!strcmp(name, "no-dependents")) {
 +              options.no_dependents = 1;
 +              return 0;
 +      } else if (!strcmp(name, "filter")) {
 +              options.filter = xstrdup(value);;
 +              return 0;
        } else {
                return 1 /* unsupported */;
        }
  }
  
  struct discovery {
-       const char *service;
+       char *service;
        char *buf_alloc;
        char *buf;
        size_t len;
        struct ref *refs;
        struct oid_array shallow;
+       enum protocol_version version;
        unsigned proto_git : 1;
  };
  static struct discovery *last_discovery;
  static struct ref *parse_git_refs(struct discovery *heads, int for_push)
  {
        struct ref *list = NULL;
-       get_remote_heads(-1, heads->buf, heads->len, &list,
-                        for_push ? REF_NORMAL : 0, NULL, &heads->shallow);
+       struct packet_reader reader;
+       packet_reader_init(&reader, -1, heads->buf, heads->len,
+                          PACKET_READ_CHOMP_NEWLINE |
+                          PACKET_READ_GENTLE_ON_EOF);
+       heads->version = discover_version(&reader);
+       switch (heads->version) {
+       case protocol_v2:
+               /*
+                * Do nothing.  This isn't a list of refs but rather a
+                * capability advertisement.  Client would have run
+                * 'stateless-connect' so we'll dump this capability listing
+                * and let them request the refs themselves.
+                */
+               break;
+       case protocol_v1:
+       case protocol_v0:
+               get_remote_heads(&reader, &list, for_push ? REF_NORMAL : 0,
+                                NULL, &heads->shallow);
+               break;
+       case protocol_unknown_version:
+               BUG("unknown protocol version");
+       }
        return list;
  }
  
@@@ -259,6 -264,7 +285,7 @@@ static void free_discovery(struct disco
                free(d->shallow.oid);
                free(d->buf_alloc);
                free_refs(d->refs);
+               free(d->service);
                free(d);
        }
  }
@@@ -290,6 -296,19 +317,19 @@@ static int show_http_message(struct str
        return 0;
  }
  
+ static int get_protocol_http_header(enum protocol_version version,
+                                   struct strbuf *header)
+ {
+       if (version > 0) {
+               strbuf_addf(header, GIT_PROTOCOL_HEADER ": version=%d",
+                           version);
+               return 1;
+       }
+       return 0;
+ }
  static struct discovery *discover_refs(const char *service, int for_push)
  {
        struct strbuf exp = STRBUF_INIT;
        struct strbuf buffer = STRBUF_INIT;
        struct strbuf refs_url = STRBUF_INIT;
        struct strbuf effective_url = STRBUF_INIT;
+       struct strbuf protocol_header = STRBUF_INIT;
+       struct string_list extra_headers = STRING_LIST_INIT_DUP;
        struct discovery *last = last_discovery;
        int http_ret, maybe_smart = 0;
        struct http_get_options http_options;
+       enum protocol_version version = get_protocol_version_config();
  
        if (last && !strcmp(service, last->service))
                return last;
                strbuf_addf(&refs_url, "service=%s", service);
        }
  
+       /*
+        * NEEDSWORK: If we are trying to use protocol v2 and we are planning
+        * to perform a push, then fallback to v0 since the client doesn't know
+        * how to push yet using v2.
+        */
+       if (version == protocol_v2 && !strcmp("git-receive-pack", service))
+               version = protocol_v0;
+       /* Add the extra Git-Protocol header */
+       if (get_protocol_http_header(version, &protocol_header))
+               string_list_append(&extra_headers, protocol_header.buf);
        memset(&http_options, 0, sizeof(http_options));
        http_options.content_type = &type;
        http_options.charset = &charset;
        http_options.effective_url = &effective_url;
        http_options.base_url = &url;
+       http_options.extra_headers = &extra_headers;
        http_options.initial_request = 1;
        http_options.no_cache = 1;
        http_options.keep_error = 1;
                warning(_("redirecting to %s"), url.buf);
  
        last= xcalloc(1, sizeof(*last_discovery));
-       last->service = service;
+       last->service = xstrdup(service);
        last->buf_alloc = strbuf_detach(&buffer, &last->len);
        last->buf = last->buf_alloc;
  
                 * pkt-line matches our request.
                 */
                line = packet_read_line_buf(&last->buf, &last->len, NULL);
 +              if (!line)
 +                      die("invalid server response; expected service, got flush packet");
  
                strbuf_reset(&exp);
                strbuf_addf(&exp, "# service=%s", service);
                        ;
  
                last->proto_git = 1;
+       } else if (maybe_smart &&
+                  last->len > 5 && starts_with(last->buf + 4, "version 2")) {
+               last->proto_git = 1;
        }
  
        if (last->proto_git)
        strbuf_release(&charset);
        strbuf_release(&effective_url);
        strbuf_release(&buffer);
+       strbuf_release(&protocol_header);
+       string_list_clear(&extra_headers, 0);
        last_discovery = last;
        return last;
  }
@@@ -426,6 -464,7 +487,7 @@@ struct rpc_state 
        char *service_url;
        char *hdr_content_type;
        char *hdr_accept;
+       char *protocol_header;
        char *buf;
        size_t alloc;
        size_t len;
@@@ -612,6 -651,10 +674,10 @@@ static int post_rpc(struct rpc_state *r
        headers = curl_slist_append(headers, needs_100_continue ?
                "Expect: 100-continue" : "Expect:");
  
+       /* Add the extra Git-Protocol header */
+       if (rpc->protocol_header)
+               headers = curl_slist_append(headers, rpc->protocol_header);
  retry:
        slot = get_active_slot();
  
@@@ -752,6 -795,11 +818,11 @@@ static int rpc_service(struct rpc_stat
        strbuf_addf(&buf, "Accept: application/x-%s-result", svc);
        rpc->hdr_accept = strbuf_detach(&buf, NULL);
  
+       if (get_protocol_http_header(heads->version, &buf))
+               rpc->protocol_header = strbuf_detach(&buf, NULL);
+       else
+               rpc->protocol_header = NULL;
        while (!err) {
                int n = packet_read(rpc->out, NULL, NULL, rpc->buf, rpc->alloc, 0);
                if (!n)
        free(rpc->service_url);
        free(rpc->hdr_content_type);
        free(rpc->hdr_accept);
+       free(rpc->protocol_header);
        free(rpc->buf);
        strbuf_release(&buf);
        return err;
@@@ -845,12 -894,6 +917,12 @@@ static int fetch_git(struct discovery *
                                 options.deepen_not.items[i].string);
        if (options.deepen_relative && options.depth)
                argv_array_push(&args, "--deepen-relative");
 +      if (options.from_promisor)
 +              argv_array_push(&args, "--from-promisor");
 +      if (options.no_dependents)
 +              argv_array_push(&args, "--no-dependents");
 +      if (options.filter)
 +              argv_array_pushf(&args, "--filter=%s", options.filter);
        argv_array_push(&args, url.buf);
  
        for (i = 0; i < nr_heads; i++) {
@@@ -1056,6 -1099,202 +1128,202 @@@ static void parse_push(struct strbuf *b
        free(specs);
  }
  
+ /*
+  * Used to represent the state of a connection to an HTTP server when
+  * communicating using git's wire-protocol version 2.
+  */
+ struct proxy_state {
+       char *service_name;
+       char *service_url;
+       struct curl_slist *headers;
+       struct strbuf request_buffer;
+       int in;
+       int out;
+       struct packet_reader reader;
+       size_t pos;
+       int seen_flush;
+ };
+ static void proxy_state_init(struct proxy_state *p, const char *service_name,
+                            enum protocol_version version)
+ {
+       struct strbuf buf = STRBUF_INIT;
+       memset(p, 0, sizeof(*p));
+       p->service_name = xstrdup(service_name);
+       p->in = 0;
+       p->out = 1;
+       strbuf_init(&p->request_buffer, 0);
+       strbuf_addf(&buf, "%s%s", url.buf, p->service_name);
+       p->service_url = strbuf_detach(&buf, NULL);
+       p->headers = http_copy_default_headers();
+       strbuf_addf(&buf, "Content-Type: application/x-%s-request", p->service_name);
+       p->headers = curl_slist_append(p->headers, buf.buf);
+       strbuf_reset(&buf);
+       strbuf_addf(&buf, "Accept: application/x-%s-result", p->service_name);
+       p->headers = curl_slist_append(p->headers, buf.buf);
+       strbuf_reset(&buf);
+       p->headers = curl_slist_append(p->headers, "Transfer-Encoding: chunked");
+       /* Add the Git-Protocol header */
+       if (get_protocol_http_header(version, &buf))
+               p->headers = curl_slist_append(p->headers, buf.buf);
+       packet_reader_init(&p->reader, p->in, NULL, 0,
+                          PACKET_READ_GENTLE_ON_EOF);
+       strbuf_release(&buf);
+ }
+ static void proxy_state_clear(struct proxy_state *p)
+ {
+       free(p->service_name);
+       free(p->service_url);
+       curl_slist_free_all(p->headers);
+       strbuf_release(&p->request_buffer);
+ }
+ /*
+  * CURLOPT_READFUNCTION callback function.
+  * Attempts to copy over a single packet-line at a time into the
+  * curl provided buffer.
+  */
+ static size_t proxy_in(char *buffer, size_t eltsize,
+                      size_t nmemb, void *userdata)
+ {
+       size_t max;
+       struct proxy_state *p = userdata;
+       size_t avail = p->request_buffer.len - p->pos;
+       if (eltsize != 1)
+               BUG("curl read callback called with size = %"PRIuMAX" != 1",
+                   (uintmax_t)eltsize);
+       max = nmemb;
+       if (!avail) {
+               if (p->seen_flush) {
+                       p->seen_flush = 0;
+                       return 0;
+               }
+               strbuf_reset(&p->request_buffer);
+               switch (packet_reader_read(&p->reader)) {
+               case PACKET_READ_EOF:
+                       die("unexpected EOF when reading from parent process");
+               case PACKET_READ_NORMAL:
+                       packet_buf_write_len(&p->request_buffer, p->reader.line,
+                                            p->reader.pktlen);
+                       break;
+               case PACKET_READ_DELIM:
+                       packet_buf_delim(&p->request_buffer);
+                       break;
+               case PACKET_READ_FLUSH:
+                       packet_buf_flush(&p->request_buffer);
+                       p->seen_flush = 1;
+                       break;
+               }
+               p->pos = 0;
+               avail = p->request_buffer.len;
+       }
+       if (max < avail)
+               avail = max;
+       memcpy(buffer, p->request_buffer.buf + p->pos, avail);
+       p->pos += avail;
+       return avail;
+ }
+ static size_t proxy_out(char *buffer, size_t eltsize,
+                       size_t nmemb, void *userdata)
+ {
+       size_t size;
+       struct proxy_state *p = userdata;
+       if (eltsize != 1)
+               BUG("curl read callback called with size = %"PRIuMAX" != 1",
+                   (uintmax_t)eltsize);
+       size = nmemb;
+       write_or_die(p->out, buffer, size);
+       return size;
+ }
+ /* Issues a request to the HTTP server configured in `p` */
+ static int proxy_request(struct proxy_state *p)
+ {
+       struct active_request_slot *slot;
+       slot = get_active_slot();
+       curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0);
+       curl_easy_setopt(slot->curl, CURLOPT_POST, 1);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, p->service_url);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, p->headers);
+       /* Setup function to read request from client */
+       curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, proxy_in);
+       curl_easy_setopt(slot->curl, CURLOPT_READDATA, p);
+       /* Setup function to write server response to client */
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, proxy_out);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEDATA, p);
+       if (run_slot(slot, NULL) != HTTP_OK)
+               return -1;
+       return 0;
+ }
+ static int stateless_connect(const char *service_name)
+ {
+       struct discovery *discover;
+       struct proxy_state p;
+       /*
+        * Run the info/refs request and see if the server supports protocol
+        * v2.  If and only if the server supports v2 can we successfully
+        * establish a stateless connection, otherwise we need to tell the
+        * client to fallback to using other transport helper functions to
+        * complete their request.
+        */
+       discover = discover_refs(service_name, 0);
+       if (discover->version != protocol_v2) {
+               printf("fallback\n");
+               fflush(stdout);
+               return -1;
+       } else {
+               /* Stateless Connection established */
+               printf("\n");
+               fflush(stdout);
+       }
+       proxy_state_init(&p, service_name, discover->version);
+       /*
+        * Dump the capability listing that we got from the server earlier
+        * during the info/refs request.
+        */
+       write_or_die(p.out, discover->buf, discover->len);
+       /* Peek the next packet line.  Until we see EOF keep sending POSTs */
+       while (packet_reader_peek(&p.reader) != PACKET_READ_EOF) {
+               if (proxy_request(&p)) {
+                       /* We would have an err here */
+                       break;
+               }
+       }
+       proxy_state_clear(&p);
+       return 0;
+ }
  int cmd_main(int argc, const char **argv)
  {
        struct strbuf buf = STRBUF_INIT;
                        fflush(stdout);
  
                } else if (!strcmp(buf.buf, "capabilities")) {
+                       printf("stateless-connect\n");
                        printf("fetch\n");
                        printf("option\n");
                        printf("push\n");
                        printf("check-connectivity\n");
                        printf("\n");
                        fflush(stdout);
+               } else if (skip_prefix(buf.buf, "stateless-connect ", &arg)) {
+                       if (!stateless_connect(arg))
+                               break;
                } else {
                        error("remote-curl: unknown command '%s' from git", buf.buf);
                        return 1;
diff --combined remote.h
index f09c01969d6b0d701140ceb9cb2e8f9e68533c96,368ba221cc9092025f61b7edfbafe29b345c0645..2b3180f94dbc185a315255938c2de6cc6aef0b13
+++ b/remote.h
@@@ -47,7 -47,6 +47,7 @@@ struct remote 
        int skip_default_update;
        int mirror;
        int prune;
 +      int prune_tags;
  
        const char *receivepack;
        const char *uploadpack;
@@@ -151,10 -150,17 +151,17 @@@ int check_ref_type(const struct ref *re
  void free_refs(struct ref *ref);
  
  struct oid_array;
- extern struct ref **get_remote_heads(int in, char *src_buf, size_t src_len,
+ struct packet_reader;
+ struct argv_array;
+ extern struct ref **get_remote_heads(struct packet_reader *reader,
                                     struct ref **list, unsigned int flags,
                                     struct oid_array *extra_have,
-                                    struct oid_array *shallow);
+                                    struct oid_array *shallow_points);
+ /* Used for protocol v2 in order to retrieve refs from a remote */
+ extern struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,
+                                   struct ref **list, int for_push,
+                                   const struct argv_array *ref_prefixes);
  
  int resolve_remote_symref(struct ref *ref, struct ref *list);
  int ref_newer(const struct object_id *new_oid, const struct object_id *old_oid);
@@@ -258,18 -264,10 +265,18 @@@ enum match_refs_flags 
        MATCH_REFS_FOLLOW_TAGS  = (1 << 3)
  };
  
 +/* Flags for --ahead-behind option. */
 +enum ahead_behind_flags {
 +      AHEAD_BEHIND_UNSPECIFIED = -1,
 +      AHEAD_BEHIND_QUICK       =  0,  /* just eq/neq reporting */
 +      AHEAD_BEHIND_FULL        =  1,  /* traditional a/b reporting */
 +};
 +
  /* Reporting of tracking info */
  int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
 -                     const char **upstream_name);
 -int format_tracking_info(struct branch *branch, struct strbuf *sb);
 +                     const char **upstream_name, enum ahead_behind_flags abf);
 +int format_tracking_info(struct branch *branch, struct strbuf *sb,
 +                       enum ahead_behind_flags abf);
  
  struct ref *get_local_heads(void);
  /*
@@@ -306,8 -304,4 +313,8 @@@ extern int parseopt_push_cas_option(con
  extern int is_empty_cas(const struct push_cas_option *);
  void apply_push_cas(struct push_cas_option *, struct remote *, struct ref *);
  
 +#define TAG_REFSPEC "refs/tags/*:refs/tags/*"
 +
 +void add_prune_tags_to_fetch_refspec(struct remote *remote);
 +
  #endif
diff --combined transport-helper.c
index 3f380d87d99eab317d5ac567b43e3cea05885145,aecbc4a845c931efdb41414df4603d8fda5d09b7..11f1055b47e5e204a272e3588f74f161bdf73895
@@@ -12,6 -12,7 +12,7 @@@
  #include "argv-array.h"
  #include "refs.h"
  #include "transport-internal.h"
+ #include "protocol.h"
  
  static int debug;
  
@@@ -26,6 -27,7 +27,7 @@@ struct helper_data 
                option : 1,
                push : 1,
                connect : 1,
+               stateless_connect : 1,
                signed_tags : 1,
                check_connectivity : 1,
                no_disconnect_req : 1,
@@@ -49,7 -51,7 +51,7 @@@ static void sendline(struct helper_dat
                die_errno("Full write to remote helper failed");
  }
  
- static int recvline_fh(FILE *helper, struct strbuf *buffer, const char *name)
+ static int recvline_fh(FILE *helper, struct strbuf *buffer)
  {
        strbuf_reset(buffer);
        if (debug)
@@@ -67,7 -69,7 +69,7 @@@
  
  static int recvline(struct helper_data *helper, struct strbuf *buffer)
  {
-       return recvline_fh(helper->out, buffer, helper->name);
+       return recvline_fh(helper->out, buffer);
  }
  
  static void write_constant(int fd, const char *str)
@@@ -188,6 -190,8 +190,8 @@@ static struct child_process *get_helper
                        refspecs[refspec_nr++] = xstrdup(arg);
                } else if (!strcmp(capname, "connect")) {
                        data->connect = 1;
+               } else if (!strcmp(capname, "stateless-connect")) {
+                       data->stateless_connect = 1;
                } else if (!strcmp(capname, "signed-tags")) {
                        data->signed_tags = 1;
                } else if (skip_prefix(capname, "export-marks ", &arg)) {
@@@ -545,14 -549,13 +549,13 @@@ static int fetch_with_import(struct tra
        return 0;
  }
  
- static int process_connect_service(struct transport *transport,
-                                  const char *name, const char *exec)
+ static int run_connect(struct transport *transport, struct strbuf *cmdbuf)
  {
        struct helper_data *data = transport->data;
-       struct strbuf cmdbuf = STRBUF_INIT;
-       struct child_process *helper;
-       int r, duped, ret = 0;
+       int ret = 0;
+       int duped;
        FILE *input;
+       struct child_process *helper;
  
        helper = get_helper(transport);
  
        input = xfdopen(duped, "r");
        setvbuf(input, NULL, _IONBF, 0);
  
+       sendline(data, cmdbuf);
+       if (recvline_fh(input, cmdbuf))
+               exit(128);
+       if (!strcmp(cmdbuf->buf, "")) {
+               data->no_disconnect_req = 1;
+               if (debug)
+                       fprintf(stderr, "Debug: Smart transport connection "
+                               "ready.\n");
+               ret = 1;
+       } else if (!strcmp(cmdbuf->buf, "fallback")) {
+               if (debug)
+                       fprintf(stderr, "Debug: Falling back to dumb "
+                               "transport.\n");
+       } else {
+               die("Unknown response to connect: %s",
+                       cmdbuf->buf);
+       }
+       fclose(input);
+       return ret;
+ }
+ static int process_connect_service(struct transport *transport,
+                                  const char *name, const char *exec)
+ {
+       struct helper_data *data = transport->data;
+       struct strbuf cmdbuf = STRBUF_INIT;
+       int ret = 0;
        /*
         * Handle --upload-pack and friends. This is fire and forget...
         * just warn if it fails.
         */
        if (strcmp(name, exec)) {
-               r = set_helper_option(transport, "servpath", exec);
+               int r = set_helper_option(transport, "servpath", exec);
                if (r > 0)
                        warning("Setting remote service path not supported by protocol.");
                else if (r < 0)
                        warning("Invalid remote service path.");
        }
  
-       if (data->connect)
+       if (data->connect) {
                strbuf_addf(&cmdbuf, "connect %s\n", name);
-       else
-               goto exit;
-       sendline(data, &cmdbuf);
-       if (recvline_fh(input, &cmdbuf, name))
-               exit(128);
-       if (!strcmp(cmdbuf.buf, "")) {
-               data->no_disconnect_req = 1;
-               if (debug)
-                       fprintf(stderr, "Debug: Smart transport connection "
-                               "ready.\n");
-               ret = 1;
-       } else if (!strcmp(cmdbuf.buf, "fallback")) {
-               if (debug)
-                       fprintf(stderr, "Debug: Falling back to dumb "
-                               "transport.\n");
-       } else
-               die("Unknown response to connect: %s",
-                       cmdbuf.buf);
+               ret = run_connect(transport, &cmdbuf);
+       } else if (data->stateless_connect &&
+                  (get_protocol_version_config() == protocol_v2) &&
+                  !strcmp("git-upload-pack", name)) {
+               strbuf_addf(&cmdbuf, "stateless-connect %s\n", name);
+               ret = run_connect(transport, &cmdbuf);
+               if (ret)
+                       transport->stateless_rpc = 1;
+       }
  
- exit:
        strbuf_release(&cmdbuf);
-       fclose(input);
        return ret;
  }
  
@@@ -672,11 -692,6 +692,11 @@@ static int fetch(struct transport *tran
        if (data->transport_options.update_shallow)
                set_helper_option(transport, "update-shallow", "true");
  
 +      if (data->transport_options.filter_options.choice)
 +              set_helper_option(
 +                      transport, "filter",
 +                      data->transport_options.filter_options.filter_spec);
 +
        if (data->fetch)
                return fetch_with_fetch(transport, nr_heads, to_fetch);
  
@@@ -1031,7 -1046,8 +1051,8 @@@ static int has_attribute(const char *at
        }
  }
  
- static struct ref *get_refs_list(struct transport *transport, int for_push)
+ static struct ref *get_refs_list(struct transport *transport, int for_push,
+                                const struct argv_array *ref_prefixes)
  {
        struct helper_data *data = transport->data;
        struct child_process *helper;
  
        if (process_connect(transport, for_push)) {
                do_take_over(transport);
-               return transport->vtable->get_refs_list(transport, for_push);
+               return transport->vtable->get_refs_list(transport, for_push, ref_prefixes);
        }
  
        if (data->push && for_push)
diff --combined transport.c
index b9dfa11bd2a1f849f2c2d33aa4943a1ea9cb2d8b,342db492cac5dfaec33de407bfca16aa9951781f..90a9eb518de08da730b1eabbc60a532ac70fb682
@@@ -18,6 -18,7 +18,7 @@@
  #include "sha1-array.h"
  #include "sigchain.h"
  #include "transport-internal.h"
+ #include "protocol.h"
  
  static void set_upstreams(struct transport *transport, struct ref *refs,
        int pretend)
@@@ -71,7 -72,9 +72,9 @@@ struct bundle_transport_data 
        struct bundle_header header;
  };
  
- static struct ref *get_refs_from_bundle(struct transport *transport, int for_push)
+ static struct ref *get_refs_from_bundle(struct transport *transport,
+                                       int for_push,
+                                       const struct argv_array *ref_prefixes)
  {
        struct bundle_transport_data *data = transport->data;
        struct ref *result = NULL;
@@@ -117,6 -120,7 +120,7 @@@ struct git_transport_data 
        struct child_process *conn;
        int fd[2];
        unsigned got_remote_heads : 1;
+       enum protocol_version version;
        struct oid_array extra_have;
        struct oid_array shallow;
  };
@@@ -161,15 -165,6 +165,15 @@@ static int set_git_option(struct git_tr
        } else if (!strcmp(name, TRANS_OPT_DEEPEN_RELATIVE)) {
                opts->deepen_relative = !!value;
                return 0;
 +      } else if (!strcmp(name, TRANS_OPT_FROM_PROMISOR)) {
 +              opts->from_promisor = !!value;
 +              return 0;
 +      } else if (!strcmp(name, TRANS_OPT_NO_DEPENDENTS)) {
 +              opts->no_dependents = !!value;
 +              return 0;
 +      } else if (!strcmp(name, TRANS_OPT_LIST_OBJECTS_FILTER)) {
 +              parse_list_objects_filter(&opts->filter_options, value);
 +              return 0;
        }
        return 1;
  }
@@@ -196,16 -191,35 +200,35 @@@ static int connect_setup(struct transpo
        return 0;
  }
  
- static struct ref *get_refs_via_connect(struct transport *transport, int for_push)
+ static struct ref *get_refs_via_connect(struct transport *transport, int for_push,
+                                       const struct argv_array *ref_prefixes)
  {
        struct git_transport_data *data = transport->data;
-       struct ref *refs;
+       struct ref *refs = NULL;
+       struct packet_reader reader;
  
        connect_setup(transport, for_push);
-       get_remote_heads(data->fd[0], NULL, 0, &refs,
-                        for_push ? REF_NORMAL : 0,
-                        &data->extra_have,
-                        &data->shallow);
+       packet_reader_init(&reader, data->fd[0], NULL, 0,
+                          PACKET_READ_CHOMP_NEWLINE |
+                          PACKET_READ_GENTLE_ON_EOF);
+       data->version = discover_version(&reader);
+       switch (data->version) {
+       case protocol_v2:
+               get_remote_refs(data->fd[1], &reader, &refs, for_push,
+                               ref_prefixes);
+               break;
+       case protocol_v1:
+       case protocol_v0:
+               get_remote_heads(&reader, &refs,
+                                for_push ? REF_NORMAL : 0,
+                                &data->extra_have,
+                                &data->shallow);
+               break;
+       case protocol_unknown_version:
+               BUG("unknown protocol version");
+       }
        data->got_remote_heads = 1;
  
        return refs;
@@@ -216,7 -230,7 +239,7 @@@ static int fetch_refs_via_pack(struct t
  {
        int ret = 0;
        struct git_transport_data *data = transport->data;
-       struct ref *refs;
+       struct ref *refs = NULL;
        char *dest = xstrdup(transport->url);
        struct fetch_pack_args args;
        struct ref *refs_tmp = NULL;
                data->options.check_self_contained_and_connected;
        args.cloning = transport->cloning;
        args.update_shallow = data->options.update_shallow;
 +      args.from_promisor = data->options.from_promisor;
 +      args.no_dependents = data->options.no_dependents;
 +      args.filter_options = data->options.filter_options;
+       args.stateless_rpc = transport->stateless_rpc;
  
-       if (!data->got_remote_heads) {
-               connect_setup(transport, 0);
-               get_remote_heads(data->fd[0], NULL, 0, &refs_tmp, 0,
-                                NULL, &data->shallow);
-               data->got_remote_heads = 1;
+       if (!data->got_remote_heads)
+               refs_tmp = get_refs_via_connect(transport, 0, NULL);
+       switch (data->version) {
+       case protocol_v2:
+               refs = fetch_pack(&args, data->fd, data->conn,
+                                 refs_tmp ? refs_tmp : transport->remote_refs,
+                                 dest, to_fetch, nr_heads, &data->shallow,
+                                 &transport->pack_lockfile, data->version);
+               break;
+       case protocol_v1:
+       case protocol_v0:
+               refs = fetch_pack(&args, data->fd, data->conn,
+                                 refs_tmp ? refs_tmp : transport->remote_refs,
+                                 dest, to_fetch, nr_heads, &data->shallow,
+                                 &transport->pack_lockfile, data->version);
+               break;
+       case protocol_unknown_version:
+               BUG("unknown protocol version");
        }
  
-       refs = fetch_pack(&args, data->fd, data->conn,
-                         refs_tmp ? refs_tmp : transport->remote_refs,
-                         dest, to_fetch, nr_heads, &data->shallow,
-                         &transport->pack_lockfile);
        close(data->fd[0]);
        close(data->fd[1]);
        if (finish_connect(data->conn))
@@@ -367,7 -389,7 +401,7 @@@ static void print_ok_ref_status(struct 
                char type;
                const char *msg;
  
 -              strbuf_add_unique_abbrev(&quickref, ref->old_oid.hash,
 +              strbuf_add_unique_abbrev(&quickref, &ref->old_oid,
                                         DEFAULT_ABBREV);
                if (ref->forced_update) {
                        strbuf_addstr(&quickref, "...");
                        type = ' ';
                        msg = NULL;
                }
 -              strbuf_add_unique_abbrev(&quickref, ref->new_oid.hash,
 +              strbuf_add_unique_abbrev(&quickref, &ref->new_oid,
                                         DEFAULT_ABBREV);
  
                print_ref_status(type, quickref.buf, ref, ref->peer_ref, msg,
@@@ -461,7 -483,7 +495,7 @@@ static int print_one_push_status(struc
  static int measure_abbrev(const struct object_id *oid, int sofar)
  {
        char hex[GIT_MAX_HEXSZ + 1];
 -      int w = find_unique_abbrev_r(hex, oid->hash, DEFAULT_ABBREV);
 +      int w = find_unique_abbrev_r(hex, oid, DEFAULT_ABBREV);
  
        return (w < sofar) ? sofar : w;
  }
@@@ -551,16 -573,10 +585,10 @@@ static int git_transport_push(struct tr
  {
        struct git_transport_data *data = transport->data;
        struct send_pack_args args;
-       int ret;
-       if (!data->got_remote_heads) {
-               struct ref *tmp_refs;
-               connect_setup(transport, 1);
+       int ret = 0;
  
-               get_remote_heads(data->fd[0], NULL, 0, &tmp_refs, REF_NORMAL,
-                                NULL, &data->shallow);
-               data->got_remote_heads = 1;
-       }
+       if (!data->got_remote_heads)
+               get_refs_via_connect(transport, 1, NULL);
  
        memset(&args, 0, sizeof(args));
        args.send_mirror = !!(flags & TRANSPORT_PUSH_MIRROR);
        else
                args.push_cert = SEND_PACK_PUSH_CERT_NEVER;
  
-       ret = send_pack(&args, data->fd, data->conn, remote_refs,
-                       &data->extra_have);
+       switch (data->version) {
+       case protocol_v2:
+               die("support for protocol v2 not implemented yet");
+               break;
+       case protocol_v1:
+       case protocol_v0:
+               ret = send_pack(&args, data->fd, data->conn, remote_refs,
+                               &data->extra_have);
+               break;
+       case protocol_unknown_version:
+               BUG("unknown protocol version");
+       }
  
        close(data->fd[1]);
        close(data->fd[0]);
@@@ -1006,11 -1032,38 +1044,38 @@@ int transport_push(struct transport *tr
                int porcelain = flags & TRANSPORT_PUSH_PORCELAIN;
                int pretend = flags & TRANSPORT_PUSH_DRY_RUN;
                int push_ret, ret, err;
+               struct refspec *tmp_rs;
+               struct argv_array ref_prefixes = ARGV_ARRAY_INIT;
+               int i;
  
                if (check_push_refs(local_refs, refspec_nr, refspec) < 0)
                        return -1;
  
-               remote_refs = transport->vtable->get_refs_list(transport, 1);
+               tmp_rs = parse_push_refspec(refspec_nr, refspec);
+               for (i = 0; i < refspec_nr; i++) {
+                       const char *prefix = NULL;
+                       if (tmp_rs[i].dst)
+                               prefix = tmp_rs[i].dst;
+                       else if (tmp_rs[i].src && !tmp_rs[i].exact_sha1)
+                               prefix = tmp_rs[i].src;
+                       if (prefix) {
+                               const char *glob = strchr(prefix, '*');
+                               if (glob)
+                                       argv_array_pushf(&ref_prefixes, "%.*s",
+                                                        (int)(glob - prefix),
+                                                        prefix);
+                               else
+                                       expand_ref_prefix(&ref_prefixes, prefix);
+                       }
+               }
+               remote_refs = transport->vtable->get_refs_list(transport, 1,
+                                                              &ref_prefixes);
+               argv_array_clear(&ref_prefixes);
+               free_refspec(refspec_nr, tmp_rs);
  
                if (flags & TRANSPORT_PUSH_ALL)
                        match_flags |= MATCH_REFS_ALL;
        return 1;
  }
  
- const struct ref *transport_get_remote_refs(struct transport *transport)
+ const struct ref *transport_get_remote_refs(struct transport *transport,
+                                           const struct argv_array *ref_prefixes)
  {
        if (!transport->got_remote_refs) {
-               transport->remote_refs = transport->vtable->get_refs_list(transport, 0);
+               transport->remote_refs =
+                       transport->vtable->get_refs_list(transport, 0,
+                                                        ref_prefixes);
                transport->got_remote_refs = 1;
        }
  
diff --combined transport.h
index 3c68d73b215bbabc81a75a810b1083697bfd6329,0e602d4d477b9734d5e6ddec9cc05a19d23658d7..e783cfa07536b5202eaa697e31a765c1847f1dbf
@@@ -4,7 -4,6 +4,7 @@@
  #include "cache.h"
  #include "run-command.h"
  #include "remote.h"
 +#include "list-objects-filter-options.h"
  
  struct string_list;
  
@@@ -16,15 -15,12 +16,15 @@@ struct git_transport_options 
        unsigned self_contained_and_connected : 1;
        unsigned update_shallow : 1;
        unsigned deepen_relative : 1;
 +      unsigned from_promisor : 1;
 +      unsigned no_dependents : 1;
        int depth;
        const char *deepen_since;
        const struct string_list *deepen_not;
        const char *uploadpack;
        const char *receivepack;
        struct push_cas_option *cas;
 +      struct list_objects_filter_options filter_options;
  };
  
  enum transport_family {
@@@ -59,6 -55,12 +59,12 @@@ struct transport 
         */
        unsigned cloning : 1;
  
+       /*
+        * Indicates that the transport is connected via a half-duplex
+        * connection and should operate in stateless-rpc mode.
+        */
+       unsigned stateless_rpc : 1;
        /*
         * These strings will be passed to the {pre, post}-receive hook,
         * on the remote side, if both sides support the push options capability.
@@@ -163,18 -165,6 +169,18 @@@ void transport_check_allowed(const cha
  /* Send push certificates */
  #define TRANS_OPT_PUSH_CERT "pushcert"
  
 +/* Indicate that these objects are being fetched by a promisor */
 +#define TRANS_OPT_FROM_PROMISOR "from-promisor"
 +
 +/*
 + * Indicate that only the objects wanted need to be fetched, not their
 + * dependents
 + */
 +#define TRANS_OPT_NO_DEPENDENTS "no-dependents"
 +
 +/* Filter objects for partial clone and fetch */
 +#define TRANS_OPT_LIST_OBJECTS_FILTER "filter"
 +
  /**
   * Returns 0 if the option was used, non-zero otherwise. Prints a
   * message to stderr if the option is not used.
@@@ -194,7 -184,17 +200,17 @@@ int transport_push(struct transport *co
                   int refspec_nr, const char **refspec, int flags,
                   unsigned int * reject_reasons);
  
- const struct ref *transport_get_remote_refs(struct transport *transport);
+ /*
+  * Retrieve refs from a remote.
+  *
+  * Optionally a list of ref prefixes can be provided which can be sent to the
+  * server (when communicating using protocol v2) to enable it to limit the ref
+  * advertisement.  Since ref filtering is done on the server's end (and only
+  * when using protocol v2), this can return refs which don't match the provided
+  * ref_prefixes.
+  */
+ const struct ref *transport_get_remote_refs(struct transport *transport,
+                                           const struct argv_array *ref_prefixes);
  
  int transport_fetch_refs(struct transport *transport, struct ref *refs);
  void transport_unlock_pack(struct transport *transport);
diff --combined upload-pack.c
index 4a82602be5d0ab111a805a2f2456f382ae7a7364,4c9428c2db64daa2cde9225419ff88d4e2f4fbbb..87b4d32a6e23aed2416a9bb752dfa56b916b141c
@@@ -6,27 -6,19 +6,22 @@@
  #include "tag.h"
  #include "object.h"
  #include "commit.h"
- #include "exec_cmd.h"
  #include "diff.h"
  #include "revision.h"
  #include "list-objects.h"
 +#include "list-objects-filter.h"
 +#include "list-objects-filter-options.h"
  #include "run-command.h"
  #include "connect.h"
  #include "sigchain.h"
  #include "version.h"
  #include "string-list.h"
- #include "parse-options.h"
  #include "argv-array.h"
  #include "prio-queue.h"
  #include "protocol.h"
- static const char * const upload_pack_usage[] = {
-       N_("git upload-pack [<options>] <dir>"),
-       NULL
- };
 +#include "quote.h"
+ #include "upload-pack.h"
+ #include "serve.h"
  
  /* Remember to update object flag allocation in object.h */
  #define THEY_HAVE     (1u << 11)
@@@ -64,14 -56,9 +59,13 @@@ static int keepalive = 5
   * otherwise maximum packet size (up to 65520 bytes).
   */
  static int use_sideband;
- static int advertise_refs;
  static int stateless_rpc;
  static const char *pack_objects_hook;
  
 +static int filter_capability_requested;
 +static int allow_filter;
 +static struct list_objects_filter_options filter_options;
 +
  static void reset_timeout(void)
  {
        alarm(timeout);
@@@ -139,17 -126,6 +133,17 @@@ static void create_pack_file(void
                argv_array_push(&pack_objects.args, "--delta-base-offset");
        if (use_include_tag)
                argv_array_push(&pack_objects.args, "--include-tag");
 +      if (filter_options.filter_spec) {
 +              if (pack_objects.use_shell) {
 +                      struct strbuf buf = STRBUF_INIT;
 +                      sq_quote_buf(&buf, filter_options.filter_spec);
 +                      argv_array_pushf(&pack_objects.args, "--filter=%s", buf.buf);
 +                      strbuf_release(&buf);
 +              } else {
 +                      argv_array_pushf(&pack_objects.args, "--filter=%s",
 +                                       filter_options.filter_spec);
 +              }
 +      }
  
        pack_objects.in = -1;
        pack_objects.out = -1;
@@@ -734,7 -710,6 +728,6 @@@ static void deepen(int depth, int deepe
        }
  
        send_unshallow(shallows);
-       packet_flush(1);
  }
  
  static void deepen_by_rev_list(int ac, const char **av,
        send_shallow(result);
        free_commit_list(result);
        send_unshallow(shallows);
-       packet_flush(1);
+ }
+ /* Returns 1 if a shallow list is sent or 0 otherwise */
+ static int send_shallow_list(int depth, int deepen_rev_list,
+                            timestamp_t deepen_since,
+                            struct string_list *deepen_not,
+                            struct object_array *shallows)
+ {
+       int ret = 0;
+       if (depth > 0 && deepen_rev_list)
+               die("git upload-pack: deepen and deepen-since (or deepen-not) cannot be used together");
+       if (depth > 0) {
+               deepen(depth, deepen_relative, shallows);
+               ret = 1;
+       } else if (deepen_rev_list) {
+               struct argv_array av = ARGV_ARRAY_INIT;
+               int i;
+               argv_array_push(&av, "rev-list");
+               if (deepen_since)
+                       argv_array_pushf(&av, "--max-age=%"PRItime, deepen_since);
+               if (deepen_not->nr) {
+                       argv_array_push(&av, "--not");
+                       for (i = 0; i < deepen_not->nr; i++) {
+                               struct string_list_item *s = deepen_not->items + i;
+                               argv_array_push(&av, s->string);
+                       }
+                       argv_array_push(&av, "--not");
+               }
+               for (i = 0; i < want_obj.nr; i++) {
+                       struct object *o = want_obj.objects[i].item;
+                       argv_array_push(&av, oid_to_hex(&o->oid));
+               }
+               deepen_by_rev_list(av.argc, av.argv, shallows);
+               argv_array_clear(&av);
+               ret = 1;
+       } else {
+               if (shallows->nr > 0) {
+                       int i;
+                       for (i = 0; i < shallows->nr; i++)
+                               register_shallow(&shallows->objects[i].item->oid);
+               }
+       }
+       shallow_nr += shallows->nr;
+       return ret;
+ }
+ static int process_shallow(const char *line, struct object_array *shallows)
+ {
+       const char *arg;
+       if (skip_prefix(line, "shallow ", &arg)) {
+               struct object_id oid;
+               struct object *object;
+               if (get_oid_hex(arg, &oid))
+                       die("invalid shallow line: %s", line);
+               object = parse_object(&oid);
+               if (!object)
+                       return 1;
+               if (object->type != OBJ_COMMIT)
+                       die("invalid shallow object %s", oid_to_hex(&oid));
+               if (!(object->flags & CLIENT_SHALLOW)) {
+                       object->flags |= CLIENT_SHALLOW;
+                       add_object_array(object, NULL, shallows);
+               }
+               return 1;
+       }
+       return 0;
+ }
+ static int process_deepen(const char *line, int *depth)
+ {
+       const char *arg;
+       if (skip_prefix(line, "deepen ", &arg)) {
+               char *end = NULL;
+               *depth = (int)strtol(arg, &end, 0);
+               if (!end || *end || *depth <= 0)
+                       die("Invalid deepen: %s", line);
+               return 1;
+       }
+       return 0;
+ }
+ static int process_deepen_since(const char *line, timestamp_t *deepen_since, int *deepen_rev_list)
+ {
+       const char *arg;
+       if (skip_prefix(line, "deepen-since ", &arg)) {
+               char *end = NULL;
+               *deepen_since = parse_timestamp(arg, &end, 0);
+               if (!end || *end || !deepen_since ||
+                   /* revisions.c's max_age -1 is special */
+                   *deepen_since == -1)
+                       die("Invalid deepen-since: %s", line);
+               *deepen_rev_list = 1;
+               return 1;
+       }
+       return 0;
+ }
+ static int process_deepen_not(const char *line, struct string_list *deepen_not, int *deepen_rev_list)
+ {
+       const char *arg;
+       if (skip_prefix(line, "deepen-not ", &arg)) {
+               char *ref = NULL;
+               struct object_id oid;
+               if (expand_ref(arg, strlen(arg), &oid, &ref) != 1)
+                       die("git upload-pack: ambiguous deepen-not: %s", line);
+               string_list_append(deepen_not, ref);
+               free(ref);
+               *deepen_rev_list = 1;
+               return 1;
+       }
+       return 0;
  }
  
  static void receive_needs(void)
                if (!line)
                        break;
  
-               if (skip_prefix(line, "shallow ", &arg)) {
-                       struct object_id oid;
-                       struct object *object;
-                       if (get_oid_hex(arg, &oid))
-                               die("invalid shallow line: %s", line);
-                       object = parse_object(&oid);
-                       if (!object)
-                               continue;
-                       if (object->type != OBJ_COMMIT)
-                               die("invalid shallow object %s", oid_to_hex(&oid));
-                       if (!(object->flags & CLIENT_SHALLOW)) {
-                               object->flags |= CLIENT_SHALLOW;
-                               add_object_array(object, NULL, &shallows);
-                       }
+               if (process_shallow(line, &shallows))
                        continue;
-               }
-               if (skip_prefix(line, "deepen ", &arg)) {
-                       char *end = NULL;
-                       depth = strtol(arg, &end, 0);
-                       if (!end || *end || depth <= 0)
-                               die("Invalid deepen: %s", line);
+               if (process_deepen(line, &depth))
                        continue;
-               }
-               if (skip_prefix(line, "deepen-since ", &arg)) {
-                       char *end = NULL;
-                       deepen_since = parse_timestamp(arg, &end, 0);
-                       if (!end || *end || !deepen_since ||
-                           /* revisions.c's max_age -1 is special */
-                           deepen_since == -1)
-                               die("Invalid deepen-since: %s", line);
-                       deepen_rev_list = 1;
+               if (process_deepen_since(line, &deepen_since, &deepen_rev_list))
                        continue;
-               }
-               if (skip_prefix(line, "deepen-not ", &arg)) {
-                       char *ref = NULL;
-                       struct object_id oid;
-                       if (expand_ref(arg, strlen(arg), &oid, &ref) != 1)
-                               die("git upload-pack: ambiguous deepen-not: %s", line);
-                       string_list_append(&deepen_not, ref);
-                       free(ref);
-                       deepen_rev_list = 1;
+               if (process_deepen_not(line, &deepen_not, &deepen_rev_list))
                        continue;
-               }
 +              if (skip_prefix(line, "filter ", &arg)) {
 +                      if (!filter_capability_requested)
 +                              die("git upload-pack: filtering capability not negotiated");
 +                      parse_list_objects_filter(&filter_options, arg);
 +                      continue;
 +              }
++
                if (!skip_prefix(line, "want ", &arg) ||
                    get_oid_hex(arg, &oid_buf))
                        die("git upload-pack: protocol error, "
                        no_progress = 1;
                if (parse_feature_request(features, "include-tag"))
                        use_include_tag = 1;
 +              if (allow_filter && parse_feature_request(features, "filter"))
 +                      filter_capability_requested = 1;
  
                o = parse_object(&oid_buf);
                if (!o) {
  
        if (depth == 0 && !deepen_rev_list && shallows.nr == 0)
                return;
-       if (depth > 0 && deepen_rev_list)
-               die("git upload-pack: deepen and deepen-since (or deepen-not) cannot be used together");
-       if (depth > 0)
-               deepen(depth, deepen_relative, &shallows);
-       else if (deepen_rev_list) {
-               struct argv_array av = ARGV_ARRAY_INIT;
-               int i;
  
-               argv_array_push(&av, "rev-list");
-               if (deepen_since)
-                       argv_array_pushf(&av, "--max-age=%"PRItime, deepen_since);
-               if (deepen_not.nr) {
-                       argv_array_push(&av, "--not");
-                       for (i = 0; i < deepen_not.nr; i++) {
-                               struct string_list_item *s = deepen_not.items + i;
-                               argv_array_push(&av, s->string);
-                       }
-                       argv_array_push(&av, "--not");
-               }
-               for (i = 0; i < want_obj.nr; i++) {
-                       struct object *o = want_obj.objects[i].item;
-                       argv_array_push(&av, oid_to_hex(&o->oid));
-               }
-               deepen_by_rev_list(av.argc, av.argv, &shallows);
-               argv_array_clear(&av);
-       }
-       else
-               if (shallows.nr > 0) {
-                       int i;
-                       for (i = 0; i < shallows.nr; i++)
-                               register_shallow(&shallows.objects[i].item->oid);
-               }
-       shallow_nr += shallows.nr;
+       if (send_shallow_list(depth, deepen_rev_list, deepen_since,
+                             &deepen_not, &shallows))
+               packet_flush(1);
        object_array_clear(&shallows);
  }
  
@@@ -967,7 -985,7 +1012,7 @@@ static int send_ref(const char *refname
                struct strbuf symref_info = STRBUF_INIT;
  
                format_symref_info(&symref_info, cb_data);
 -              packet_write_fmt(1, "%s %s%c%s%s%s%s%s agent=%s\n",
 +              packet_write_fmt(1, "%s %s%c%s%s%s%s%s%s agent=%s\n",
                             oid_to_hex(oid), refname_nons,
                             0, capabilities,
                             (allow_unadvertised_object_request & ALLOW_TIP_SHA1) ?
                                     " allow-reachable-sha1-in-want" : "",
                             stateless_rpc ? " no-done" : "",
                             symref_info.buf,
 +                           allow_filter ? " filter" : "",
                             git_user_agent_sanitized());
                strbuf_release(&symref_info);
        } else {
@@@ -1004,33 -1021,6 +1049,6 @@@ static int find_symref(const char *refn
        return 0;
  }
  
- static void upload_pack(void)
- {
-       struct string_list symref = STRING_LIST_INIT_DUP;
-       head_ref_namespaced(find_symref, &symref);
-       if (advertise_refs || !stateless_rpc) {
-               reset_timeout();
-               head_ref_namespaced(send_ref, &symref);
-               for_each_namespaced_ref(send_ref, &symref);
-               advertise_shallow_grafts(1);
-               packet_flush(1);
-       } else {
-               head_ref_namespaced(check_ref, NULL);
-               for_each_namespaced_ref(check_ref, NULL);
-       }
-       string_list_clear(&symref, 1);
-       if (advertise_refs)
-               return;
-       receive_needs();
-       if (want_obj.nr) {
-               get_common_commits();
-               create_pack_file();
-       }
- }
  static int upload_pack_config(const char *var, const char *value, void *unused)
  {
        if (!strcmp("uploadpack.allowtipsha1inwant", var)) {
        } else if (current_config_scope() != CONFIG_SCOPE_REPO) {
                if (!strcmp("uploadpack.packobjectshook", var))
                        return git_config_string(&pack_objects_hook, var, value);
 +      } else if (!strcmp("uploadpack.allowfilter", var)) {
 +              allow_filter = git_config_bool(var, value);
        }
        return parse_hide_refs_config(var, value, "uploadpack");
  }
  
int cmd_main(int argc, const char **argv)
void upload_pack(struct upload_pack_options *options)
  {
-       const char *dir;
-       int strict = 0;
-       struct option options[] = {
-               OPT_BOOL(0, "stateless-rpc", &stateless_rpc,
-                        N_("quit after a single request/response exchange")),
-               OPT_BOOL(0, "advertise-refs", &advertise_refs,
-                        N_("exit immediately after initial ref advertisement")),
-               OPT_BOOL(0, "strict", &strict,
-                        N_("do not try <directory>/.git/ if <directory> is no Git directory")),
-               OPT_INTEGER(0, "timeout", &timeout,
-                           N_("interrupt transfer after <n> seconds of inactivity")),
-               OPT_END()
-       };
+       struct string_list symref = STRING_LIST_INIT_DUP;
  
-       packet_trace_identity("upload-pack");
-       check_replace_refs = 0;
+       stateless_rpc = options->stateless_rpc;
+       timeout = options->timeout;
+       daemon_mode = options->daemon_mode;
  
-       argc = parse_options(argc, argv, NULL, options, upload_pack_usage, 0);
+       git_config(upload_pack_config, NULL);
  
-       if (argc != 1)
-               usage_with_options(upload_pack_usage, options);
+       head_ref_namespaced(find_symref, &symref);
  
-       if (timeout)
-               daemon_mode = 1;
+       if (options->advertise_refs || !stateless_rpc) {
+               reset_timeout();
+               head_ref_namespaced(send_ref, &symref);
+               for_each_namespaced_ref(send_ref, &symref);
+               advertise_shallow_grafts(1);
+               packet_flush(1);
+       } else {
+               head_ref_namespaced(check_ref, NULL);
+               for_each_namespaced_ref(check_ref, NULL);
+       }
+       string_list_clear(&symref, 1);
+       if (options->advertise_refs)
+               return;
  
-       setup_path();
+       receive_needs();
+       if (want_obj.nr) {
+               get_common_commits();
+               create_pack_file();
+       }
+ }
  
-       dir = argv[0];
+ struct upload_pack_data {
+       struct object_array wants;
+       struct oid_array haves;
  
-       if (!enter_repo(dir, strict))
-               die("'%s' does not appear to be a git repository", dir);
+       struct object_array shallows;
+       struct string_list deepen_not;
+       int depth;
+       timestamp_t deepen_since;
+       int deepen_rev_list;
+       int deepen_relative;
  
-       git_config(upload_pack_config, NULL);
+       unsigned stateless_rpc : 1;
  
-       switch (determine_protocol_version_server()) {
-       case protocol_v1:
-               /*
-                * v1 is just the original protocol with a version string,
-                * so just fall through after writing the version string.
-                */
-               if (advertise_refs || !stateless_rpc)
-                       packet_write_fmt(1, "version 1\n");
-               /* fallthrough */
-       case protocol_v0:
-               upload_pack();
-               break;
-       case protocol_unknown_version:
-               BUG("unknown protocol version");
+       unsigned use_thin_pack : 1;
+       unsigned use_ofs_delta : 1;
+       unsigned no_progress : 1;
+       unsigned use_include_tag : 1;
+       unsigned done : 1;
+ };
+ static void upload_pack_data_init(struct upload_pack_data *data)
+ {
+       struct object_array wants = OBJECT_ARRAY_INIT;
+       struct oid_array haves = OID_ARRAY_INIT;
+       struct object_array shallows = OBJECT_ARRAY_INIT;
+       struct string_list deepen_not = STRING_LIST_INIT_DUP;
+       memset(data, 0, sizeof(*data));
+       data->wants = wants;
+       data->haves = haves;
+       data->shallows = shallows;
+       data->deepen_not = deepen_not;
+ }
+ static void upload_pack_data_clear(struct upload_pack_data *data)
+ {
+       object_array_clear(&data->wants);
+       oid_array_clear(&data->haves);
+       object_array_clear(&data->shallows);
+       string_list_clear(&data->deepen_not, 0);
+ }
+ static int parse_want(const char *line)
+ {
+       const char *arg;
+       if (skip_prefix(line, "want ", &arg)) {
+               struct object_id oid;
+               struct object *o;
+               if (get_oid_hex(arg, &oid))
+                       die("git upload-pack: protocol error, "
+                           "expected to get oid, not '%s'", line);
+               o = parse_object(&oid);
+               if (!o) {
+                       packet_write_fmt(1,
+                                        "ERR upload-pack: not our ref %s",
+                                        oid_to_hex(&oid));
+                       die("git upload-pack: not our ref %s",
+                           oid_to_hex(&oid));
+               }
+               if (!(o->flags & WANTED)) {
+                       o->flags |= WANTED;
+                       add_object_array(o, NULL, &want_obj);
+               }
+               return 1;
+       }
+       return 0;
+ }
+ static int parse_have(const char *line, struct oid_array *haves)
+ {
+       const char *arg;
+       if (skip_prefix(line, "have ", &arg)) {
+               struct object_id oid;
+               if (get_oid_hex(arg, &oid))
+                       die("git upload-pack: expected SHA1 object, got '%s'", arg);
+               oid_array_append(haves, &oid);
+               return 1;
        }
  
        return 0;
  }
+ static void process_args(struct packet_reader *request,
+                        struct upload_pack_data *data)
+ {
+       while (packet_reader_read(request) != PACKET_READ_FLUSH) {
+               const char *arg = request->line;
+               /* process want */
+               if (parse_want(arg))
+                       continue;
+               /* process have line */
+               if (parse_have(arg, &data->haves))
+                       continue;
+               /* process args like thin-pack */
+               if (!strcmp(arg, "thin-pack")) {
+                       use_thin_pack = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "ofs-delta")) {
+                       use_ofs_delta = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "no-progress")) {
+                       no_progress = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "include-tag")) {
+                       use_include_tag = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "done")) {
+                       data->done = 1;
+                       continue;
+               }
+               /* Shallow related arguments */
+               if (process_shallow(arg, &data->shallows))
+                       continue;
+               if (process_deepen(arg, &data->depth))
+                       continue;
+               if (process_deepen_since(arg, &data->deepen_since,
+                                        &data->deepen_rev_list))
+                       continue;
+               if (process_deepen_not(arg, &data->deepen_not,
+                                      &data->deepen_rev_list))
+                       continue;
+               if (!strcmp(arg, "deepen-relative")) {
+                       data->deepen_relative = 1;
+                       continue;
+               }
+               /* ignore unknown lines maybe? */
+               die("unexpect line: '%s'", arg);
+       }
+ }
+ static int process_haves(struct oid_array *haves, struct oid_array *common)
+ {
+       int i;
+       /* Process haves */
+       for (i = 0; i < haves->nr; i++) {
+               const struct object_id *oid = &haves->oid[i];
+               struct object *o;
+               int we_knew_they_have = 0;
+               if (!has_object_file(oid))
+                       continue;
+               oid_array_append(common, oid);
+               o = parse_object(oid);
+               if (!o)
+                       die("oops (%s)", oid_to_hex(oid));
+               if (o->type == OBJ_COMMIT) {
+                       struct commit_list *parents;
+                       struct commit *commit = (struct commit *)o;
+                       if (o->flags & THEY_HAVE)
+                               we_knew_they_have = 1;
+                       else
+                               o->flags |= THEY_HAVE;
+                       if (!oldest_have || (commit->date < oldest_have))
+                               oldest_have = commit->date;
+                       for (parents = commit->parents;
+                            parents;
+                            parents = parents->next)
+                               parents->item->object.flags |= THEY_HAVE;
+               }
+               if (!we_knew_they_have)
+                       add_object_array(o, NULL, &have_obj);
+       }
+       return 0;
+ }
+ static int send_acks(struct oid_array *acks, struct strbuf *response)
+ {
+       int i;
+       packet_buf_write(response, "acknowledgments\n");
+       /* Send Acks */
+       if (!acks->nr)
+               packet_buf_write(response, "NAK\n");
+       for (i = 0; i < acks->nr; i++) {
+               packet_buf_write(response, "ACK %s\n",
+                                oid_to_hex(&acks->oid[i]));
+       }
+       if (ok_to_give_up()) {
+               /* Send Ready */
+               packet_buf_write(response, "ready\n");
+               return 1;
+       }
+       return 0;
+ }
+ static int process_haves_and_send_acks(struct upload_pack_data *data)
+ {
+       struct oid_array common = OID_ARRAY_INIT;
+       struct strbuf response = STRBUF_INIT;
+       int ret = 0;
+       process_haves(&data->haves, &common);
+       if (data->done) {
+               ret = 1;
+       } else if (send_acks(&common, &response)) {
+               packet_buf_delim(&response);
+               ret = 1;
+       } else {
+               /* Add Flush */
+               packet_buf_flush(&response);
+               ret = 0;
+       }
+       /* Send response */
+       write_or_die(1, response.buf, response.len);
+       strbuf_release(&response);
+       oid_array_clear(&data->haves);
+       oid_array_clear(&common);
+       return ret;
+ }
+ static void send_shallow_info(struct upload_pack_data *data)
+ {
+       /* No shallow info needs to be sent */
+       if (!data->depth && !data->deepen_rev_list && !data->shallows.nr &&
+           !is_repository_shallow())
+               return;
+       packet_write_fmt(1, "shallow-info\n");
+       if (!send_shallow_list(data->depth, data->deepen_rev_list,
+                              data->deepen_since, &data->deepen_not,
+                              &data->shallows) && is_repository_shallow())
+               deepen(INFINITE_DEPTH, data->deepen_relative, &data->shallows);
+       packet_delim(1);
+ }
+ enum fetch_state {
+       FETCH_PROCESS_ARGS = 0,
+       FETCH_SEND_ACKS,
+       FETCH_SEND_PACK,
+       FETCH_DONE,
+ };
+ int upload_pack_v2(struct repository *r, struct argv_array *keys,
+                  struct packet_reader *request)
+ {
+       enum fetch_state state = FETCH_PROCESS_ARGS;
+       struct upload_pack_data data;
+       upload_pack_data_init(&data);
+       use_sideband = LARGE_PACKET_MAX;
+       while (state != FETCH_DONE) {
+               switch (state) {
+               case FETCH_PROCESS_ARGS:
+                       process_args(request, &data);
+                       if (!want_obj.nr) {
+                               /*
+                                * Request didn't contain any 'want' lines,
+                                * guess they didn't want anything.
+                                */
+                               state = FETCH_DONE;
+                       } else if (data.haves.nr) {
+                               /*
+                                * Request had 'have' lines, so lets ACK them.
+                                */
+                               state = FETCH_SEND_ACKS;
+                       } else {
+                               /*
+                                * Request had 'want's but no 'have's so we can
+                                * immedietly go to construct and send a pack.
+                                */
+                               state = FETCH_SEND_PACK;
+                       }
+                       break;
+               case FETCH_SEND_ACKS:
+                       if (process_haves_and_send_acks(&data))
+                               state = FETCH_SEND_PACK;
+                       else
+                               state = FETCH_DONE;
+                       break;
+               case FETCH_SEND_PACK:
+                       send_shallow_info(&data);
+                       packet_write_fmt(1, "packfile\n");
+                       create_pack_file();
+                       state = FETCH_DONE;
+                       break;
+               case FETCH_DONE:
+                       continue;
+               }
+       }
+       upload_pack_data_clear(&data);
+       return 0;
+ }
+ int upload_pack_advertise(struct repository *r,
+                         struct strbuf *value)
+ {
+       if (value)
+               strbuf_addstr(value, "shallow");
+       return 1;
+ }