Code

Merge branch 'jk/parse-object-cached'
authorJunio C Hamano <gitster@pobox.com>
Sun, 29 Jan 2012 21:18:55 +0000 (13:18 -0800)
committerJunio C Hamano <gitster@pobox.com>
Sun, 29 Jan 2012 21:18:55 +0000 (13:18 -0800)
* jk/parse-object-cached:
  upload-pack: avoid parsing tag destinations
  upload-pack: avoid parsing objects during ref advertisement
  parse_object: try internal cache before reading object db

109 files changed:
Documentation/Makefile
Documentation/RelNotes/1.7.6.6.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.7.6.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.8.3.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.8.4.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.9.txt
Documentation/config.txt
Documentation/git-am.txt
Documentation/git-clone.txt
Documentation/git-grep.txt
Documentation/git-mailinfo.txt
Documentation/git-p4.txt
Documentation/git-read-tree.txt
Documentation/git-show-ref.txt
Documentation/git-tag.txt
Documentation/git.txt
Documentation/howto/using-signed-tag-in-pull-request.txt [new file with mode: 0644]
GIT-VERSION-GEN
INSTALL
archive.c
attr.c
builtin/clone.c
builtin/commit-tree.c
builtin/commit.c
builtin/fetch.c
builtin/mailinfo.c
builtin/merge.c
builtin/pack-objects.c
builtin/receive-pack.c
builtin/remote.c
builtin/send-pack.c
cache.h
commit.c
commit.h
connect.c
contrib/completion/git-completion.bash
contrib/fast-import/git-p4
credential-cache.c
daemon.c
diff-lib.c
diff.c
git-am.sh
git-cvsexportcommit.perl
git-request-pull.sh
git-send-email.perl
git-stash.sh
git.c
gitweb/gitweb.perl
gpg-interface.c
log-tree.c
notes-cache.c
notes-merge.c
pack-refs.c
pretty.c
refs.c
refs.h
remote-curl.c
revision.c
revision.h
run-command.c
run-command.h
t/README
t/lib-git-daemon.sh [new file with mode: 0644]
t/t0003-attributes.sh
t/t2203-add-intent.sh
t/t3903-stash.sh
t/t3904-stash-patch.sh
t/t3905-stash-include-untracked.sh
t/t4010-diff-pathspec.sh
t/t4034-diff-words.sh
t/t4150-am.sh
t/t5500-fetch-pack.sh
t/t5510-fetch.sh
t/t5515/fetch.br-branches-default-merge
t/t5515/fetch.br-branches-default-merge_branches-default
t/t5515/fetch.br-branches-default-octopus
t/t5515/fetch.br-branches-default-octopus_branches-default
t/t5515/fetch.br-branches-one-merge
t/t5515/fetch.br-branches-one-merge_branches-one
t/t5515/fetch.br-config-explicit-merge
t/t5515/fetch.br-config-explicit-merge_config-explicit
t/t5515/fetch.br-config-explicit-octopus
t/t5515/fetch.br-config-explicit-octopus_config-explicit
t/t5515/fetch.br-config-glob-merge
t/t5515/fetch.br-config-glob-merge_config-glob
t/t5515/fetch.br-config-glob-octopus
t/t5515/fetch.br-config-glob-octopus_config-glob
t/t5515/fetch.br-remote-explicit-merge
t/t5515/fetch.br-remote-explicit-merge_remote-explicit
t/t5515/fetch.br-remote-explicit-octopus
t/t5515/fetch.br-remote-explicit-octopus_remote-explicit
t/t5515/fetch.br-remote-glob-merge
t/t5515/fetch.br-remote-glob-merge_remote-glob
t/t5515/fetch.br-remote-glob-octopus
t/t5515/fetch.br-remote-glob-octopus_remote-glob
t/t5523-push-upstream.sh
t/t5541-http-push.sh
t/t5570-git-daemon.sh [new file with mode: 0755]
t/t6012-rev-list-simplify.sh
t/t7510-signed-commit.sh [new file with mode: 0755]
t/t8006-blame-textconv.sh
t/t9200-git-cvsexportcommit.sh
t/t9500-gitweb-standalone-no-errors.sh
t/t9806-git-p4-options.sh
t/t9809-git-p4-client-view.sh [new file with mode: 0755]
t/test-lib.sh
tree-walk.c
unix-socket.c
upload-pack.c

index 116f17587e724271d343de0082e7ef0071d459e3..d40e211f22dbe6c102a102041ac9f0779c7cb837 100644 (file)
@@ -20,7 +20,10 @@ ARTICLES += everyday
 ARTICLES += git-tools
 ARTICLES += git-bisect-lk2009
 # with their own formatting rules.
-SP_ARTICLES = howto/revert-branch-rebase howto/using-merge-subtree user-manual
+SP_ARTICLES = user-manual
+SP_ARTICLES += howto/revert-branch-rebase
+SP_ARTICLES += howto/using-merge-subtree
+SP_ARTICLES += howto/using-signed-tag-in-pull-request
 API_DOCS = $(patsubst %.txt,%,$(filter-out technical/api-index-skel.txt technical/api-index.txt, $(wildcard technical/api-*.txt)))
 SP_ARTICLES += $(API_DOCS)
 SP_ARTICLES += technical/api-index
diff --git a/Documentation/RelNotes/1.7.6.6.txt b/Documentation/RelNotes/1.7.6.6.txt
new file mode 100644 (file)
index 0000000..5343e00
--- /dev/null
@@ -0,0 +1,16 @@
+Git v1.7.6.6 Release Notes
+==========================
+
+Fixes since v1.7.6.5
+--------------------
+
+ * The code to look up attributes for paths reused entries from a wrong
+   directory when two paths in question are in adjacent directories and
+   the name of the one directory is a prefix of the other.
+
+ * When producing a "thin pack" (primarily used in bundles and smart
+   HTTP transfers) out of a fully packed repository, we unnecessarily
+   avoided sending recent objects as a delta against objects we know
+   the other side has.
+
+Also contains minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.7.6.txt b/Documentation/RelNotes/1.7.7.6.txt
new file mode 100644 (file)
index 0000000..8df606d
--- /dev/null
@@ -0,0 +1,20 @@
+Git v1.7.7.6 Release Notes
+==========================
+
+Fixes since v1.7.7.5
+--------------------
+
+ * The code to look up attributes for paths reused entries from a wrong
+   directory when two paths in question are in adjacent directories and
+   the name of the one directory is a prefix of the other.
+
+ * A wildcard that matches deeper hierarchy given to the "diff-index" command,
+   e.g. "git diff-index HEAD -- '*.txt'", incorrectly reported additions of
+   matching files even when there is no change.
+
+ * When producing a "thin pack" (primarily used in bundles and smart
+   HTTP transfers) out of a fully packed repository, we unnecessarily
+   avoided sending recent objects as a delta against objects we know
+   the other side has.
+
+Also contains minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.8.3.txt b/Documentation/RelNotes/1.7.8.3.txt
new file mode 100644 (file)
index 0000000..a92714c
--- /dev/null
@@ -0,0 +1,16 @@
+Git v1.7.8.3 Release Notes
+==========================
+
+Fixes since v1.7.8.2
+--------------------
+
+ * Attempt to fetch from an empty file pretending it to be a bundle did
+   not error out correctly.
+
+ * gitweb did not correctly fall back to configured $fallback_encoding
+   that is not 'latin1'.
+
+ * "git clone --depth $n" did not catch a non-number given as $n as an
+   error.
+
+Also contains minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.8.4.txt b/Documentation/RelNotes/1.7.8.4.txt
new file mode 100644 (file)
index 0000000..9bebdbf
--- /dev/null
@@ -0,0 +1,23 @@
+Git v1.7.8.4 Release Notes
+==========================
+
+Fixes since v1.7.8.3
+--------------------
+
+ * The code to look up attributes for paths reused entries from a wrong
+   directory when two paths in question are in adjacent directories and
+   the name of the one directory is a prefix of the other.
+
+ * A wildcard that matches deeper hierarchy given to the "diff-index" command,
+   e.g. "git diff-index HEAD -- '*.txt'", incorrectly reported additions of
+   matching files even when there is no change.
+
+ * When producing a "thin pack" (primarily used in bundles and smart
+   HTTP transfers) out of a fully packed repository, we unnecessarily
+   avoided sending recent objects as a delta against objects we know
+   the other side has.
+
+ * "git send-email" did not properly treat sendemail.multiedit as a
+   boolean (e.g. setting it to "false" did not turn it off).
+
+Also contains minor fixes and documentation updates.
index 9689efa986c27ffdc9684f62767bfc7cd93fb113..95320aad5dd2414efeacbc088b34e45c4ac51bbf 100644 (file)
@@ -1,4 +1,4 @@
-Git v1.7.9 Release Notes (draft)
+Git v1.7.9 Release Notes
 ========================
 
 Updates since v1.7.8
@@ -12,22 +12,27 @@ Updates since v1.7.8
 
  * Git uses gettext to translate its most common interface messages
    into the user's language if translations are available and the
-   locale is appropriately set. Distributors can drop in new PO files
+   locale is appropriately set. Distributors can drop new PO files
    in po/ to add new translations.
 
- * The code to handle username/password for HTTP transaction used in
+ * The code to handle username/password for HTTP transactions used in
    "git push" & "git fetch" learned to talk "credential API" to
    external programs to cache or store them, to allow integration with
    platform native keychain mechanisms.
 
- * The prompted input in the terminal use our own getpass() replacement
-   when possible. HTTP transactions used to ask username without echoing
-   back what was typed, but with this change you will see it as you type.
+ * The input prompts in the terminal use our own getpass() replacement
+   when possible. HTTP transactions used to ask for the username without
+   echoing back what was typed, but with this change you will see it as
+   you type.
 
- * The internal of "revert/cherry-pick" has been tweaked to prepare
+ * The internals of "revert/cherry-pick" have been tweaked to prepare
    building more generic "sequencer" on top of the implementation that
    drives them.
 
+ * "git rev-parse FETCH_HEAD" after "git fetch" without specifying
+   what to fetch from the command line will now show the commit that
+   would be merged if the command were "git pull".
+
  * "git add" learned to stream large files directly into a packfile
    instead of writing them into individual loose object files.
 
@@ -48,6 +53,9 @@ Updates since v1.7.8
  * "git commit" detects and rejects an attempt to stuff NUL byte in
    the commit log message.
 
+ * "git commit" learned "-S" to GPG-sign the commit; this can be shown
+   with the "--show-signature" option to "git log".
+
  * fsck and prune are relatively lengthy operations that still go
    silent while making the end-user wait. They learned to give progress
    output like other slow operations.
@@ -56,7 +64,7 @@ Updates since v1.7.8
    knows MATLAB.
 
  * "git log --format='<format>'" learned new %g[nNeE] specifiers to
-   show information from the reflog entries when warlking the reflog
+   show information from the reflog entries when walking the reflog
    (i.e. with "-g").
 
  * "git pull" can be used to fetch and merge an annotated/signed tag,
@@ -64,6 +72,10 @@ Updates since v1.7.8
    signed tag is recorded in the resulting merge commit for later
    auditing.
 
+ * "git log" learned "--show-signature" option to show the signed tag
+   that was merged that is embedded in the merge commit. It also can
+   show the signature made on the commit with "git commit -S".
+
  * "git branch --edit-description" can be used to add descriptive text
    to explain what a topic branch is about.
 
@@ -98,15 +110,3 @@ Fixes since v1.7.8
 Unless otherwise noted, all the fixes since v1.7.8 in the maintenance
 releases are contained in this release (see release notes to them for
 details).
-
- * gitweb did not correctly fall back to configured $fallback_encoding
-   that is not 'latin1'.
-   (merge b13e3ea jn/maint-gitweb-utf8-fix later to maint).
-
---
-exec >/var/tmp/1
-O=v1.7.8.2-301-g48de656
-echo O=$(git describe master)
-git log --first-parent --oneline --reverse ^$O master
-echo
-git shortlog --no-merges ^$O ^maint master
index 6e63b5938f60f544cf3db3ce0d9a277964e99115..abeb82b2c6d40e8557f7a5f8ad4c5e98b3a26a62 100644 (file)
@@ -1123,6 +1123,17 @@ grep.lineNumber::
 grep.extendedRegexp::
        If set to true, enable '--extended-regexp' option by default.
 
+gpg.program::
+       Use this custom program instead of "gpg" found on $PATH when
+       making or verifying a PGP signature. The program must support the
+       same command line interface as GPG, namely, to verify a detached
+       signature, "gpg --verify $file - <$signature" is run, and the
+       program is expected to signal a good signature by exiting with
+       code 0, and to generate an ascii-armored detached signature, the
+       standard input of "gpg -bsau $key" is fed with the contents to be
+       signed, and the program is expected to send the result to its
+       standard output.
+
 gui.commitmsgwidth::
        Defines how wide the commit message window is in the
        linkgit:git-gui[1]. "75" is the default.
@@ -1772,10 +1783,11 @@ rerere.autoupdate::
 
 rerere.enabled::
        Activate recording of resolved conflicts, so that identical
-       conflict hunks can be resolved automatically, should they
-       be encountered again.  linkgit:git-rerere[1] command is by
-       default enabled if you create `rr-cache` directory under
-       `$GIT_DIR`, but can be disabled by setting this option to false.
+       conflict hunks can be resolved automatically, should they be
+       encountered again.  By default, linkgit:git-rerere[1] is
+       enabled if there is an `rr-cache` directory under the
+       `$GIT_DIR`, e.g. if "rerere" was previously used in the
+       repository.
 
 sendemail.identity::
        A configuration identity. When given, causes values in the
index 887466d7777bb3cedce976b55d446f0f7803c423..ee6cca2e1333eb26b0913eb5c4eaf9f38e5855d9 100644 (file)
@@ -40,6 +40,9 @@ OPTIONS
 --keep::
        Pass `-k` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]).
 
+--keep-non-patch::
+       Pass `-b` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]).
+
 --keep-cr::
 --no-keep-cr::
        With `--keep-cr`, call 'git mailsplit' (see linkgit:git-mailsplit[1])
index 4b8b26b75e63cc56e679d2e2c6d8fd1240010419..0931a3e39237da0abbf03c43a2f35092ea48efd5 100644 (file)
@@ -13,7 +13,8 @@ SYNOPSIS
          [-l] [-s] [--no-hardlinks] [-q] [-n] [--bare] [--mirror]
          [-o <name>] [-b <name>] [-u <upload-pack>] [--reference <repository>]
          [--separate-git-dir <git dir>]
-         [--depth <depth>] [--recursive|--recurse-submodules] [--] <repository>
+         [--depth <depth>] [--[no-]single-branch]
+         [--recursive|--recurse-submodules] [--] <repository>
          [<directory>]
 
 DESCRIPTION
@@ -179,6 +180,14 @@ objects from the source repository into a pack in the cloned repository.
        with a long history, and would want to send in fixes
        as patches.
 
+--single-branch::
+       Clone only the history leading to the tip of a single branch,
+       either specified by the `--branch` option or the primary
+       branch remote's `HEAD` points at. When creating a shallow
+       clone with the `--depth` option, this is the default, unless
+       `--no-single-branch` is given to fetch the histories near the
+       tips of all branches.
+
 --recursive::
 --recurse-submodules::
        After the clone is created, initialize all submodules within,
index 15d6711d46754e9659561b4372b46ec3c6654d6b..6a8b1e3a7d06218903ce5614f51c4e7cbba73b17 100644 (file)
@@ -79,6 +79,9 @@ OPTIONS
 --max-depth <depth>::
        For each <pathspec> given on command line, descend at most <depth>
        levels of directories. A negative value means no limit.
+       This option is ignored if <pathspec> contains active wildcards.
+       In other words if "a*" matches a directory named "a*",
+       "*" is matched literally so --max-depth is still effective.
 
 -w::
 --word-regexp::
index 51dc3257486b07edaec71fe98fed6533e17e1a77..97e7a8e9e7cc01bc350d57ca66e08f5ed96221de 100644 (file)
@@ -25,13 +25,24 @@ command directly.  See linkgit:git-am[1] instead.
 OPTIONS
 -------
 -k::
-       Usually the program 'cleans up' the Subject: header line
-       to extract the title line for the commit log message,
-       among which (1) remove 'Re:' or 're:', (2) leading
-       whitespaces, (3) '[' up to ']', typically '[PATCH]', and
-       then prepends "[PATCH] ".  This flag forbids this
-       munging, and is most useful when used to read back
-       'git format-patch -k' output.
+       Usually the program removes email cruft from the Subject:
+       header line to extract the title line for the commit log
+       message.  This option prevents this munging, and is most
+       useful when used to read back 'git format-patch -k' output.
++
+Specifically, the following are removed until none of them remain:
++
+--
+*      Leading and trailing whitespace.
+
+*      Leading `Re:`, `re:`, and `:`.
+
+*      Leading bracketed strings (between `[` and `]`, usually
+       `[PATCH]`).
+--
++
+Finally, runs of whitespace are normalized to a single ASCII space
+character.
 
 -b::
        When -k is not in effect, all leading strings bracketed with '['
index d7f5a51feaeaf1acd804510ddc910f11fcfdbe50..8b92cc0f8d54a819236ab879690708ec486f7a19 100644 (file)
@@ -230,12 +230,7 @@ git repository:
 
 --use-client-spec::
        Use a client spec to find the list of interesting files in p4.
-       The client spec is discovered using 'p4 client -o' which checks
-       the 'P4CLIENT' environment variable and returns a mapping of
-       depot files to workspace files.  Note that a depot path is
-       still required, but files found in the path that match in
-       the client spec view will be laid out according to the client
-       spec.
+       See the "CLIENT SPEC" section below.
 
 Clone options
 ~~~~~~~~~~~~~
@@ -304,6 +299,32 @@ p4 revision specifier on the end:
 See 'p4 help revisions' for the full syntax of p4 revision specifiers.
 
 
+CLIENT SPEC
+-----------
+The p4 client specification is maintained with the 'p4 client' command
+and contains among other fields, a View that specifies how the depot
+is mapped into the client repository.  Git-p4 can consult the client
+spec when given the '--use-client-spec' option or useClientSpec
+variable.
+
+The full syntax for a p4 view is documented in 'p4 help views'.  Git-p4
+knows only a subset of the view syntax.  It understands multi-line
+mappings, overlays with '+', exclusions with '-' and double-quotes
+around whitespace.  Of the possible wildcards, git-p4 only handles
+'...', and only when it is at the end of the path.  Git-p4 will complain
+if it encounters an unhandled wildcard.
+
+Bugs in the implementation of overlap mappings exist.  If multiple depot
+paths map through overlays to the same location in the repository,
+git-p4 can choose the wrong one.  This is hard to solve without
+dedicating a client spec just for git-p4.
+
+The name of the client can be given to git-p4 in multiple ways.  The
+variable 'git-p4.client' takes precedence if it exists.  Otherwise,
+normal p4 mechanisms of determining the client are used:  environment
+variable P4CLIENT, a file referenced by P4CONFIG, or the local host name.
+
+
 BRANCH DETECTION
 ----------------
 P4 does not have the same concept of a branch as git.  Instead,
@@ -387,9 +408,7 @@ git-p4.host::
 
 git-p4.client::
        Client specified as an option to all p4 commands, with
-       '-c <client>'.  This can also be used as a way to find
-       the client spec for the 'useClientSpec' option.
-       The environment variable 'P4CLIENT' can be used instead.
+       '-c <client>', including the client spec.
 
 Clone and sync variables
 ~~~~~~~~~~~~~~~~~~~~~~~~
@@ -417,10 +436,10 @@ git config --add git-p4.branchList main:branchB
 -------------
 
 git-p4.useClientSpec::
-       Specify that the p4 client spec to be used to identify p4 depot
-       paths of interest.  This is equivalent to specifying the option
-       '--use-client-spec'.  The variable 'git-p4.client' can be used
-       to specify the name of the client.
+       Specify that the p4 client spec should be used to identify p4
+       depot paths of interest.  This is equivalent to specifying the
+       option '--use-client-spec'.  See the "CLIENT SPEC" section above.
+       This variable is a boolean, not the name of a p4 client.
 
 Submit variables
 ~~~~~~~~~~~~~~~~
index a43e87448ba035278b58b191ea7c1c04179985a6..c4bde6509e5a2f652dca9345a3a28e9e674c216a 100644 (file)
@@ -341,7 +341,7 @@ since you pulled from him:
 
 ----------------
 $ git fetch git://.... linus
-$ LT=`cat .git/FETCH_HEAD`
+$ LT=`git rev-parse FETCH_HEAD`
 ----------------
 
 Your work tree is still based on your HEAD ($JC), but you have
index 3c4589529960e013df364c68e4480caa09b744c6..fcee0008a9d868bb27c9eacbcfe72a3a3f8ed60f 100644 (file)
@@ -44,7 +44,7 @@ OPTIONS
 -d::
 --dereference::
 
-       Dereference tags into object IDs as well. They will be shown with "^{}"
+       Dereference tags into object IDs as well. They will be shown with "{caret}{}"
        appended.
 
 -s::
@@ -73,9 +73,9 @@ OPTIONS
 --exclude-existing[=<pattern>]::
 
        Make 'git show-ref' act as a filter that reads refs from stdin of the
-       form "^(?:<anything>\s)?<refname>(?:{backslash}{caret}\{\})?$"
+       form "`{caret}(?:<anything>\s)?<refname>(?:{backslash}{caret}{})?$`"
        and performs the following actions on each:
-       (1) strip "^{}" at the end of line if any;
+       (1) strip "{caret}{}" at the end of line if any;
        (2) ignore if pattern is provided and does not head-match refname;
        (3) warn if refname is not a well-formed refname and skip;
        (4) ignore if refname is a ref that exists in the local repository;
index 622a019eb034e61f73bf326ed1f000fd90fe9375..53ff5f6cf7b9420933b022accace1355db6337c6 100644 (file)
@@ -38,7 +38,9 @@ created (i.e. a lightweight tag).
 A GnuPG signed tag object will be created when `-s` or `-u
 <key-id>` is used.  When `-u <key-id>` is not used, the
 committer identity for the current user is used to find the
-GnuPG key for signing.
+GnuPG key for signing.         The configuration variable `gpg.program`
+is used to specify custom GnuPG binary.
+
 
 OPTIONS
 -------
@@ -48,11 +50,11 @@ OPTIONS
 
 -s::
 --sign::
-       Make a GPG-signed tag, using the default e-mail address's key
+       Make a GPG-signed tag, using the default e-mail address's key.
 
 -u <key-id>::
 --local-user=<key-id>::
-       Make a GPG-signed tag, using the given key
+       Make a GPG-signed tag, using the given key.
 
 -f::
 --force::
index da7d48787e7a00e7e14f9d9a0bf236ffe088bd8b..c991430642a56001a82b6526bda70d602af9e9dc 100644 (file)
@@ -44,14 +44,24 @@ unreleased) version of git, that is available from 'master'
 branch of the `git.git` repository.
 Documentation for older releases are available here:
 
-* link:v1.7.8/git.html[documentation for release 1.7.8]
+* link:v1.7.9/git.html[documentation for release 1.7.9]
 
 * release notes for
+  link:RelNotes/1.7.9.txt[1.7.9].
+
+* link:v1.7.8.4/git.html[documentation for release 1.7.8.4]
+
+* release notes for
+  link:RelNotes/1.7.8.4.txt[1.7.8.4],
+  link:RelNotes/1.7.8.3.txt[1.7.8.3],
+  link:RelNotes/1.7.8.2.txt[1.7.8.2],
+  link:RelNotes/1.7.8.1.txt[1.7.8.1],
   link:RelNotes/1.7.8.txt[1.7.8].
 
-* link:v1.7.7.5/git.html[documentation for release 1.7.7.5]
+* link:v1.7.7.6/git.html[documentation for release 1.7.7.6]
 
 * release notes for
+  link:RelNotes/1.7.7.6.txt[1.7.7.6],
   link:RelNotes/1.7.7.5.txt[1.7.7.5],
   link:RelNotes/1.7.7.4.txt[1.7.7.4],
   link:RelNotes/1.7.7.3.txt[1.7.7.3],
diff --git a/Documentation/howto/using-signed-tag-in-pull-request.txt b/Documentation/howto/using-signed-tag-in-pull-request.txt
new file mode 100644 (file)
index 0000000..a1351c5
--- /dev/null
@@ -0,0 +1,217 @@
+From: Junio C Hamano <gitster@pobox.com>
+Date: Tue, 17 Jan 2011 13:00:00 -0800
+Subject: Using signed tag in pull requests
+Abstract: Beginning v1.7.9, a contributor can push a signed tag to her
+ publishing repository and ask her integrator to pull it. This assures the
+ integrator that the pulled history is authentic and allows others to
+ later validate it.
+Content-type: text/asciidoc
+
+Using signed tag in pull requests
+=================================
+
+A typical distributed workflow using Git is for a contributor to fork a
+project, build on it, publish the result to her public repository, and ask
+the "upstream" person (often the owner of the project where she forked
+from) to pull from her public repository. Requesting such a "pull" is made
+easy by the `git request-pull` command.
+
+Earlier, a typical pull request may have started like this:
+
+------------
+ The following changes since commit 406da78032179...:
+
+   Froboz 3.2 (2011-09-30 14:20:57 -0700)
+
+ are available in the git repository at:
+
+   example.com:/git/froboz.git for-xyzzy
+------------
+
+followed by a shortlog of the changes and a diffstat.
+
+The request was for a branch name (e.g. `for-xyzzy`) in the public
+repository of the contributor, and even though it stated where the
+contributor forked her work from, the message did not say anything about
+the commit to expect at the tip of the for-xyzzy branch. If the site that
+hosts the public repository of the contributor cannot be fully trusted, it
+was unnecessarily hard to make sure what was pulled by the integrator was
+genuinely what the contributor had produced for the project. Also there
+was no easy way for third-party auditors to later verify the resulting
+history.
+
+Starting from Git release v1.7.9, a contributor can add a signed tag to
+the commit at the tip of the history and ask the integrator to pull that
+signed tag. When the integrator runs `git pull`, the signed tag is
+automatically verified to assure that the history is not tampered with.
+In addition, the resulting merge commit records the content of the signed
+tag, so that other people can verify that the branch merged by the
+integrator was signed by the contributor, without fetching the signed tag
+used to validate the pull request separately and keeping it in the refs
+namespace.
+
+This document describes the workflow between the contributor and the
+integrator, using Git v1.7.9 or later.
+
+
+A contributor or a lieutenant
+-----------------------------
+
+After preparing her work to be pulled, the contributor uses `git tag -s`
+to create a signed tag:
+
+------------
+ $ git checkout work
+ $ ... "git pull" from sublieutenants, "git commit" your own work ...
+ $ git tag -s -m "Completed frotz feature" frotz-for-xyzzy work
+------------
+
+Note that this example uses the `-m` option to create a signed tag with
+just a one-liner message, but this is for illustration purposes only. It
+is advisable to compose a well-written explanation of what the topic does
+to justify why it is worthwhile for the integrator to pull it, as this
+message will eventually become part of the final history after the
+integrator responds to the pull request (as we will see later).
+
+Then she pushes the tag out to her public repository:
+
+------------
+ $ git push example.com:/git/froboz.git/ +frotz-for-xyzzy
+------------
+
+There is no need to push the `work` branch or anything else.
+
+Note that the above command line used a plus sign at the beginning of
+`+frotz-for-xyzzy` to allow forcing the update of a tag, as the same
+contributor may want to reuse a signed tag with the same name after the
+previous pull request has already been responded to.
+
+The contributor then prepares a message to request a "pull":
+
+------------
+ $ git request-pull v3.2 example.com:/git/froboz.git/ frotz-for-xyzzy >msg.txt
+------------
+
+The arguments are:
+
+. the version of the integrator's commit the contributor based her work on;
+. the URL of the repository, to which the contributor has pushed what she
+  wants to get pulled; and
+. the name of the tag the contributor wants to get pulled (earlier, she could
+  write only a branch name here).
+
+The resulting msg.txt file begins like so:
+
+------------
+ The following changes since commit 406da78032179...:
+
+   Froboz 3.2 (2011-09-30 14:20:57 -0700)
+
+ are available in the git repository at:
+
+   example.com:/git/froboz.git frotz-for-xyzzy
+
+ for you to fetch changes up to 703f05ad5835c...:
+
+   Add tests and documentation for frotz (2011-12-02 10:02:52 -0800)
+
+ -----------------------------------------------
+ Completed frotz feature
+ -----------------------------------------------
+------------
+
+followed by a shortlog of the changes and a diffstat.  Comparing this with
+the earlier illustration of the output from the traditional `git request-pull`
+command, the reader should notice that:
+
+. The tip commit to expect is shown to the integrator; and
+. The signed tag message is shown prominently between the dashed lines
+  before the shortlog.
+
+The latter is why the contributor would want to justify why pulling her
+work is worthwhile when creating the signed tag.  The contributor then
+opens her favorite MUA, reads msg.txt, edits and sends it to her upstream
+integrator.
+
+
+Integrator
+----------
+
+After receiving such a pull request message, the integrator fetches and
+integrates the tag named in the request, with:
+
+------------
+ $ git pull example.com:/git/froboz.git/ frotz-for-xyzzy
+------------
+
+This operation will always open an editor to allow the integrator to fine
+tune the commit log message when merging a signed tag.  Also, pulling a
+signed tag will always create a merge commit even when the integrator does
+not have any new commit since the contributor's work forked (i.e. 'fast
+forward'), so that the integrator can properly explain what the merge is
+about and why it was made.
+
+In the editor, the integrator will see something like this:
+
+------------
+ Merge tag 'frotz-for-xyzzy' of example.com:/git/froboz.git/
+
+ Completed frotz feature
+ # gpg: Signature made Fri 02 Dec 2011 10:03:01 AM PST using RSA key ID 96AFE6CB
+ # gpg: Good signature from "Con Tributor <nitfol@example.com>"
+------------
+
+Notice that the message recorded in the signed tag "Completed frotz
+feature" appears here, and again that is why it is important for the
+contributor to explain her work well when creating the signed tag.
+
+As usual, the lines commented with `#` are stripped out. The resulting
+commit records the signed tag used for this validation in a hidden field
+so that it can later be used by others to audit the history. There is no
+need for the integrator to keep a separate copy of the tag in his
+repository (i.e. `git tag -l` won't list the `frotz-for-xyzzy` tag in the
+above example), and there is no need to publish the tag to his public
+repository, either.
+
+After the integrator responds to the pull request and her work becomes
+part of the permanent history, the contributor can remove the tag from
+her public repository, if she chooses, in order to keep the tag namespace
+of her public repository clean, with:
+
+------------
+ $ git push example.com:/git/froboz.git :frotz-for-xyzzy
+------------
+
+
+Auditors
+--------
+
+The `--show-signature` option can be given to `git log` or `git show` and
+shows the verification status of the embedded signed tag in merge commits
+created when the integrator responded to a pull request of a signed tag.
+
+A typical output from `git show --show-signature` may look like this:
+
+------------
+ $ git show --show-signature
+ commit 02306ef6a3498a39118aef9df7975bdb50091585
+ merged tag 'frotz-for-xyzzy'
+ gpg: Signature made Fri 06 Jan 2012 12:41:49 PM PST using RSA key ID 96AFE6CB
+ gpg: Good signature from "Con Tributor <nitfol@example.com>"
+ Merge: 406da78 703f05a
+ Author: Inte Grator <xyzzy@example.com>
+ Date:   Tue Jan 17 13:49:41 2012 -0800
+
+     Merge tag 'frotz-for-xyzzy' of example.com:/git/froboz.git/
+
+     Completed frotz feature
+
+     * tag 'frotz-for-xyzzy' (100 commits)
+       Add tests and documentation for frotz
+       ...
+------------
+
+There is no need for the auditor to explicitly fetch the contributor's
+signature, or to even be aware of what tag(s) the contributor and integrator
+used to communicate the signature.  All the required information is recorded
+as part of the merge commit.
index e18a30a16a6bd671290ac466745c218fed669755..70204f87f90c733db7043b3111b5c18226ab64ac 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v1.7.8.GIT
+DEF_VER=v1.7.9
 
 LF='
 '
diff --git a/INSTALL b/INSTALL
index 8120641a513f1528b7e8f312b1ba8f496b936539..6fa83fe2b85f2611fc0253a90b7b238bf74745b7 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -83,7 +83,11 @@ Issues of note:
        - "Perl" version 5.8 or later is needed to use some of the
          features (e.g. preparing a partial commit using "git add -i/-p",
          interacting with svn repositories with "git svn").  If you can
-         live without these, use NO_PERL.
+         live without these, use NO_PERL.  Note that recent releases of
+         Redhat/Fedora are reported to ship Perl binary package with some
+         core modules stripped away (see http://lwn.net/Articles/477234/),
+         so you might need to install additional packages other than Perl
+         itself, e.g. Time::HiRes.
 
        - "openssl" library is used by git-imap-send to use IMAP over SSL.
          If you don't need it, use NO_OPENSSL.
index 164bbd014a82feac48886db6f27ba85d61a059ac..1ee837d7170cfa52da6725cfe7c5ae0d6d67462e 100644 (file)
--- a/archive.c
+++ b/archive.c
@@ -260,14 +260,23 @@ static void parse_treeish_arg(const char **argv,
        /* Remotes are only allowed to fetch actual refs */
        if (remote) {
                char *ref = NULL;
-               if (!dwim_ref(name, strlen(name), sha1, &ref))
-                       die("no such ref: %s", name);
+               const char *refname, *colon = NULL;
+
+               colon = strchr(name, ':');
+               if (colon)
+                       refname = xstrndup(name, colon - name);
+               else
+                       refname = name;
+
+               if (!dwim_ref(refname, strlen(refname), sha1, &ref))
+                       die("no such ref: %s", refname);
+               if (refname != name)
+                       free((void *)refname);
                free(ref);
        }
-       else {
-               if (get_sha1(name, sha1))
-                       die("Not a valid object name");
-       }
+
+       if (get_sha1(name, sha1))
+               die("Not a valid object name");
 
        commit = lookup_commit_reference_gently(sha1, 1);
        if (commit) {
diff --git a/attr.c b/attr.c
index 76b079f0f530e1372b2866f40cce21ec5266394c..303751f6c2bd4d558cffbb928c636581efb2b310 100644 (file)
--- a/attr.c
+++ b/attr.c
@@ -301,6 +301,7 @@ static void free_attr_elem(struct attr_stack *e)
                }
                free(a);
        }
+       free(e->attrs);
        free(e);
 }
 
@@ -495,47 +496,48 @@ static int git_attr_system(void)
 
 static void bootstrap_attr_stack(void)
 {
-       if (!attr_stack) {
-               struct attr_stack *elem;
+       struct attr_stack *elem;
 
-               elem = read_attr_from_array(builtin_attr);
-               elem->origin = NULL;
-               elem->prev = attr_stack;
-               attr_stack = elem;
+       if (attr_stack)
+               return;
 
-               if (git_attr_system()) {
-                       elem = read_attr_from_file(git_etc_gitattributes(), 1);
-                       if (elem) {
-                               elem->origin = NULL;
-                               elem->prev = attr_stack;
-                               attr_stack = elem;
-                       }
-               }
+       elem = read_attr_from_array(builtin_attr);
+       elem->origin = NULL;
+       elem->prev = attr_stack;
+       attr_stack = elem;
 
-               if (git_attributes_file) {
-                       elem = read_attr_from_file(git_attributes_file, 1);
-                       if (elem) {
-                               elem->origin = NULL;
-                               elem->prev = attr_stack;
-                               attr_stack = elem;
-                       }
+       if (git_attr_system()) {
+               elem = read_attr_from_file(git_etc_gitattributes(), 1);
+               if (elem) {
+                       elem->origin = NULL;
+                       elem->prev = attr_stack;
+                       attr_stack = elem;
                }
+       }
 
-               if (!is_bare_repository() || direction == GIT_ATTR_INDEX) {
-                       elem = read_attr(GITATTRIBUTES_FILE, 1);
-                       elem->origin = xstrdup("");
+       if (git_attributes_file) {
+               elem = read_attr_from_file(git_attributes_file, 1);
+               if (elem) {
+                       elem->origin = NULL;
                        elem->prev = attr_stack;
                        attr_stack = elem;
-                       debug_push(elem);
                }
+       }
 
-               elem = read_attr_from_file(git_path(INFOATTRIBUTES_FILE), 1);
-               if (!elem)
-                       elem = xcalloc(1, sizeof(*elem));
-               elem->origin = NULL;
+       if (!is_bare_repository() || direction == GIT_ATTR_INDEX) {
+               elem = read_attr(GITATTRIBUTES_FILE, 1);
+               elem->origin = xstrdup("");
                elem->prev = attr_stack;
                attr_stack = elem;
+               debug_push(elem);
        }
+
+       elem = read_attr_from_file(git_path(INFOATTRIBUTES_FILE), 1);
+       if (!elem)
+               elem = xcalloc(1, sizeof(*elem));
+       elem->origin = NULL;
+       elem->prev = attr_stack;
+       attr_stack = elem;
 }
 
 static void prepare_attr_stack(const char *path)
@@ -575,14 +577,17 @@ static void prepare_attr_stack(const char *path)
 
        /*
         * Pop the ones from directories that are not the prefix of
-        * the path we are checking.
+        * the path we are checking. Break out of the loop when we see
+        * the root one (whose origin is an empty string "") or the builtin
+        * one (whose origin is NULL) without popping it.
         */
-       while (attr_stack && attr_stack->origin) {
+       while (attr_stack->origin) {
                int namelen = strlen(attr_stack->origin);
 
                elem = attr_stack;
                if (namelen <= dirlen &&
-                   !strncmp(elem->origin, path, namelen))
+                   !strncmp(elem->origin, path, namelen) &&
+                   (!namelen || path[namelen] == '/'))
                        break;
 
                debug_pop(elem);
@@ -594,8 +599,15 @@ static void prepare_attr_stack(const char *path)
         * Read from parent directories and push them down
         */
        if (!is_bare_repository() || direction == GIT_ATTR_INDEX) {
+               /*
+                * bootstrap_attr_stack() should have added, and the
+                * above loop should have stopped before popping, the
+                * root element whose attr_stack->origin is set to an
+                * empty string.
+                */
                struct strbuf pathbuf = STRBUF_INIT;
 
+               assert(attr_stack->origin);
                while (1) {
                        len = strlen(attr_stack->origin);
                        if (dirlen <= len)
index 86db95473021bc8d0b1cc8850185e1660fd9c776..9084febb1428a4232b43d97912c7e3e7dfb9e382 100644 (file)
@@ -37,7 +37,7 @@ static const char * const builtin_clone_usage[] = {
        NULL
 };
 
-static int option_no_checkout, option_bare, option_mirror;
+static int option_no_checkout, option_bare, option_mirror, option_single_branch = -1;
 static int option_local, option_no_hardlinks, option_shared, option_recursive;
 static char *option_template, *option_depth;
 static char *option_origin = NULL;
@@ -48,6 +48,7 @@ static int option_verbosity;
 static int option_progress;
 static struct string_list option_config;
 static struct string_list option_reference;
+static const char *src_ref_prefix = "refs/heads/";
 
 static int opt_parse_reference(const struct option *opt, const char *arg, int unset)
 {
@@ -92,6 +93,8 @@ static struct option builtin_clone_options[] = {
                   "path to git-upload-pack on the remote"),
        OPT_STRING(0, "depth", &option_depth, "depth",
                    "create a shallow clone of that depth"),
+       OPT_BOOL(0, "single-branch", &option_single_branch,
+                   "clone only one branch, HEAD or --branch"),
        OPT_STRING(0, "separate-git-dir", &real_git_dir, "gitdir",
                   "separate git dir from working tree"),
        OPT_STRING_LIST('c', "config", &option_config, "key=value",
@@ -427,8 +430,28 @@ static struct ref *wanted_peer_refs(const struct ref *refs,
        struct ref *local_refs = head;
        struct ref **tail = head ? &head->next : &local_refs;
 
-       get_fetch_map(refs, refspec, &tail, 0);
-       if (!option_mirror)
+       if (option_single_branch) {
+               struct ref *remote_head = NULL;
+
+               if (!option_branch)
+                       remote_head = guess_remote_head(head, refs, 0);
+               else {
+                       struct strbuf sb = STRBUF_INIT;
+                       strbuf_addstr(&sb, src_ref_prefix);
+                       strbuf_addstr(&sb, option_branch);
+                       remote_head = find_ref_by_name(refs, sb.buf);
+                       strbuf_release(&sb);
+               }
+
+               if (!remote_head && option_branch)
+                       warning(_("Could not find remote branch %s to clone."),
+                               option_branch);
+               else
+                       get_fetch_map(remote_head, refspec, &tail, 0);
+       } else
+               get_fetch_map(refs, refspec, &tail, 0);
+
+       if (!option_mirror && !option_single_branch)
                get_fetch_map(refs, tag_refspec, &tail, 0);
 
        return local_refs;
@@ -441,11 +464,25 @@ static void write_remote_refs(const struct ref *local_refs)
        for (r = local_refs; r; r = r->next) {
                if (!r->peer_ref)
                        continue;
-               add_extra_ref(r->peer_ref->name, r->old_sha1, 0);
+               add_packed_ref(r->peer_ref->name, r->old_sha1);
        }
 
        pack_refs(PACK_REFS_ALL);
-       clear_extra_refs();
+}
+
+static void write_followtags(const struct ref *refs, const char *msg)
+{
+       const struct ref *ref;
+       for (ref = refs; ref; ref = ref->next) {
+               if (prefixcmp(ref->name, "refs/tags/"))
+                       continue;
+               if (!suffixcmp(ref->name, "^{}"))
+                       continue;
+               if (!has_sha1_file(ref->old_sha1))
+                       continue;
+               update_ref(msg, ref->name, ref->old_sha1,
+                          NULL, 0, DIE_ON_ERR);
+       }
 }
 
 static int write_one_config(const char *key, const char *value, void *data)
@@ -478,7 +515,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
        struct strbuf key = STRBUF_INIT, value = STRBUF_INIT;
        struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT;
        struct transport *transport = NULL;
-       char *src_ref_prefix = "refs/heads/";
        int err = 0;
 
        struct refspec *refspec;
@@ -498,6 +534,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                usage_msg_opt(_("You must specify a repository to clone."),
                        builtin_clone_usage, builtin_clone_options);
 
+       if (option_single_branch == -1)
+               option_single_branch = option_depth ? 1 : 0;
+
        if (option_mirror)
                option_bare = 1;
 
@@ -645,6 +684,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                if (option_depth)
                        transport_set_option(transport, TRANS_OPT_DEPTH,
                                             option_depth);
+               if (option_single_branch)
+                       transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1");
 
                transport_set_verbosity(transport, option_verbosity, option_progress);
 
@@ -663,6 +704,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                clear_extra_refs();
 
                write_remote_refs(mapped_refs);
+               if (option_single_branch)
+                       write_followtags(refs, reflog_msg.buf);
 
                remote_head = find_ref_by_name(refs, "HEAD");
                remote_head_points_at =
index 05353c30f292684b44b42f7f744d8eb1b7062f69..164b655df93fea1ec2f63f5238c1ad7a377c8385 100644 (file)
@@ -8,8 +8,9 @@
 #include "tree.h"
 #include "builtin.h"
 #include "utf8.h"
+#include "gpg-interface.h"
 
-static const char commit_tree_usage[] = "git commit-tree [(-p <sha1>)...] [-m <message>] [-F <file>] <sha1> <changelog";
+static const char commit_tree_usage[] = "git commit-tree [(-p <sha1>)...] [-S<signer>] [-m <message>] [-F <file>] <sha1> <changelog";
 
 static void new_parent(struct commit *parent, struct commit_list **parents_p)
 {
@@ -25,6 +26,14 @@ static void new_parent(struct commit *parent, struct commit_list **parents_p)
        commit_list_insert(parent, parents_p);
 }
 
+static int commit_tree_config(const char *var, const char *value, void *cb)
+{
+       int status = git_gpg_config(var, value, NULL);
+       if (status)
+               return status;
+       return git_default_config(var, value, cb);
+}
+
 int cmd_commit_tree(int argc, const char **argv, const char *prefix)
 {
        int i, got_tree = 0;
@@ -32,12 +41,16 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
        unsigned char tree_sha1[20];
        unsigned char commit_sha1[20];
        struct strbuf buffer = STRBUF_INIT;
+       const char *sign_commit = NULL;
 
-       git_config(git_default_config, NULL);
+       git_config(commit_tree_config, NULL);
 
        if (argc < 2 || !strcmp(argv[1], "-h"))
                usage(commit_tree_usage);
 
+       if (get_sha1(argv[1], tree_sha1))
+               die("Not a valid object name %s", argv[1]);
+
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
                if (!strcmp(arg, "-p")) {
@@ -51,6 +64,11 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
                        continue;
                }
 
+               if (!memcmp(arg, "-S", 2)) {
+                       sign_commit = arg + 2;
+                       continue;
+               }
+
                if (!strcmp(arg, "-m")) {
                        if (argc <= ++i)
                                usage(commit_tree_usage);
@@ -98,7 +116,8 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
                        die_errno("git commit-tree: failed to read");
        }
 
-       if (commit_tree(&buffer, tree_sha1, parents, commit_sha1, NULL)) {
+       if (commit_tree(&buffer, tree_sha1, parents, commit_sha1,
+                       NULL, sign_commit)) {
                strbuf_release(&buffer);
                return 1;
        }
index 5891e95758a0d2530b325bd5fd346e15702e3017..eba1377eb32c02e57c46364c381df940afa66048 100644 (file)
@@ -26,6 +26,7 @@
 #include "unpack-trees.h"
 #include "quote.h"
 #include "submodule.h"
+#include "gpg-interface.h"
 
 static const char * const builtin_commit_usage[] = {
        "git commit [options] [--] <filepattern>...",
@@ -86,6 +87,8 @@ static int edit_flag = -1; /* unspecified */
 static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
 static int no_post_rewrite, allow_empty_message;
 static char *untracked_files_arg, *force_date, *ignore_submodule_arg;
+static char *sign_commit;
+
 /*
  * The default commit message cleanup mode will remove the lines
  * beginning with # (shell comments) and leading and trailing
@@ -145,6 +148,8 @@ static struct option builtin_commit_options[] = {
        OPT_BOOL('e', "edit", &edit_flag, "force edit of commit"),
        OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"),
        OPT_BOOLEAN(0, "status", &include_status, "include status in commit message template"),
+       { OPTION_STRING, 'S', "gpg-sign", &sign_commit, "key id",
+         "GPG sign commit", PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
        /* end commit message options */
 
        OPT_GROUP("Commit contents options"),
@@ -1325,6 +1330,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1,
 static int git_commit_config(const char *k, const char *v, void *cb)
 {
        struct wt_status *s = cb;
+       int status;
 
        if (!strcmp(k, "commit.template"))
                return git_config_pathname(&template_file, k, v);
@@ -1333,6 +1339,9 @@ static int git_commit_config(const char *k, const char *v, void *cb)
                return 0;
        }
 
+       status = git_gpg_config(k, v, NULL);
+       if (status)
+               return status;
        return git_status_config(k, v, s);
 }
 
@@ -1486,14 +1495,15 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
        }
 
        if (amend) {
-               extra = read_commit_extra_headers(current_head);
+               const char *exclude_gpgsig[2] = { "gpgsig", NULL };
+               extra = read_commit_extra_headers(current_head, exclude_gpgsig);
        } else {
                struct commit_extra_header **tail = &extra;
                append_merge_tag_headers(parents, &tail);
        }
 
        if (commit_tree_extended(&sb, active_cache_tree->sha1, parents, sha1,
-                                author_ident.buf, extra)) {
+                                author_ident.buf, sign_commit, extra)) {
                rollback_index_files();
                die(_("failed to write commit object"));
        }
index 33ad3aad2c99116a237d7e7d11dab22fcf4295b1..ab186332fa881e0f823b0042f2d650fda8f365a8 100644 (file)
@@ -377,6 +377,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
        const char *what, *kind;
        struct ref *rm;
        char *url, *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD");
+       int want_merge;
 
        fp = fopen(filename, "a");
        if (!fp)
@@ -393,84 +394,95 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
                goto abort;
        }
 
-       for (rm = ref_map; rm; rm = rm->next) {
-               struct ref *ref = NULL;
-
-               if (rm->peer_ref) {
-                       ref = xcalloc(1, sizeof(*ref) + strlen(rm->peer_ref->name) + 1);
-                       strcpy(ref->name, rm->peer_ref->name);
-                       hashcpy(ref->old_sha1, rm->peer_ref->old_sha1);
-                       hashcpy(ref->new_sha1, rm->old_sha1);
-                       ref->force = rm->peer_ref->force;
-               }
+       /*
+        * The first pass writes objects to be merged and then the
+        * second pass writes the rest, in order to allow using
+        * FETCH_HEAD as a refname to refer to the ref to be merged.
+        */
+       for (want_merge = 1; 0 <= want_merge; want_merge--) {
+               for (rm = ref_map; rm; rm = rm->next) {
+                       struct ref *ref = NULL;
+
+                       commit = lookup_commit_reference_gently(rm->old_sha1, 1);
+                       if (!commit)
+                               rm->merge = 0;
+
+                       if (rm->merge != want_merge)
+                               continue;
+
+                       if (rm->peer_ref) {
+                               ref = xcalloc(1, sizeof(*ref) + strlen(rm->peer_ref->name) + 1);
+                               strcpy(ref->name, rm->peer_ref->name);
+                               hashcpy(ref->old_sha1, rm->peer_ref->old_sha1);
+                               hashcpy(ref->new_sha1, rm->old_sha1);
+                               ref->force = rm->peer_ref->force;
+                       }
 
-               commit = lookup_commit_reference_gently(rm->old_sha1, 1);
-               if (!commit)
-                       rm->merge = 0;
 
-               if (!strcmp(rm->name, "HEAD")) {
-                       kind = "";
-                       what = "";
-               }
-               else if (!prefixcmp(rm->name, "refs/heads/")) {
-                       kind = "branch";
-                       what = rm->name + 11;
-               }
-               else if (!prefixcmp(rm->name, "refs/tags/")) {
-                       kind = "tag";
-                       what = rm->name + 10;
-               }
-               else if (!prefixcmp(rm->name, "refs/remotes/")) {
-                       kind = "remote-tracking branch";
-                       what = rm->name + 13;
-               }
-               else {
-                       kind = "";
-                       what = rm->name;
-               }
+                       if (!strcmp(rm->name, "HEAD")) {
+                               kind = "";
+                               what = "";
+                       }
+                       else if (!prefixcmp(rm->name, "refs/heads/")) {
+                               kind = "branch";
+                               what = rm->name + 11;
+                       }
+                       else if (!prefixcmp(rm->name, "refs/tags/")) {
+                               kind = "tag";
+                               what = rm->name + 10;
+                       }
+                       else if (!prefixcmp(rm->name, "refs/remotes/")) {
+                               kind = "remote-tracking branch";
+                               what = rm->name + 13;
+                       }
+                       else {
+                               kind = "";
+                               what = rm->name;
+                       }
 
-               url_len = strlen(url);
-               for (i = url_len - 1; url[i] == '/' && 0 <= i; i--)
-                       ;
-               url_len = i + 1;
-               if (4 < i && !strncmp(".git", url + i - 3, 4))
-                       url_len = i - 3;
-
-               strbuf_reset(&note);
-               if (*what) {
-                       if (*kind)
-                               strbuf_addf(&note, "%s ", kind);
-                       strbuf_addf(&note, "'%s' of ", what);
-               }
-               fprintf(fp, "%s\t%s\t%s",
-                       sha1_to_hex(rm->old_sha1),
-                       rm->merge ? "" : "not-for-merge",
-                       note.buf);
-               for (i = 0; i < url_len; ++i)
-                       if ('\n' == url[i])
-                               fputs("\\n", fp);
-                       else
-                               fputc(url[i], fp);
-               fputc('\n', fp);
-
-               strbuf_reset(&note);
-               if (ref) {
-                       rc |= update_local_ref(ref, what, &note);
-                       free(ref);
-               } else
-                       strbuf_addf(&note, "* %-*s %-*s -> FETCH_HEAD",
-                                   TRANSPORT_SUMMARY_WIDTH,
-                                   *kind ? kind : "branch",
-                                   REFCOL_WIDTH,
-                                   *what ? what : "HEAD");
-               if (note.len) {
-                       if (verbosity >= 0 && !shown_url) {
-                               fprintf(stderr, _("From %.*s\n"),
-                                               url_len, url);
-                               shown_url = 1;
+                       url_len = strlen(url);
+                       for (i = url_len - 1; url[i] == '/' && 0 <= i; i--)
+                               ;
+                       url_len = i + 1;
+                       if (4 < i && !strncmp(".git", url + i - 3, 4))
+                               url_len = i - 3;
+
+                       strbuf_reset(&note);
+                       if (*what) {
+                               if (*kind)
+                                       strbuf_addf(&note, "%s ", kind);
+                               strbuf_addf(&note, "'%s' of ", what);
+                       }
+                       fprintf(fp, "%s\t%s\t%s",
+                               sha1_to_hex(rm->old_sha1),
+                               rm->merge ? "" : "not-for-merge",
+                               note.buf);
+                       for (i = 0; i < url_len; ++i)
+                               if ('\n' == url[i])
+                                       fputs("\\n", fp);
+                               else
+                                       fputc(url[i], fp);
+                       fputc('\n', fp);
+
+                       strbuf_reset(&note);
+                       if (ref) {
+                               rc |= update_local_ref(ref, what, &note);
+                               free(ref);
+                       } else
+                               strbuf_addf(&note, "* %-*s %-*s -> FETCH_HEAD",
+                                           TRANSPORT_SUMMARY_WIDTH,
+                                           *kind ? kind : "branch",
+                                           REFCOL_WIDTH,
+                                           *what ? what : "HEAD");
+                       if (note.len) {
+                               if (verbosity >= 0 && !shown_url) {
+                                       fprintf(stderr, _("From %.*s\n"),
+                                                       url_len, url);
+                                       shown_url = 1;
+                               }
+                               if (verbosity >= 0)
+                                       fprintf(stderr, " %s\n", note.buf);
                        }
-                       if (verbosity >= 0)
-                               fprintf(stderr, " %s\n", note.buf);
                }
        }
 
@@ -573,7 +585,7 @@ static void find_non_local_tags(struct transport *transport,
 
        for_each_ref(add_existing, &existing_refs);
        for (ref = transport_get_remote_refs(transport); ref; ref = ref->next) {
-               if (prefixcmp(ref->name, "refs/tags"))
+               if (prefixcmp(ref->name, "refs/tags/"))
                        continue;
 
                /*
index bfb32b7233850a68bdc226038a9c0f973037499b..eaf9e157a3897c2442756911b906ff9d3ee2be90 100644 (file)
@@ -250,8 +250,17 @@ static void cleanup_subject(struct strbuf *subject)
                            (7 <= remove &&
                             memmem(subject->buf + at, remove, "PATCH", 5)))
                                strbuf_remove(subject, at, remove);
-                       else
+                       else {
                                at += remove;
+                               /*
+                                * If the input had a space after the ], keep
+                                * it.  We don't bother with finding the end of
+                                * the space, since we later normalize it
+                                * anyway.
+                                */
+                               if (isspace(subject->buf[at]))
+                                       at += 1;
+                       }
                        continue;
                }
                break;
index 4b0ca6550c130dc8efbf248c56503018b8bcb604..3a451727d0e637ae197c9a1193435e57e84e6c58 100644 (file)
@@ -27,6 +27,7 @@
 #include "resolve-undo.h"
 #include "remote.h"
 #include "fmt-merge-msg.h"
+#include "gpg-interface.h"
 
 #define DEFAULT_TWOHEAD (1<<0)
 #define DEFAULT_OCTOPUS (1<<1)
@@ -64,6 +65,7 @@ static int allow_rerere_auto;
 static int abort_current_merge;
 static int show_progress = -1;
 static int default_to_upstream;
+static const char *sign_commit;
 
 static struct strategy all_strategy[] = {
        { "recursive",  DEFAULT_TWOHEAD | NO_TRIVIAL },
@@ -209,6 +211,8 @@ static struct option builtin_merge_options[] = {
        OPT_BOOLEAN(0, "abort", &abort_current_merge,
                "abort the current in-progress merge"),
        OPT_SET_INT(0, "progress", &show_progress, "force progress reporting", 1),
+       { OPTION_STRING, 'S', "gpg-sign", &sign_commit, "key id",
+         "GPG sign commit", PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
        OPT_BOOLEAN(0, "overwrite-ignore", &overwrite_ignore, "update ignored files (default)"),
        OPT_END()
 };
@@ -571,7 +575,11 @@ static int git_merge_config(const char *k, const char *v, void *cb)
                default_to_upstream = git_config_bool(k, v);
                return 0;
        }
+
        status = fmt_merge_msg_config(k, v, cb);
+       if (status)
+               return status;
+       status = git_gpg_config(k, v, NULL);
        if (status)
                return status;
        return git_diff_ui_config(k, v, cb);
@@ -910,7 +918,8 @@ static int merge_trivial(struct commit *head)
        parent->next->item = remoteheads->item;
        parent->next->next = NULL;
        prepare_to_commit();
-       if (commit_tree(&merge_msg, result_tree, parent, result_commit, NULL))
+       if (commit_tree(&merge_msg, result_tree, parent, result_commit, NULL,
+                       sign_commit))
                die(_("failed to write commit object"));
        finish(head, result_commit, "In-index merge");
        drop_save();
@@ -942,7 +951,8 @@ static int finish_automerge(struct commit *head,
        strbuf_addch(&merge_msg, '\n');
        prepare_to_commit();
        free_commit_list(remoteheads);
-       if (commit_tree(&merge_msg, result_tree, parents, result_commit, NULL))
+       if (commit_tree(&merge_msg, result_tree, parents, result_commit,
+                       NULL, sign_commit))
                die(_("failed to write commit object"));
        strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy);
        finish(head, result_commit, buf.buf);
index 96c1680976fc653a1cd7a9dd81a567885a5b0050..0f2e7b8f5cb26910679c39550d59ef23353a278f 100644 (file)
@@ -1434,11 +1434,16 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,
                return -1;
 
        /*
-        * We do not bother to try a delta that we discarded
-        * on an earlier try, but only when reusing delta data.
+        * We do not bother to try a delta that we discarded on an
+        * earlier try, but only when reusing delta data.  Note that
+        * src_entry that is marked as the preferred_base should always
+        * be considered, as even if we produce a suboptimal delta against
+        * it, we will still save the transfer cost, as we already know
+        * the other side has it and we won't send src_entry at all.
         */
        if (reuse_delta && trg_entry->in_pack &&
            trg_entry->in_pack == src_entry->in_pack &&
+           !src_entry->preferred_base &&
            trg_entry->in_pack_type != OBJ_REF_DELTA &&
            trg_entry->in_pack_type != OBJ_OFS_DELTA)
                return 0;
index d2dcb7e4af259a43a539f83ab5961472c594c99a..fa7448be5aaf9d2830168b77f9a555e9b6740993 100644 (file)
@@ -33,6 +33,7 @@ static int transfer_unpack_limit = -1;
 static int unpack_limit = 100;
 static int report_status;
 static int use_sideband;
+static int quiet;
 static int prefer_ofs_delta = 1;
 static int auto_update_server_info;
 static int auto_gc = 1;
@@ -115,20 +116,19 @@ static int receive_pack_config(const char *var, const char *value, void *cb)
        return git_default_config(var, value, cb);
 }
 
-static int show_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+static void show_ref(const char *path, const unsigned char *sha1)
 {
        if (sent_capabilities)
                packet_write(1, "%s %s\n", sha1_to_hex(sha1), path);
        else
                packet_write(1, "%s %s%c%s%s\n",
                             sha1_to_hex(sha1), path, 0,
-                            " report-status delete-refs side-band-64k",
+                            " report-status delete-refs side-band-64k quiet",
                             prefer_ofs_delta ? " ofs-delta" : "");
        sent_capabilities = 1;
-       return 0;
 }
 
-static int show_ref_cb(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+static int show_ref_cb(const char *path, const unsigned char *sha1, int flag, void *unused)
 {
        path = strip_namespace(path);
        /*
@@ -141,15 +141,33 @@ static int show_ref_cb(const char *path, const unsigned char *sha1, int flag, vo
         */
        if (!path)
                path = ".have";
-       return show_ref(path, sha1, flag, cb_data);
+       show_ref(path, sha1);
+       return 0;
+}
+
+static void show_one_alternate_sha1(const unsigned char sha1[20], void *unused)
+{
+       show_ref(".have", sha1);
+}
+
+static void collect_one_alternate_ref(const struct ref *ref, void *data)
+{
+       struct sha1_array *sa = data;
+       sha1_array_append(sa, ref->old_sha1);
 }
 
 static void write_head_info(void)
 {
+       struct sha1_array sa = SHA1_ARRAY_INIT;
+       for_each_alternate_ref(collect_one_alternate_ref, &sa);
+       sha1_array_for_each_unique(&sa, show_one_alternate_sha1, NULL);
+       sha1_array_clear(&sa);
        for_each_ref(show_ref_cb, NULL);
        if (!sent_capabilities)
-               show_ref("capabilities^{}", null_sha1, 0, NULL);
+               show_ref("capabilities^{}", null_sha1);
 
+       /* EOF */
+       packet_flush(1);
 }
 
 struct command {
@@ -731,10 +749,13 @@ static struct command *read_head_info(void)
                refname = line + 82;
                reflen = strlen(refname);
                if (reflen + 82 < len) {
-                       if (strstr(refname + reflen + 1, "report-status"))
+                       const char *feature_list = refname + reflen + 1;
+                       if (parse_feature_request(feature_list, "report-status"))
                                report_status = 1;
-                       if (strstr(refname + reflen + 1, "side-band-64k"))
+                       if (parse_feature_request(feature_list, "side-band-64k"))
                                use_sideband = LARGE_PACKET_MAX;
+                       if (parse_feature_request(feature_list, "quiet"))
+                               quiet = 1;
                }
                cmd = xcalloc(1, sizeof(struct command) + len - 80);
                hashcpy(cmd->old_sha1, old_sha1);
@@ -788,8 +809,10 @@ static const char *unpack(void)
 
        if (ntohl(hdr.hdr_entries) < unpack_limit) {
                int code, i = 0;
-               const char *unpacker[4];
+               const char *unpacker[5];
                unpacker[i++] = "unpack-objects";
+               if (quiet)
+                       unpacker[i++] = "-q";
                if (fsck_objects)
                        unpacker[i++] = "--strict";
                unpacker[i++] = hdr_arg;
@@ -869,25 +892,6 @@ static int delete_only(struct command *commands)
        return 1;
 }
 
-static void add_one_alternate_sha1(const unsigned char sha1[20], void *unused)
-{
-       add_extra_ref(".have", sha1, 0);
-}
-
-static void collect_one_alternate_ref(const struct ref *ref, void *data)
-{
-       struct sha1_array *sa = data;
-       sha1_array_append(sa, ref->old_sha1);
-}
-
-static void add_alternate_refs(void)
-{
-       struct sha1_array sa = SHA1_ARRAY_INIT;
-       for_each_alternate_ref(collect_one_alternate_ref, &sa);
-       sha1_array_for_each_unique(&sa, add_one_alternate_sha1, NULL);
-       sha1_array_clear(&sa);
-}
-
 int cmd_receive_pack(int argc, const char **argv, const char *prefix)
 {
        int advertise_refs = 0;
@@ -903,6 +907,11 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
                const char *arg = *argv++;
 
                if (*arg == '-') {
+                       if (!strcmp(arg, "--quiet")) {
+                               quiet = 1;
+                               continue;
+                       }
+
                        if (!strcmp(arg, "--advertise-refs")) {
                                advertise_refs = 1;
                                continue;
@@ -937,12 +946,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
                unpack_limit = receive_unpack_limit;
 
        if (advertise_refs || !stateless_rpc) {
-               add_alternate_refs();
                write_head_info();
-               clear_extra_refs();
-
-               /* EOF */
-               packet_flush(1);
        }
        if (advertise_refs)
                return 0;
index 583eec90e0b69a4d47a819e70352d250bae3bcef..f54a89adc795fe86f76d60cff4de9487bc792bd3 100644 (file)
@@ -534,7 +534,7 @@ static int add_branch_for_removal(const char *refname,
        }
 
        /* don't delete non-remote-tracking refs */
-       if (prefixcmp(refname, "refs/remotes")) {
+       if (prefixcmp(refname, "refs/remotes/")) {
                /* advise user how to delete local branches */
                if (!prefixcmp(refname, "refs/heads/"))
                        string_list_append(branches->skipped,
index cd1115ffc687c642acd4bda09b1ea7976e1e0478..71f258ef6e620979a9dbeaa87d2501a304b3a0fa 100644 (file)
@@ -263,6 +263,8 @@ int send_pack(struct send_pack_args *args,
                args->use_ofs_delta = 1;
        if (server_supports("side-band-64k"))
                use_sideband = 1;
+       if (!server_supports("quiet"))
+               args->quiet = 0;
 
        if (!remote_refs) {
                fprintf(stderr, "No refs in common and none specified; doing nothing.\n"
@@ -301,11 +303,12 @@ int send_pack(struct send_pack_args *args,
                        char *old_hex = sha1_to_hex(ref->old_sha1);
                        char *new_hex = sha1_to_hex(ref->new_sha1);
 
-                       if (!cmds_sent && (status_report || use_sideband)) {
-                               packet_buf_write(&req_buf, "%s %s %s%c%s%s",
+                       if (!cmds_sent && (status_report || use_sideband || args->quiet)) {
+                               packet_buf_write(&req_buf, "%s %s %s%c%s%s%s",
                                        old_hex, new_hex, ref->name, 0,
                                        status_report ? " report-status" : "",
-                                       use_sideband ? " side-band-64k" : "");
+                                       use_sideband ? " side-band-64k" : "",
+                                       args->quiet ? " quiet" : "");
                        }
                        else
                                packet_buf_write(&req_buf, "%s %s %s",
@@ -439,6 +442,10 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
                                args.force_update = 1;
                                continue;
                        }
+                       if (!strcmp(arg, "--quiet")) {
+                               args.quiet = 1;
+                               continue;
+                       }
                        if (!strcmp(arg, "--verbose")) {
                                args.verbose = 1;
                                continue;
diff --git a/cache.h b/cache.h
index 10afd71d435920a3cc2152208a06058791ea7a04..9bd8c2d06c80c958b5f654fe16e7294883e49a30 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -1037,6 +1037,7 @@ struct extra_have_objects {
 };
 extern struct ref **get_remote_heads(int in, struct ref **list, unsigned int flags, struct extra_have_objects *);
 extern int server_supports(const char *feature);
+extern const char *parse_feature_request(const char *features, const char *feature);
 
 extern struct packed_git *parse_pack_index(unsigned char *sha1, const char *idx_path);
 
index 44bc96d44d391209d3a60e9e8c1b98ed209e6b4a..35af4988f0ff83c6a3379ea9f6de4e4e1568c39f 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -6,6 +6,7 @@
 #include "diff.h"
 #include "revision.h"
 #include "notes.h"
+#include "gpg-interface.h"
 
 int save_commit_buffer = 1;
 
@@ -840,6 +841,86 @@ struct commit_list *reduce_heads(struct commit_list *heads)
        return result;
 }
 
+static const char gpg_sig_header[] = "gpgsig";
+static const int gpg_sig_header_len = sizeof(gpg_sig_header) - 1;
+
+static int do_sign_commit(struct strbuf *buf, const char *keyid)
+{
+       struct strbuf sig = STRBUF_INIT;
+       int inspos, copypos;
+
+       /* find the end of the header */
+       inspos = strstr(buf->buf, "\n\n") - buf->buf + 1;
+
+       if (!keyid || !*keyid)
+               keyid = get_signing_key();
+       if (sign_buffer(buf, &sig, keyid)) {
+               strbuf_release(&sig);
+               return -1;
+       }
+
+       for (copypos = 0; sig.buf[copypos]; ) {
+               const char *bol = sig.buf + copypos;
+               const char *eol = strchrnul(bol, '\n');
+               int len = (eol - bol) + !!*eol;
+
+               if (!copypos) {
+                       strbuf_insert(buf, inspos, gpg_sig_header, gpg_sig_header_len);
+                       inspos += gpg_sig_header_len;
+               }
+               strbuf_insert(buf, inspos++, " ", 1);
+               strbuf_insert(buf, inspos, bol, len);
+               inspos += len;
+               copypos += len;
+       }
+       strbuf_release(&sig);
+       return 0;
+}
+
+int parse_signed_commit(const unsigned char *sha1,
+                       struct strbuf *payload, struct strbuf *signature)
+{
+       unsigned long size;
+       enum object_type type;
+       char *buffer = read_sha1_file(sha1, &type, &size);
+       int in_signature, saw_signature = -1;
+       char *line, *tail;
+
+       if (!buffer || type != OBJ_COMMIT)
+               goto cleanup;
+
+       line = buffer;
+       tail = buffer + size;
+       in_signature = 0;
+       saw_signature = 0;
+       while (line < tail) {
+               const char *sig = NULL;
+               char *next = memchr(line, '\n', tail - line);
+
+               next = next ? next + 1 : tail;
+               if (in_signature && line[0] == ' ')
+                       sig = line + 1;
+               else if (!prefixcmp(line, gpg_sig_header) &&
+                        line[gpg_sig_header_len] == ' ')
+                       sig = line + gpg_sig_header_len + 1;
+               if (sig) {
+                       strbuf_add(signature, sig, next - sig);
+                       saw_signature = 1;
+                       in_signature = 1;
+               } else {
+                       if (*line == '\n')
+                               /* dump the whole remainder of the buffer */
+                               next = tail;
+                       strbuf_add(payload, line, next - line);
+                       in_signature = 0;
+               }
+               line = next;
+       }
+ cleanup:
+       free(buffer);
+       return saw_signature;
+}
+
 static void handle_signed_tag(struct commit *parent, struct commit_extra_header ***tail)
 {
        struct merge_remote_desc *desc;
@@ -900,14 +981,15 @@ static void add_extra_header(struct strbuf *buffer,
                strbuf_addch(buffer, '\n');
 }
 
-struct commit_extra_header *read_commit_extra_headers(struct commit *commit)
+struct commit_extra_header *read_commit_extra_headers(struct commit *commit,
+                                                     const char **exclude)
 {
        struct commit_extra_header *extra = NULL;
        unsigned long size;
        enum object_type type;
        char *buffer = read_sha1_file(commit->object.sha1, &type, &size);
        if (buffer && type == OBJ_COMMIT)
-               extra = read_commit_extra_header_lines(buffer, size);
+               extra = read_commit_extra_header_lines(buffer, size, exclude);
        free(buffer);
        return extra;
 }
@@ -921,7 +1003,23 @@ static inline int standard_header_field(const char *field, size_t len)
                (len == 8 && !memcmp(field, "encoding ", 9)));
 }
 
-struct commit_extra_header *read_commit_extra_header_lines(const char *buffer, size_t size)
+static int excluded_header_field(const char *field, size_t len, const char **exclude)
+{
+       if (!exclude)
+               return 0;
+
+       while (*exclude) {
+               size_t xlen = strlen(*exclude);
+               if (len == xlen &&
+                   !memcmp(field, *exclude, xlen) && field[xlen] == ' ')
+                       return 1;
+               exclude++;
+       }
+       return 0;
+}
+
+struct commit_extra_header *read_commit_extra_header_lines(const char *buffer, size_t size,
+                                                          const char **exclude)
 {
        struct commit_extra_header *extra = NULL, **tail = &extra, *it = NULL;
        const char *line, *next, *eof, *eob;
@@ -947,7 +1045,8 @@ struct commit_extra_header *read_commit_extra_header_lines(const char *buffer, s
                if (next <= eof)
                        eof = next;
 
-               if (standard_header_field(line, eof - line))
+               if (standard_header_field(line, eof - line) ||
+                   excluded_header_field(line, eof - line, exclude))
                        continue;
 
                it = xcalloc(1, sizeof(*it));
@@ -975,13 +1074,14 @@ void free_commit_extra_headers(struct commit_extra_header *extra)
 
 int commit_tree(const struct strbuf *msg, unsigned char *tree,
                struct commit_list *parents, unsigned char *ret,
-               const char *author)
+               const char *author, const char *sign_commit)
 {
        struct commit_extra_header *extra = NULL, **tail = &extra;
        int result;
 
        append_merge_tag_headers(parents, &tail);
-       result = commit_tree_extended(msg, tree, parents, ret, author, extra);
+       result = commit_tree_extended(msg, tree, parents, ret,
+                                     author, sign_commit, extra);
        free_commit_extra_headers(extra);
        return result;
 }
@@ -993,7 +1093,8 @@ static const char commit_utf8_warn[] =
 
 int commit_tree_extended(const struct strbuf *msg, unsigned char *tree,
                         struct commit_list *parents, unsigned char *ret,
-                        const char *author, struct commit_extra_header *extra)
+                        const char *author, const char *sign_commit,
+                        struct commit_extra_header *extra)
 {
        int result;
        int encoding_is_utf8;
@@ -1046,6 +1147,9 @@ int commit_tree_extended(const struct strbuf *msg, unsigned char *tree,
        if (encoding_is_utf8 && !is_utf8(buffer.buf))
                fprintf(stderr, commit_utf8_warn);
 
+       if (sign_commit && do_sign_commit(&buffer, sign_commit))
+               return -1;
+
        result = write_sha1_file(buffer.buf, buffer.len, commit_type, ret);
        strbuf_release(&buffer);
        return result;
index 4df397865abe7161a9f16e3177e3b486bf8434ca..154c0e34ff7d2dbaddcfb66b74d26697ffba6381 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -193,15 +193,15 @@ extern void append_merge_tag_headers(struct commit_list *parents,
 
 extern int commit_tree(const struct strbuf *msg, unsigned char *tree,
                       struct commit_list *parents, unsigned char *ret,
-                      const char *author);
+                      const char *author, const char *sign_commit);
 
 extern int commit_tree_extended(const struct strbuf *msg, unsigned char *tree,
                                struct commit_list *parents, unsigned char *ret,
-                               const char *author,
+                               const char *author, const char *sign_commit,
                                struct commit_extra_header *);
 
-extern struct commit_extra_header *read_commit_extra_headers(struct commit *);
-extern struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len);
+extern struct commit_extra_header *read_commit_extra_headers(struct commit *, const char **);
+extern struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **);
 
 extern void free_commit_extra_headers(struct commit_extra_header *extra);
 
@@ -218,4 +218,6 @@ struct merge_remote_desc {
  */
 struct commit *get_merge_parent(const char *name);
 
+extern int parse_signed_commit(const unsigned char *sha1,
+                              struct strbuf *message, struct strbuf *signature);
 #endif /* COMMIT_H */
index 2ea5c3c0fbedb89e4189d32a455f289398ce106b..912cddeea8c4f09ec523ce19e677358c84eda9b7 100644 (file)
--- a/connect.c
+++ b/connect.c
@@ -101,8 +101,27 @@ struct ref **get_remote_heads(int in, struct ref **list,
 
 int server_supports(const char *feature)
 {
-       return server_capabilities &&
-               strstr(server_capabilities, feature) != NULL;
+       return !!parse_feature_request(server_capabilities, feature);
+}
+
+const char *parse_feature_request(const char *feature_list, const char *feature)
+{
+       int len;
+
+       if (!feature_list)
+               return NULL;
+
+       len = strlen(feature);
+       while (*feature_list) {
+               const char *found = strstr(feature_list, feature);
+               if (!found)
+                       return NULL;
+               if ((feature_list == found || isspace(found[-1])) &&
+                   (!found[len] || isspace(found[len]) || found[len] == '='))
+                       return found;
+               feature_list = found + 1;
+       }
+       return NULL;
 }
 
 enum protocol {
index b0062bac22347e42dd64047c51dfb2bff92243ef..1496c6dc05822d3009aaf7d88cc5a66dfca9ea23 100755 (executable)
@@ -2631,6 +2631,10 @@ _git ()
                # workaround zsh's bug that leaves 'words' as a special
                # variable in versions < 4.3.12
                typeset -h words
+
+               # workaround zsh's bug that quotes spaces in the COMPREPLY
+               # array if IFS doesn't contain spaces.
+               typeset -h IFS
        fi
 
        local cur words cword prev
@@ -2687,6 +2691,10 @@ _gitk ()
                # workaround zsh's bug that leaves 'words' as a special
                # variable in versions < 4.3.12
                typeset -h words
+
+               # workaround zsh's bug that quotes spaces in the COMPREPLY
+               # array if IFS doesn't contain spaces.
+               typeset -h IFS
        fi
 
        local cur words cword prev
index d3c3ad859c7fdf60756cba8623b6b34842bd5ca4..e11e15bad0ca6c552ffe9093af71afebe6e4323d 100755 (executable)
@@ -1169,6 +1169,219 @@ class P4Submit(Command, P4UserMap):
 
         return True
 
+class View(object):
+    """Represent a p4 view ("p4 help views"), and map files in a
+       repo according to the view."""
+
+    class Path(object):
+        """A depot or client path, possibly containing wildcards.
+           The only one supported is ... at the end, currently.
+           Initialize with the full path, with //depot or //client."""
+
+        def __init__(self, path, is_depot):
+            self.path = path
+            self.is_depot = is_depot
+            self.find_wildcards()
+            # remember the prefix bit, useful for relative mappings
+            m = re.match("(//[^/]+/)", self.path)
+            if not m:
+                die("Path %s does not start with //prefix/" % self.path)
+            prefix = m.group(1)
+            if not self.is_depot:
+                # strip //client/ on client paths
+                self.path = self.path[len(prefix):]
+
+        def find_wildcards(self):
+            """Make sure wildcards are valid, and set up internal
+               variables."""
+
+            self.ends_triple_dot = False
+            # There are three wildcards allowed in p4 views
+            # (see "p4 help views").  This code knows how to
+            # handle "..." (only at the end), but cannot deal with
+            # "%%n" or "*".  Only check the depot_side, as p4 should
+            # validate that the client_side matches too.
+            if re.search(r'%%[1-9]', self.path):
+                die("Can't handle %%n wildcards in view: %s" % self.path)
+            if self.path.find("*") >= 0:
+                die("Can't handle * wildcards in view: %s" % self.path)
+            triple_dot_index = self.path.find("...")
+            if triple_dot_index >= 0:
+                if triple_dot_index != len(self.path) - 3:
+                    die("Can handle only single ... wildcard, at end: %s" %
+                        self.path)
+                self.ends_triple_dot = True
+
+        def ensure_compatible(self, other_path):
+            """Make sure the wildcards agree."""
+            if self.ends_triple_dot != other_path.ends_triple_dot:
+                 die("Both paths must end with ... if either does;\n" +
+                     "paths: %s %s" % (self.path, other_path.path))
+
+        def match_wildcards(self, test_path):
+            """See if this test_path matches us, and fill in the value
+               of the wildcards if so.  Returns a tuple of
+               (True|False, wildcards[]).  For now, only the ... at end
+               is supported, so at most one wildcard."""
+            if self.ends_triple_dot:
+                dotless = self.path[:-3]
+                if test_path.startswith(dotless):
+                    wildcard = test_path[len(dotless):]
+                    return (True, [ wildcard ])
+            else:
+                if test_path == self.path:
+                    return (True, [])
+            return (False, [])
+
+        def match(self, test_path):
+            """Just return if it matches; don't bother with the wildcards."""
+            b, _ = self.match_wildcards(test_path)
+            return b
+
+        def fill_in_wildcards(self, wildcards):
+            """Return the relative path, with the wildcards filled in
+               if there are any."""
+            if self.ends_triple_dot:
+                return self.path[:-3] + wildcards[0]
+            else:
+                return self.path
+
+    class Mapping(object):
+        def __init__(self, depot_side, client_side, overlay, exclude):
+            # depot_side is without the trailing /... if it had one
+            self.depot_side = View.Path(depot_side, is_depot=True)
+            self.client_side = View.Path(client_side, is_depot=False)
+            self.overlay = overlay  # started with "+"
+            self.exclude = exclude  # started with "-"
+            assert not (self.overlay and self.exclude)
+            self.depot_side.ensure_compatible(self.client_side)
+
+        def __str__(self):
+            c = " "
+            if self.overlay:
+                c = "+"
+            if self.exclude:
+                c = "-"
+            return "View.Mapping: %s%s -> %s" % \
+                   (c, self.depot_side.path, self.client_side.path)
+
+        def map_depot_to_client(self, depot_path):
+            """Calculate the client path if using this mapping on the
+               given depot path; does not consider the effect of other
+               mappings in a view.  Even excluded mappings are returned."""
+            matches, wildcards = self.depot_side.match_wildcards(depot_path)
+            if not matches:
+                return ""
+            client_path = self.client_side.fill_in_wildcards(wildcards)
+            return client_path
+
+    #
+    # View methods
+    #
+    def __init__(self):
+        self.mappings = []
+
+    def append(self, view_line):
+        """Parse a view line, splitting it into depot and client
+           sides.  Append to self.mappings, preserving order."""
+
+        # Split the view line into exactly two words.  P4 enforces
+        # structure on these lines that simplifies this quite a bit.
+        #
+        # Either or both words may be double-quoted.
+        # Single quotes do not matter.
+        # Double-quote marks cannot occur inside the words.
+        # A + or - prefix is also inside the quotes.
+        # There are no quotes unless they contain a space.
+        # The line is already white-space stripped.
+        # The two words are separated by a single space.
+        #
+        if view_line[0] == '"':
+            # First word is double quoted.  Find its end.
+            close_quote_index = view_line.find('"', 1)
+            if close_quote_index <= 0:
+                die("No first-word closing quote found: %s" % view_line)
+            depot_side = view_line[1:close_quote_index]
+            # skip closing quote and space
+            rhs_index = close_quote_index + 1 + 1
+        else:
+            space_index = view_line.find(" ")
+            if space_index <= 0:
+                die("No word-splitting space found: %s" % view_line)
+            depot_side = view_line[0:space_index]
+            rhs_index = space_index + 1
+
+        if view_line[rhs_index] == '"':
+            # Second word is double quoted.  Make sure there is a
+            # double quote at the end too.
+            if not view_line.endswith('"'):
+                die("View line with rhs quote should end with one: %s" %
+                    view_line)
+            # skip the quotes
+            client_side = view_line[rhs_index+1:-1]
+        else:
+            client_side = view_line[rhs_index:]
+
+        # prefix + means overlay on previous mapping
+        overlay = False
+        if depot_side.startswith("+"):
+            overlay = True
+            depot_side = depot_side[1:]
+
+        # prefix - means exclude this path
+        exclude = False
+        if depot_side.startswith("-"):
+            exclude = True
+            depot_side = depot_side[1:]
+
+        m = View.Mapping(depot_side, client_side, overlay, exclude)
+        self.mappings.append(m)
+
+    def map_in_client(self, depot_path):
+        """Return the relative location in the client where this
+           depot file should live.  Returns "" if the file should
+           not be mapped in the client."""
+
+        paths_filled = []
+        client_path = ""
+
+        # look at later entries first
+        for m in self.mappings[::-1]:
+
+            # see where will this path end up in the client
+            p = m.map_depot_to_client(depot_path)
+
+            if p == "":
+                # Depot path does not belong in client.  Must remember
+                # this, as previous items should not cause files to
+                # exist in this path either.  Remember that the list is
+                # being walked from the end, which has higher precedence.
+                # Overlap mappings do not exclude previous mappings.
+                if not m.overlay:
+                    paths_filled.append(m.client_side)
+
+            else:
+                # This mapping matched; no need to search any further.
+                # But, the mapping could be rejected if the client path
+                # has already been claimed by an earlier mapping (i.e.
+                # one later in the list, which we are walking backwards).
+                already_mapped_in_client = False
+                for f in paths_filled:
+                    # this is View.Path.match
+                    if f.match(p):
+                        already_mapped_in_client = True
+                        break
+                if not already_mapped_in_client:
+                    # Include this file, unless it is from a line that
+                    # explicitly said to exclude it.
+                    if not m.exclude:
+                        client_path = p
+
+                # a match, even if rejected, always stops the search
+                break
+
+        return client_path
+
 class P4Sync(Command, P4UserMap):
     delete_actions = ( "delete", "move/delete", "purge" )
 
@@ -1216,7 +1429,7 @@ class P4Sync(Command, P4UserMap):
         self.p4BranchesInGit = []
         self.cloneExclude = []
         self.useClientSpec = False
-        self.clientSpecDirs = []
+        self.clientSpecDirs = None
 
         if gitConfig("git-p4.syncFromOrigin") == "false":
             self.syncWithOrigin = False
@@ -1267,20 +1480,7 @@ class P4Sync(Command, P4UserMap):
 
     def stripRepoPath(self, path, prefixes):
         if self.useClientSpec:
-
-            # if using the client spec, we use the output directory
-            # specified in the client.  For example, a view
-            #   //depot/foo/branch/... //client/branch/foo/...
-            # will end up putting all foo/branch files into
-            #  branch/foo/
-            for val in self.clientSpecDirs:
-                if path.startswith(val[0]):
-                    # replace the depot path with the client path
-                    path = path.replace(val[0], val[1][1])
-                    # now strip out the client (//client/...)
-                    path = re.sub("^(//[^/]+/)", '', path)
-                    # the rest is all path
-                    return path
+            return self.clientSpecDirs.map_in_client(path)
 
         if self.keepRepoPath:
             prefixes = [re.sub("^(//[^/]+/).*", r'\1', prefixes[0])]
@@ -1430,19 +1630,17 @@ class P4Sync(Command, P4UserMap):
         filesToDelete = []
 
         for f in files:
-            includeFile = True
-            for val in self.clientSpecDirs:
-                if f['path'].startswith(val[0]):
-                    if val[1][0] <= 0:
-                        includeFile = False
-                    break
+            # if using a client spec, only add the files that have
+            # a path in the client
+            if self.clientSpecDirs:
+                if self.clientSpecDirs.map_in_client(f['path']) == "":
+                    continue
 
-            if includeFile:
-                filesForCommit.append(f)
-                if f['action'] in self.delete_actions:
-                    filesToDelete.append(f)
-                else:
-                    filesToRead.append(f)
+            filesForCommit.append(f)
+            if f['action'] in self.delete_actions:
+                filesToDelete.append(f)
+            else:
+                filesToRead.append(f)
 
         # deleted files...
         for f in filesToDelete:
@@ -1881,50 +2079,31 @@ class P4Sync(Command, P4UserMap):
 
 
     def getClientSpec(self):
-        specList = p4CmdList( "client -o" )
-        temp = {}
-        for entry in specList:
-            for k,v in entry.iteritems():
-                if k.startswith("View"):
-
-                    # p4 has these %%1 to %%9 arguments in specs to
-                    # reorder paths; which we can't handle (yet :)
-                    if re.match('%%\d', v) != None:
-                        print "Sorry, can't handle %%n arguments in client specs"
-                        sys.exit(1)
-
-                    if v.startswith('"'):
-                        start = 1
-                    else:
-                        start = 0
-                    index = v.find("...")
-
-                    # save the "client view"; i.e the RHS of the view
-                    # line that tells the client where to put the
-                    # files for this view.
-                    cv = v[index+3:].strip() # +3 to remove previous '...'
-
-                    # if the client view doesn't end with a
-                    # ... wildcard, then we're going to mess up the
-                    # output directory, so fail gracefully.
-                    if not cv.endswith('...'):
-                        print 'Sorry, client view in "%s" needs to end with wildcard' % (k)
-                        sys.exit(1)
-                    cv=cv[:-3]
-
-                    # now save the view; +index means included, -index
-                    # means it should be filtered out.
-                    v = v[start:index]
-                    if v.startswith("-"):
-                        v = v[1:]
-                        include = -len(v)
-                    else:
-                        include = len(v)
+        specList = p4CmdList("client -o")
+        if len(specList) != 1:
+            die('Output from "client -o" is %d lines, expecting 1' %
+                len(specList))
+
+        # dictionary of all client parameters
+        entry = specList[0]
 
-                    temp[v] = (include, cv)
+        # just the keys that start with "View"
+        view_keys = [ k for k in entry.keys() if k.startswith("View") ]
 
-        self.clientSpecDirs = temp.items()
-        self.clientSpecDirs.sort( lambda x, y: abs( y[1][0] ) - abs( x[1][0] ) )
+        # hold this new View
+        view = View()
+
+        # append the lines, in order, to the view
+        for view_num in range(len(view_keys)):
+            k = "View%d" % view_num
+            if k not in view_keys:
+                die("Expected view key %s missing" % k)
+            view.append(entry[k])
+
+        self.clientSpecDirs = view
+        if self.verbose:
+            for i, m in enumerate(self.clientSpecDirs.mappings):
+                    print "clientSpecDirs %d: %s" % (i, str(m))
 
     def run(self, args):
         self.depotPaths = []
index dc98372e59d1baf2a9714ecde9ffc96589b9cbac..9a03792c7de109e957a1f01924c4f66ba87b5c87 100644 (file)
@@ -71,11 +71,14 @@ static void do_cache(const char *socket, const char *action, int timeout,
                        die_errno("unable to relay credential");
        }
 
-       if (!send_request(socket, &buf))
-               return;
-       if (flags & FLAG_SPAWN) {
-               spawn_daemon(socket);
-               send_request(socket, &buf);
+       if (send_request(socket, &buf) < 0) {
+               if (errno != ENOENT && errno != ECONNREFUSED)
+                       die_errno("unable to connect to cache daemon");
+               if (flags & FLAG_SPAWN) {
+                       spawn_daemon(socket);
+                       if (send_request(socket, &buf) < 0)
+                               die_errno("unable to connect to cache daemon");
+               }
        }
        strbuf_release(&buf);
 }
index 15ce918a21e1cce6cb603caa8638e4d854eee9dd..ab21e66b2fb69015c3085761b344b11053c67f4a 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -1086,6 +1086,8 @@ static int serve(struct string_list *listen_addr, int listen_port,
 
        drop_privileges(cred);
 
+       loginfo("Ready to rumble");
+
        return service_loop(&socklist);
 }
 
@@ -1270,10 +1272,8 @@ int main(int argc, char **argv)
        if (inetd_mode || serve_mode)
                return execute();
 
-       if (detach) {
+       if (detach)
                daemonize();
-               loginfo("Ready to rumble");
-       }
        else
                sanitize_stdfds();
 
index 62f4cd94cfbc4d3fe9e46c84f318af6624349a48..fc0dff31b58c8bd6668de5c6396a93b31cc5729d 100644 (file)
@@ -469,6 +469,8 @@ static int diff_cache(struct rev_info *revs,
        opts.src_index = &the_index;
        opts.dst_index = NULL;
        opts.pathspec = &revs->diffopt.pathspec;
+       opts.pathspec->recursive = 1;
+       opts.pathspec->max_depth = -1;
 
        init_tree_desc(&t, tree->buffer, tree->size);
        return unpack_trees(1, &t, &opts);
diff --git a/diff.c b/diff.c
index 374ecf3b486e218c52f94ba14481a715c19b80db..7e154265f778c645192cbf17c65b9bea2a507402 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -1113,6 +1113,15 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
                        diff_words_append(line, len,
                                          &ecbdata->diff_words->plus);
                        return;
+               } else if (!prefixcmp(line, "\\ ")) {
+                       /*
+                        * Eat the "no newline at eof" marker as if we
+                        * saw a "+" or "-" line with nothing on it,
+                        * and return without diff_words_flush() to
+                        * defer processing. If this is the end of
+                        * preimage, more "+" lines may come after it.
+                        */
+                       return;
                }
                diff_words_flush(ecbdata);
                if (ecbdata->diff_words->type == DIFF_WORDS_PORCELAIN) {
index 1c13b1399185506dd11750fe7e8c1b989a0f3d0c..64d8e2a64ddd2e6162e7a8fd93ab4294de3f161e 100755 (executable)
--- a/git-am.sh
+++ b/git-am.sh
@@ -15,6 +15,7 @@ q,quiet         be quiet
 s,signoff       add a Signed-off-by line to the commit message
 u,utf8          recode into utf8 (default)
 k,keep          pass -k flag to git-mailinfo
+keep-non-patch  pass -b flag to git-mailinfo
 keep-cr         pass --keep-cr flag to git-mailsplit for mbox format
 no-keep-cr      do not pass --keep-cr flag to git-mailsplit independent of am.keepcr
 c,scissors      strip everything before a scissors line
@@ -387,6 +388,8 @@ do
                utf8= ;;
        -k|--keep)
                keep=t ;;
+       --keep-non-patch)
+               keep=b ;;
        -c|--scissors)
                scissors=t ;;
        --no-scissors)
@@ -565,16 +568,25 @@ case "$resolved" in
        fi
 esac
 
+# Now, decide what command line options we will give to the git
+# commands we invoke, based on the result of parsing command line
+# options and previous invocation state stored in $dotest/ files.
+
 if test "$(cat "$dotest/utf8")" = t
 then
        utf8=-u
 else
        utf8=-n
 fi
-if test "$(cat "$dotest/keep")" = t
-then
-       keep=-k
-fi
+keep=$(cat "$dotest/keep")
+case "$keep" in
+t)
+       keep=-k ;;
+b)
+       keep=-b ;;
+*)
+       keep= ;;
+esac
 case "$(cat "$dotest/scissors")" in
 t)
        scissors=--scissors ;;
index 39a426e067c76e01d3f2a0d63243ec494436da46..e6bf25232c4ec27fc98f78e076678ae4d0a524c1 100755 (executable)
@@ -30,6 +30,13 @@ if ($opt_w || $opt_W) {
                chomp($gd);
                $ENV{GIT_DIR} = $gd;
        }
+
+       # On MSYS, convert a Windows-style path to an MSYS-style path
+       # so that rel2abs() below works correctly.
+       if ($^O eq 'msys') {
+               $ENV{GIT_DIR} =~ s#^([[:alpha:]]):/#/$1/#;
+       }
+
        # Make sure GIT_DIR is absolute
        $ENV{GIT_DIR} = File::Spec->rel2abs($ENV{GIT_DIR});
 }
index d7ba1178ae75019aba44f78d6cabd775a84ad526..64960d65a1c2bf3c260c8fd46c4b298ae870679f 100755 (executable)
@@ -96,7 +96,7 @@ git show -s --format='The following changes since commit %H:
   %s (%ci)
 
 are available in the git repository at:
-' $baserev &&
+' $merge_base &&
 echo "  $url${ref+ $ref}" &&
 git show -s --format='
 for you to fetch changes up to %H:
index d491db92c9379cf0eea63182dd704feb6582b6e7..ef30c557c7dee549e891fe7605902ba58d0566a3 100755 (executable)
@@ -210,6 +210,7 @@ my %config_bool_settings = (
     "signedoffbycc" => [\$signed_off_by_cc, undef],
     "signedoffcc" => [\$signed_off_by_cc, undef],      # Deprecated
     "validate" => [\$validate, 1],
+    "multiedit" => [\$multiedit, undef]
 );
 
 my %config_settings = (
@@ -227,7 +228,6 @@ my %config_settings = (
     "bcc" => \@bcclist,
     "suppresscc" => \@suppress_cc,
     "envelopesender" => \$envelope_sender,
-    "multiedit" => \$multiedit,
     "confirm"   => \$confirm,
     "from" => \$sender,
     "assume8bitencoding" => \$auto_8bit_encoding,
index c76669284ce48411b0cd580c5943e3eb0a785884..fe4ab28b2e10f85b20ff7892663cbb141fad4723 100755 (executable)
@@ -115,7 +115,8 @@ create_stash () {
                        git read-tree --index-output="$TMPindex" -m $i_tree &&
                        GIT_INDEX_FILE="$TMPindex" &&
                        export GIT_INDEX_FILE &&
-                       git diff --name-only -z HEAD | git update-index -z --add --remove --stdin &&
+                       git diff --name-only -z HEAD -- >"$TMP-stagenames" &&
+                       git update-index -z --add --remove --stdin <"$TMP-stagenames" &&
                        git write-tree &&
                        rm -f "$TMPindex"
                ) ) ||
@@ -134,7 +135,7 @@ create_stash () {
                w_tree=$(GIT_INDEX_FILE="$TMP-index" git write-tree) ||
                die "$(gettext "Cannot save the current worktree state")"
 
-               git diff-tree -p HEAD $w_tree "$TMP-patch" &&
+               git diff-tree -p HEAD $w_tree -- >"$TMP-patch" &&
                test -s "$TMP-patch" ||
                die "$(gettext "No changes selected")"
 
@@ -491,7 +492,7 @@ drop_stash () {
                die "$(eval_gettext "\${REV}: Could not drop stash entry")"
 
        # clear_stash if we just dropped the last stash entry
-       git rev-parse --verify "$ref_stash@{0}" > /dev/null 2>&1 || clear_stash
+       git rev-parse --verify "$ref_stash@{0}" >/dev/null 2>&1 || clear_stash
 }
 
 apply_to_branch () {
diff --git a/git.c b/git.c
index fb9029cbf17bdad1d52ea6eae0c8f4ddf6a13979..380561663061f011189de883a865fbd59a922190 100644 (file)
--- a/git.c
+++ b/git.c
@@ -495,7 +495,7 @@ static void execv_dashed_external(const char **argv)
         * if we fail because the command is not found, it is
         * OK to return. Otherwise, we just pass along the status code.
         */
-       status = run_command_v_opt(argv, RUN_SILENT_EXEC_FAILURE);
+       status = run_command_v_opt(argv, RUN_SILENT_EXEC_FAILURE | RUN_CLEAN_ON_EXIT);
        if (status >= 0 || errno != ENOENT)
                exit(status);
 
index fc41b07bcb4a7493d255150b3a3fa75ca7412352..9cf7e714a9a00d0d7d153d4336614e70b565d3fa 100755 (executable)
@@ -1123,8 +1123,10 @@ sub dispatch {
        if (!defined $action) {
                if (defined $hash) {
                        $action = git_get_type($hash);
+                       $action or die_error(404, "Object does not exist");
                } elsif (defined $hash_base && defined $file_name) {
                        $action = git_get_type("$hash_base:$file_name");
+                       $action or die_error(404, "File or directory does not exist");
                } elsif (defined $project) {
                        $action = 'summary';
                } else {
@@ -2391,7 +2393,7 @@ sub get_feed_info {
        return unless (defined $project);
        # some views should link to OPML, or to generic project feed,
        # or don't have specific feed yet (so they should use generic)
-       return if ($action =~ /^(?:tags|heads|forks|tag|search)$/x);
+       return if (!$action || $action =~ /^(?:tags|heads|forks|tag|search)$/x);
 
        my $branch;
        # branches refs uses 'refs/heads/' prefix (fullname) to differentiate
@@ -2836,8 +2838,8 @@ sub git_get_projects_list {
                my $dir = $projects_list;
                # remove the trailing "/"
                $dir =~ s!/+$!!;
-               my $pfxlen = length("$projects_list");
-               my $pfxdepth = ($projects_list =~ tr!/!!);
+               my $pfxlen = length("$dir");
+               my $pfxdepth = ($dir =~ tr!/!!);
                # when filtering, search only given subdirectory
                if ($filter) {
                        $dir .= "/$filter";
@@ -5836,7 +5838,7 @@ sub git_search_files {
        my %co = @_;
 
        local $/ = "\n";
-       open my $fd, "-|", git_cmd(), 'grep', '-n',
+       open my $fd, "-|", git_cmd(), 'grep', '-n', '-z',
                $search_use_regexp ? ('-E', '-i') : '-F',
                $searchtext, $co{'tree'}
                        or die_error(500, "Open git-grep failed");
@@ -5852,13 +5854,14 @@ sub git_search_files {
        my $lastfile = '';
        while (my $line = <$fd>) {
                chomp $line;
-               my ($file, $lno, $ltext, $binary);
+               my ($file, $file_href, $lno, $ltext, $binary);
                last if ($matches++ > 1000);
                if ($line =~ /^Binary file (.+) matches$/) {
                        $file = $1;
                        $binary = 1;
                } else {
-                       (undef, $file, $lno, $ltext) = split(/:/, $line, 4);
+                       ($file, $lno, $ltext) = split(/\0/, $line, 3);
+                       $file =~ s/^$co{'tree'}://;
                }
                if ($file ne $lastfile) {
                        $lastfile and print "</td></tr>\n";
@@ -5867,10 +5870,10 @@ sub git_search_files {
                        } else {
                                print "<tr class=\"light\">\n";
                        }
+                       $file_href = href(action=>"blob", hash_base=>$co{'id'},
+                                         file_name=>$file);
                        print "<td class=\"list\">".
-                               $cgi->a({-href => href(action=>"blob", hash=>$co{'hash'},
-                                                      file_name=>"$file"),
-                                       -class => "list"}, esc_path($file));
+                               $cgi->a({-href => $file_href, -class => "list"}, esc_path($file));
                        print "</td><td>\n";
                        $lastfile = $file;
                }
@@ -5888,10 +5891,9 @@ sub git_search_files {
                                $ltext = esc_html($ltext, -nbsp=>1);
                        }
                        print "<div class=\"pre\">" .
-                               $cgi->a({-href => href(action=>"blob", hash=>$co{'hash'},
-                                                      file_name=>"$file").'#l'.$lno,
-                                       -class => "linenr"}, sprintf('%4i', $lno))
-                               . ' ' .  $ltext . "</div>\n";
+                               $cgi->a({-href => $file_href.'#l'.$lno,
+                                       -class => "linenr"}, sprintf('%4i', $lno)) .
+                               ' ' .  $ltext . "</div>\n";
                }
        }
        if ($lastfile) {
index ff232c8c5d47f42481e3de365442e46967432022..09ab64aa24a1239cdb1ed5259c0b975a89594e9c 100644 (file)
@@ -5,6 +5,7 @@
 #include "sigchain.h"
 
 static char *configured_signing_key;
+static const char *gpg_program = "gpg";
 
 void set_signing_key(const char *key)
 {
@@ -15,9 +16,12 @@ void set_signing_key(const char *key)
 int git_gpg_config(const char *var, const char *value, void *cb)
 {
        if (!strcmp(var, "user.signingkey")) {
+               set_signing_key(value);
+       }
+       if (!strcmp(var, "gpg.program")) {
                if (!value)
                        return config_error_nonbool(var);
-               set_signing_key(value);
+               gpg_program = xstrdup(value);
        }
        return 0;
 }
@@ -46,7 +50,7 @@ int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *sig
        gpg.argv = args;
        gpg.in = -1;
        gpg.out = -1;
-       args[0] = "gpg";
+       args[0] = gpg_program;
        args[1] = "-bsau";
        args[2] = signing_key;
        args[3] = NULL;
@@ -91,20 +95,18 @@ int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *sig
 
 /*
  * Run "gpg" to see if the payload matches the detached signature.
- * gpg_output_to tells where the output from "gpg" should go:
- *   < 0: /dev/null
- *   = 0: standard error of the calling process
- *   > 0: the specified file descriptor
+ * gpg_output, when set, receives the diagnostic output from GPG.
  */
 int verify_signed_buffer(const char *payload, size_t payload_size,
                         const char *signature, size_t signature_size,
                         struct strbuf *gpg_output)
 {
        struct child_process gpg;
-       const char *args_gpg[] = {"gpg", "--verify", "FILE", "-", NULL};
+       const char *args_gpg[] = {NULL, "--verify", "FILE", "-", NULL};
        char path[PATH_MAX];
        int fd, ret;
 
+       args_gpg[0] = gpg_program;
        fd = git_mkstemp(path, PATH_MAX, ".git_vtag_tmpXXXXXX");
        if (fd < 0)
                return error("could not create temporary file '%s': %s",
index 319bd31e2bd67f5ee38d6b887da2ca1b8e9ac742..cea8756866e5ab86f09f3fadb0fe33e92b04b4bd 100644 (file)
@@ -8,6 +8,7 @@
 #include "refs.h"
 #include "string-list.h"
 #include "color.h"
+#include "gpg-interface.h"
 
 struct decoration name_decoration = { "object names" };
 
@@ -119,9 +120,9 @@ static int add_ref_decoration(const char *refname, const unsigned char *sha1, in
                type = DECORATION_REF_REMOTE;
        else if (!prefixcmp(refname, "refs/tags/"))
                type = DECORATION_REF_TAG;
-       else if (!prefixcmp(refname, "refs/stash"))
+       else if (!strcmp(refname, "refs/stash"))
                type = DECORATION_REF_STASH;
-       else if (!prefixcmp(refname, "HEAD"))
+       else if (!strcmp(refname, "HEAD"))
                type = DECORATION_REF_HEAD;
 
        if (!cb_data || *(int *)cb_data == DECORATE_SHORT_REFS)
@@ -403,6 +404,129 @@ void log_write_email_headers(struct rev_info *opt, struct commit *commit,
        *extra_headers_p = extra_headers;
 }
 
+static void show_sig_lines(struct rev_info *opt, int status, const char *bol)
+{
+       const char *color, *reset, *eol;
+
+       color = diff_get_color_opt(&opt->diffopt,
+                                  status ? DIFF_WHITESPACE : DIFF_FRAGINFO);
+       reset = diff_get_color_opt(&opt->diffopt, DIFF_RESET);
+       while (*bol) {
+               eol = strchrnul(bol, '\n');
+               printf("%s%.*s%s%s", color, (int)(eol - bol), bol, reset,
+                      *eol ? "\n" : "");
+               bol = (*eol) ? (eol + 1) : eol;
+       }
+}
+
+static void show_signature(struct rev_info *opt, struct commit *commit)
+{
+       struct strbuf payload = STRBUF_INIT;
+       struct strbuf signature = STRBUF_INIT;
+       struct strbuf gpg_output = STRBUF_INIT;
+       int status;
+
+       if (parse_signed_commit(commit->object.sha1, &payload, &signature) <= 0)
+               goto out;
+
+       status = verify_signed_buffer(payload.buf, payload.len,
+                                     signature.buf, signature.len,
+                                     &gpg_output);
+       if (status && !gpg_output.len)
+               strbuf_addstr(&gpg_output, "No signature\n");
+
+       show_sig_lines(opt, status, gpg_output.buf);
+
+ out:
+       strbuf_release(&gpg_output);
+       strbuf_release(&payload);
+       strbuf_release(&signature);
+}
+
+static int which_parent(const unsigned char *sha1, const struct commit *commit)
+{
+       int nth;
+       const struct commit_list *parent;
+
+       for (nth = 0, parent = commit->parents; parent; parent = parent->next) {
+               if (!hashcmp(parent->item->object.sha1, sha1))
+                       return nth;
+               nth++;
+       }
+       return -1;
+}
+
+static int is_common_merge(const struct commit *commit)
+{
+       return (commit->parents
+               && commit->parents->next
+               && !commit->parents->next->next);
+}
+
+static void show_one_mergetag(struct rev_info *opt,
+                             struct commit_extra_header *extra,
+                             struct commit *commit)
+{
+       unsigned char sha1[20];
+       struct tag *tag;
+       struct strbuf verify_message;
+       int status, nth;
+       size_t payload_size, gpg_message_offset;
+
+       hash_sha1_file(extra->value, extra->len, typename(OBJ_TAG), sha1);
+       tag = lookup_tag(sha1);
+       if (!tag)
+               return; /* error message already given */
+
+       strbuf_init(&verify_message, 256);
+       if (parse_tag_buffer(tag, extra->value, extra->len))
+               strbuf_addstr(&verify_message, "malformed mergetag\n");
+       else if (is_common_merge(commit) &&
+                !hashcmp(tag->tagged->sha1,
+                         commit->parents->next->item->object.sha1))
+               strbuf_addf(&verify_message,
+                           "merged tag '%s'\n", tag->tag);
+       else if ((nth = which_parent(tag->tagged->sha1, commit)) < 0)
+               strbuf_addf(&verify_message, "tag %s names a non-parent %s\n",
+                                   tag->tag, tag->tagged->sha1);
+       else
+               strbuf_addf(&verify_message,
+                           "parent #%d, tagged '%s'\n", nth + 1, tag->tag);
+       gpg_message_offset = verify_message.len;
+
+       payload_size = parse_signature(extra->value, extra->len);
+       if ((extra->len <= payload_size) ||
+           (verify_signed_buffer(extra->value, payload_size,
+                                 extra->value + payload_size,
+                                 extra->len - payload_size,
+                                 &verify_message) &&
+            verify_message.len <= gpg_message_offset)) {
+               strbuf_addstr(&verify_message, "No signature\n");
+               status = -1;
+       }
+       else if (strstr(verify_message.buf + gpg_message_offset,
+                       ": Good signature from "))
+               status = 0;
+       else
+               status = -1;
+
+       show_sig_lines(opt, status, verify_message.buf);
+       strbuf_release(&verify_message);
+}
+
+static void show_mergetag(struct rev_info *opt, struct commit *commit)
+{
+       struct commit_extra_header *extra, *to_free;
+
+       to_free = read_commit_extra_headers(commit, NULL);
+       for (extra = to_free; extra; extra = extra->next) {
+               if (strcmp(extra->key, "mergetag"))
+                       continue; /* not a merge tag */
+               show_one_mergetag(opt, extra, commit);
+       }
+       free_commit_extra_headers(to_free);
+}
+
 void show_log(struct rev_info *opt)
 {
        struct strbuf msgbuf = STRBUF_INIT;
@@ -514,6 +638,11 @@ void show_log(struct rev_info *opt)
                }
        }
 
+       if (opt->show_signature) {
+               show_signature(opt, commit);
+               show_mergetag(opt, commit);
+       }
+
        if (!commit->buffer)
                return;
 
index bea013eeae70f360e12af01345058b1b217848c4..eabe4a0d9bf44050dba43cbd821642cdfd3fb40b 100644 (file)
@@ -59,7 +59,7 @@ int notes_cache_write(struct notes_cache *c)
                return -1;
        strbuf_attach(&msg, c->validity,
                      strlen(c->validity), strlen(c->validity) + 1);
-       if (commit_tree(&msg, tree_sha1, NULL, commit_sha1, NULL) < 0)
+       if (commit_tree(&msg, tree_sha1, NULL, commit_sha1, NULL, NULL) < 0)
                return -1;
        if (update_ref("update notes cache", c->tree.ref, commit_sha1, NULL,
                       0, QUIET_ON_ERR) < 0)
index 0d38a1b17d8399c7acf6355d598f0a50d0ec1d3b..fb0832f97d218ecd1812361721800d6288935c06 100644 (file)
@@ -551,7 +551,7 @@ void create_notes_commit(struct notes_tree *t, struct commit_list *parents,
                /* else: t->ref points to nothing, assume root/orphan commit */
        }
 
-       if (commit_tree(msg, tree_sha1, parents, result_sha1, NULL))
+       if (commit_tree(msg, tree_sha1, parents, result_sha1, NULL, NULL))
                die("Failed to commit notes tree to database");
 }
 
index 23bbd00e3e542e844fce08156c5c1073e6437d43..f09a05422854c919cd31c542b82b057624275959 100644 (file)
@@ -143,7 +143,6 @@ int pack_refs(unsigned int flags)
        packed.fd = -1;
        if (commit_lock_file(&packed) < 0)
                die_errno("unable to overwrite old ref-pack file");
-       if (cbdata.flags & PACK_REFS_PRUNE)
-               prune_refs(cbdata.ref_to_prune);
+       prune_refs(cbdata.ref_to_prune);
        return 0;
 }
index 1580299d4037ada9e1f0e3c11c6c5578f44f52dd..8688b8f2d45a493aa8b51b29f7d46b2abff7f30e 100644 (file)
--- a/pretty.c
+++ b/pretty.c
@@ -9,6 +9,7 @@
 #include "notes.h"
 #include "color.h"
 #include "reflog-walk.h"
+#include "gpg-interface.h"
 
 static char *user_format;
 static struct cmt_fmt_map {
@@ -640,6 +641,12 @@ struct format_commit_context {
        const struct pretty_print_context *pretty_ctx;
        unsigned commit_header_parsed:1;
        unsigned commit_message_parsed:1;
+       unsigned commit_signature_parsed:1;
+       struct {
+               char *gpg_output;
+               char good_bad;
+               char *signer;
+       } signature;
        char *message;
        size_t width, indent1, indent2;
 
@@ -822,6 +829,59 @@ static void rewrap_message_tail(struct strbuf *sb,
        c->indent2 = new_indent2;
 }
 
+static struct {
+       char result;
+       const char *check;
+} signature_check[] = {
+       { 'G', ": Good signature from " },
+       { 'B', ": BAD signature from " },
+};
+
+static void parse_signature_lines(struct format_commit_context *ctx)
+{
+       const char *buf = ctx->signature.gpg_output;
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(signature_check); i++) {
+               const char *found = strstr(buf, signature_check[i].check);
+               const char *next;
+               if (!found)
+                       continue;
+               ctx->signature.good_bad = signature_check[i].result;
+               found += strlen(signature_check[i].check);
+               next = strchrnul(found, '\n');
+               ctx->signature.signer = xmemdupz(found, next - found);
+               break;
+       }
+}
+
+static void parse_commit_signature(struct format_commit_context *ctx)
+{
+       struct strbuf payload = STRBUF_INIT;
+       struct strbuf signature = STRBUF_INIT;
+       struct strbuf gpg_output = STRBUF_INIT;
+       int status;
+
+       ctx->commit_signature_parsed = 1;
+
+       if (parse_signed_commit(ctx->commit->object.sha1,
+                               &payload, &signature) <= 0)
+               goto out;
+       status = verify_signed_buffer(payload.buf, payload.len,
+                                     signature.buf, signature.len,
+                                     &gpg_output);
+       if (status && !gpg_output.len)
+               goto out;
+       ctx->signature.gpg_output = strbuf_detach(&gpg_output, NULL);
+       parse_signature_lines(ctx);
+
+ out:
+       strbuf_release(&gpg_output);
+       strbuf_release(&payload);
+       strbuf_release(&signature);
+}
+
+
 static int format_reflog_person(struct strbuf *sb,
                                char part,
                                struct reflog_walk_info *log,
@@ -999,6 +1059,30 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
                return 0;
        }
 
+       if (placeholder[0] == 'G') {
+               if (!c->commit_signature_parsed)
+                       parse_commit_signature(c);
+               switch (placeholder[1]) {
+               case 'G':
+                       if (c->signature.gpg_output)
+                               strbuf_addstr(sb, c->signature.gpg_output);
+                       break;
+               case '?':
+                       switch (c->signature.good_bad) {
+                       case 'G':
+                       case 'B':
+                               strbuf_addch(sb, c->signature.good_bad);
+                       }
+                       break;
+               case 'S':
+                       if (c->signature.signer)
+                               strbuf_addstr(sb, c->signature.signer);
+                       break;
+               }
+               return 2;
+       }
+
+
        /* For the rest we have to parse the commit header. */
        if (!c->commit_header_parsed)
                parse_commit_header(c);
@@ -1141,6 +1225,8 @@ void format_commit_message(const struct commit *commit,
 
        if (context.message != commit->buffer)
                free(context.message);
+       free(context.signature.gpg_output);
+       free(context.signature.signer);
 }
 
 static void pp_header(const struct pretty_print_context *pp,
diff --git a/refs.c b/refs.c
index 6f436f1cb05e62c6afda086f5e410a268cf8bb52..b8843bb4769a2c5e3496d962f8d6b85a483fa92e 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -17,6 +17,15 @@ struct ref_entry {
 
 struct ref_array {
        int nr, alloc;
+
+       /*
+        * Entries with index 0 <= i < sorted are sorted by name.  New
+        * entries are appended to the list unsorted, and are sorted
+        * only when required; thus we avoid the need to sort the list
+        * after the addition of every reference.
+        */
+       int sorted;
+
        struct ref_entry **refs;
 };
 
@@ -105,12 +114,18 @@ static int is_dup_ref(const struct ref_entry *ref1, const struct ref_entry *ref2
        }
 }
 
+/*
+ * Sort the entries in array (if they are not already sorted).
+ */
 static void sort_ref_array(struct ref_array *array)
 {
        int i, j;
 
-       /* Nothing to sort unless there are at least two entries */
-       if (array->nr < 2)
+       /*
+        * This check also prevents passing a zero-length array to qsort(),
+        * which is a problem on some platforms.
+        */
+       if (array->sorted == array->nr)
                return;
 
        qsort(array->refs, array->nr, sizeof(*array->refs), ref_entry_cmp);
@@ -124,7 +139,7 @@ static void sort_ref_array(struct ref_array *array)
                }
                array->refs[++i] = array->refs[j];
        }
-       array->nr = i + 1;
+       array->sorted = array->nr = i + 1;
 }
 
 static struct ref_entry *search_ref_array(struct ref_array *array, const char *refname)
@@ -137,7 +152,7 @@ static struct ref_entry *search_ref_array(struct ref_array *array, const char *r
 
        if (!array->nr)
                return NULL;
-
+       sort_ref_array(array);
        len = strlen(refname) + 1;
        e = xmalloc(sizeof(struct ref_entry) + len);
        memcpy(e->name, refname, len);
@@ -168,6 +183,10 @@ static struct ref_cache {
 
 static struct ref_entry *current_ref;
 
+/*
+ * Never call sort_ref_array() on the extra_refs, because it is
+ * allowed to contain entries with duplicate names.
+ */
 static struct ref_array extra_refs;
 
 static void clear_ref_array(struct ref_array *array)
@@ -176,7 +195,7 @@ static void clear_ref_array(struct ref_array *array)
        for (i = 0; i < array->nr; i++)
                free(array->refs[i]);
        free(array->refs);
-       array->nr = array->alloc = 0;
+       array->sorted = array->nr = array->alloc = 0;
        array->refs = NULL;
 }
 
@@ -268,7 +287,6 @@ static void read_packed_refs(FILE *f, struct ref_array *array)
                    !get_sha1_hex(refline + 1, sha1))
                        hashcpy(last->peeled, sha1);
        }
-       sort_ref_array(array);
 }
 
 void add_extra_ref(const char *refname, const unsigned char *sha1, int flag)
@@ -301,6 +319,12 @@ static struct ref_array *get_packed_refs(struct ref_cache *refs)
        return &refs->packed;
 }
 
+void add_packed_ref(const char *refname, const unsigned char *sha1)
+{
+       add_ref(get_packed_refs(get_ref_cache(NULL)),
+                       create_ref_entry(refname, sha1, REF_ISPACKED, 1));
+}
+
 static void get_ref_dir(struct ref_cache *refs, const char *base,
                        struct ref_array *array)
 {
@@ -404,7 +428,6 @@ static struct ref_array *get_loose_refs(struct ref_cache *refs)
 {
        if (!refs->did_loose) {
                get_ref_dir(refs, "refs", &refs->loose);
-               sort_ref_array(&refs->loose);
                refs->did_loose = 1;
        }
        return &refs->loose;
@@ -720,6 +743,8 @@ static int do_for_each_ref(const char *submodule, const char *base, each_ref_fn
        for (i = 0; i < extra->nr; i++)
                retval = do_one_ref(base, fn, trim, flags, cb_data, extra->refs[i]);
 
+       sort_ref_array(packed);
+       sort_ref_array(loose);
        while (p < packed->nr && l < loose->nr) {
                struct ref_entry *entry;
                int cmp = strcmp(packed->refs[p]->name, loose->refs[l]->name);
diff --git a/refs.h b/refs.h
index d4982915c58c7c17229e4a6621d02337e2550e13..00ba1e2813b2c2fff74300e3b2036a04f9e28065 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -50,6 +50,12 @@ extern int for_each_rawref(each_ref_fn, void *);
 
 extern void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname);
 
+/*
+ * Add a reference to the in-memory packed reference cache.  To actually
+ * write the reference to the packed-refs file, call pack_refs().
+ */
+extern void add_packed_ref(const char *refname, const unsigned char *sha1);
+
 /*
  * Extra refs will be listed by for_each_ref() before any actual refs
  * for the duration of this process or until clear_extra_refs() is
index 48c20b86f3cfc6c189972718046a6890aaefacf3..d159fe7f3433ccf6e8c8908961736951e42b9c35 100644 (file)
@@ -770,7 +770,9 @@ static int push_git(struct discovery *heads, int nr_spec, char **specs)
                argv[argc++] = "--thin";
        if (options.dry_run)
                argv[argc++] = "--dry-run";
-       if (options.verbosity > 1)
+       if (options.verbosity == 0)
+               argv[argc++] = "--quiet";
+       else if (options.verbosity > 1)
                argv[argc++] = "--verbose";
        argv[argc++] = url;
        for (i = 0; i < nr_spec; i++)
@@ -805,7 +807,7 @@ static int push(int nr_spec, char **specs)
 static void parse_push(struct strbuf *buf)
 {
        char **specs = NULL;
-       int alloc_spec = 0, nr_spec = 0, i;
+       int alloc_spec = 0, nr_spec = 0, i, ret;
 
        do {
                if (!prefixcmp(buf->buf, "push ")) {
@@ -822,12 +824,13 @@ static void parse_push(struct strbuf *buf)
                        break;
        } while (1);
 
-       if (push(nr_spec, specs))
-               exit(128); /* error already reported */
-
+       ret = push(nr_spec, specs);
        printf("\n");
        fflush(stdout);
 
+       if (ret)
+               exit(128); /* error already reported */
+
  free_specs:
        for (i = 0; i < nr_spec; i++)
                free(specs[i]);
index 8764dde381111cfc9c8ea7eb3856223de9786ec9..eb0be4206d2772e4d8eb11bfd164725529f915e7 100644 (file)
@@ -416,7 +416,7 @@ static int rev_same_tree_as_empty(struct rev_info *revs, struct commit *commit)
 static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
 {
        struct commit_list **pp, *parent;
-       int tree_changed = 0, tree_same = 0;
+       int tree_changed = 0, tree_same = 0, nth_parent = 0;
 
        /*
         * If we don't do pruning, everything is interesting
@@ -444,6 +444,14 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
        while ((parent = *pp) != NULL) {
                struct commit *p = parent->item;
 
+               /*
+                * Do not compare with later parents when we care only about
+                * the first parent chain, in order to avoid derailing the
+                * traversal to follow a side branch that brought everything
+                * in the path we are limited to by the pathspec.
+                */
+               if (revs->first_parent_only && nth_parent++)
+                       break;
                if (parse_commit(p) < 0)
                        die("cannot simplify commit %s (because of %s)",
                            sha1_to_hex(commit->object.sha1),
@@ -1469,6 +1477,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
                revs->show_notes = 1;
                revs->show_notes_given = 1;
                revs->notes_opt.use_default_notes = 1;
+       } else if (!strcmp(arg, "--show-signature")) {
+               revs->show_signature = 1;
        } else if (!prefixcmp(arg, "--show-notes=") ||
                   !prefixcmp(arg, "--notes=")) {
                struct strbuf buf = STRBUF_INIT;
index 6aa53d1aa708918e4bbbebb042e0bf75c1629b05..b8e9223954a5d66e01bd1eb342a936495aa67ad1 100644 (file)
@@ -110,6 +110,7 @@ struct rev_info {
                        show_merge:1,
                        show_notes:1,
                        show_notes_given:1,
+                       show_signature:1,
                        pretty_given:1,
                        abbrev_commit:1,
                        abbrev_commit_given:1,
index 1c5104388429e50722769358d5d89948c55bb3aa..1db8abf9843516576f30f8105bbfdd66487db6e1 100644 (file)
@@ -1,8 +1,66 @@
 #include "cache.h"
 #include "run-command.h"
 #include "exec_cmd.h"
+#include "sigchain.h"
 #include "argv-array.h"
 
+struct child_to_clean {
+       pid_t pid;
+       struct child_to_clean *next;
+};
+static struct child_to_clean *children_to_clean;
+static int installed_child_cleanup_handler;
+
+static void cleanup_children(int sig)
+{
+       while (children_to_clean) {
+               struct child_to_clean *p = children_to_clean;
+               children_to_clean = p->next;
+               kill(p->pid, sig);
+               free(p);
+       }
+}
+
+static void cleanup_children_on_signal(int sig)
+{
+       cleanup_children(sig);
+       sigchain_pop(sig);
+       raise(sig);
+}
+
+static void cleanup_children_on_exit(void)
+{
+       cleanup_children(SIGTERM);
+}
+
+static void mark_child_for_cleanup(pid_t pid)
+{
+       struct child_to_clean *p = xmalloc(sizeof(*p));
+       p->pid = pid;
+       p->next = children_to_clean;
+       children_to_clean = p;
+
+       if (!installed_child_cleanup_handler) {
+               atexit(cleanup_children_on_exit);
+               sigchain_push_common(cleanup_children_on_signal);
+               installed_child_cleanup_handler = 1;
+       }
+}
+
+static void clear_child_for_cleanup(pid_t pid)
+{
+       struct child_to_clean **last, *p;
+
+       last = &children_to_clean;
+       for (p = children_to_clean; p; p = p->next) {
+               if (p->pid == pid) {
+                       *last = p->next;
+                       free(p);
+                       return;
+               }
+       }
+}
+
 static inline void close_pair(int fd[2])
 {
        close(fd[0]);
@@ -130,6 +188,9 @@ static int wait_or_whine(pid_t pid, const char *argv0, int silent_exec_failure)
        } else {
                error("waitpid is confused (%s)", argv0);
        }
+
+       clear_child_for_cleanup(pid);
+
        errno = failed_errno;
        return code;
 }
@@ -292,6 +353,8 @@ fail_pipe:
        if (cmd->pid < 0)
                error("cannot fork() for %s: %s", cmd->argv[0],
                        strerror(failed_errno = errno));
+       else if (cmd->clean_on_exit)
+               mark_child_for_cleanup(cmd->pid);
 
        /*
         * Wait for child's execvp. If the execvp succeeds (or if fork()
@@ -312,6 +375,7 @@ fail_pipe:
                cmd->pid = -1;
        }
        close(notify_pipe[0]);
+
 }
 #else
 {
@@ -356,6 +420,8 @@ fail_pipe:
        failed_errno = errno;
        if (cmd->pid < 0 && (!cmd->silent_exec_failure || errno != ENOENT))
                error("cannot spawn %s: %s", cmd->argv[0], strerror(errno));
+       if (cmd->clean_on_exit && cmd->pid >= 0)
+               mark_child_for_cleanup(cmd->pid);
 
        if (cmd->env)
                free_environ(env);
@@ -431,6 +497,7 @@ static void prepare_run_command_v_opt(struct child_process *cmd,
        cmd->stdout_to_stderr = opt & RUN_COMMAND_STDOUT_TO_STDERR ? 1 : 0;
        cmd->silent_exec_failure = opt & RUN_SILENT_EXEC_FAILURE ? 1 : 0;
        cmd->use_shell = opt & RUN_USING_SHELL ? 1 : 0;
+       cmd->clean_on_exit = opt & RUN_CLEAN_ON_EXIT ? 1 : 0;
 }
 
 int run_command_v_opt(const char **argv, int opt)
@@ -540,6 +607,8 @@ int start_async(struct async *async)
                exit(!!async->proc(proc_in, proc_out, async->data));
        }
 
+       mark_child_for_cleanup(async->pid);
+
        if (need_in)
                close(fdin[0]);
        else if (async->in)
index 56491b9f2344541c02bd0da2928a535f11193bd8..44f7d2bd42ddcf2d65722b7e54bea69645d167a2 100644 (file)
@@ -38,6 +38,7 @@ struct child_process {
        unsigned silent_exec_failure:1;
        unsigned stdout_to_stderr:1;
        unsigned use_shell:1;
+       unsigned clean_on_exit:1;
        void (*preexec_cb)(void);
 };
 
@@ -52,6 +53,7 @@ extern int run_hook(const char *index_file, const char *name, ...);
 #define RUN_COMMAND_STDOUT_TO_STDERR 4
 #define RUN_SILENT_EXEC_FAILURE 8
 #define RUN_USING_SHELL 16
+#define RUN_CLEAN_ON_EXIT 32
 int run_command_v_opt(const char **argv, int opt);
 
 /*
index c85abaffb3b8c2142e87f6e0525fa67c6b62c1a1..c09c582c163dd547ed5b1d7a65e13e3345bb7eca 100644 (file)
--- a/t/README
+++ b/t/README
@@ -548,6 +548,19 @@ library for your script to use.
                ...
        '
 
+ - test_pause
+
+       This command is useful for writing and debugging tests and must be
+       removed before submitting. It halts the execution of the test and
+       spawns a shell in the trash directory. Exit the shell to continue
+       the test. Example:
+
+       test_expect_success 'test' '
+               git do-something >actual &&
+               test_pause &&
+               test_cmp expected actual
+       '
+
 Prerequisites
 -------------
 
diff --git a/t/lib-git-daemon.sh b/t/lib-git-daemon.sh
new file mode 100644 (file)
index 0000000..ef2d01f
--- /dev/null
@@ -0,0 +1,69 @@
+#!/bin/sh
+
+if test -z "$GIT_TEST_GIT_DAEMON"
+then
+       skip_all="git-daemon testing disabled (define GIT_TEST_GIT_DAEMON to enable)"
+       test_done
+fi
+
+LIB_GIT_DAEMON_PORT=${LIB_GIT_DAEMON_PORT-'8121'}
+
+GIT_DAEMON_PID=
+GIT_DAEMON_DOCUMENT_ROOT_PATH="$PWD"/repo
+GIT_DAEMON_URL=git://127.0.0.1:$LIB_GIT_DAEMON_PORT
+
+start_git_daemon() {
+       if test -n "$GIT_DAEMON_PID"
+       then
+               error "start_git_daemon already called"
+       fi
+
+       mkdir -p "$GIT_DAEMON_DOCUMENT_ROOT_PATH"
+
+       trap 'code=$?; stop_git_daemon; (exit $code); die' EXIT
+
+       say >&3 "Starting git daemon ..."
+       mkfifo git_daemon_output
+       git daemon --listen=127.0.0.1 --port="$LIB_GIT_DAEMON_PORT" \
+               --reuseaddr --verbose \
+               --base-path="$GIT_DAEMON_DOCUMENT_ROOT_PATH" \
+               "$@" "$GIT_DAEMON_DOCUMENT_ROOT_PATH" \
+               >&3 2>git_daemon_output &
+       GIT_DAEMON_PID=$!
+       {
+               read line
+               echo >&4 "$line"
+               cat >&4 &
+
+               # Check expected output
+               if test x"$(expr "$line" : "\[[0-9]*\] \(.*\)")" != x"Ready to rumble"
+               then
+                       kill "$GIT_DAEMON_PID"
+                       wait "$GIT_DAEMON_PID"
+                       trap 'die' EXIT
+                       error "git daemon failed to start"
+               fi
+       } <git_daemon_output
+}
+
+stop_git_daemon() {
+       if test -z "$GIT_DAEMON_PID"
+       then
+               return
+       fi
+
+       trap 'die' EXIT
+
+       # kill git-daemon child of git
+       say >&3 "Stopping git daemon ..."
+       kill "$GIT_DAEMON_PID"
+       wait "$GIT_DAEMON_PID" >&3 2>&4
+       ret=$?
+       # expect exit with status 143 = 128+15 for signal TERM=15
+       if test $ret -ne 143
+       then
+               error "git daemon exited with status: $ret"
+       fi
+       GIT_DAEMON_PID=
+       rm -f git_daemon_output
+}
index dbb2623d930e433111f7e125749f5f1071e9ab3c..51f3045ba4bde549b06ea669b1877f0e2db7c401 100755 (executable)
@@ -159,6 +159,16 @@ test_expect_success 'relative paths' '
        (cd b && attr_check ../a/b/g a/b/g)
 '
 
+test_expect_success 'prefixes are not confused with leading directories' '
+       attr_check a_plus/g unspecified &&
+       cat >expect <<-\EOF &&
+       a/g: test: a/g
+       a_plus/g: test: unspecified
+       EOF
+       git check-attr test a/g a_plus/g >actual &&
+       test_cmp expect actual
+'
+
 test_expect_success 'core.attributesfile' '
        attr_check global unspecified &&
        git config core.attributesfile "$HOME/global-gitattributes" &&
index 58a329961e5c269e1b035558db890c9e30375147..25435290a71ae8031762a3e63537fd3a23a8fc90 100755 (executable)
@@ -41,7 +41,7 @@ test_expect_success 'cannot commit with i-t-a entry' '
        echo frotz >nitfol &&
        git add rezrov &&
        git add -N nitfol &&
-       test_must_fail git commit
+       test_must_fail git commit -m initial
 '
 
 test_expect_success 'can commit with an unrelated i-t-a entry in index' '
index fcdb18217a777f5dbb77b0071af7159c6985656d..dbe2ac179dead1ea825f62b7c26e3ceb6c0d638d 100755 (executable)
@@ -601,4 +601,28 @@ test_expect_success 'stash apply shows status same as git status (relative to cu
        test_cmp expect actual
 '
 
+cat > expect << EOF
+diff --git a/HEAD b/HEAD
+new file mode 100644
+index 0000000..fe0cbee
+--- /dev/null
++++ b/HEAD
+@@ -0,0 +1 @@
++file-not-a-ref
+EOF
+
+test_expect_success 'stash where working directory contains "HEAD" file' '
+       git stash clear &&
+       git reset --hard &&
+       echo file-not-a-ref > HEAD &&
+       git add HEAD &&
+       test_tick &&
+       git stash &&
+       git diff-files --quiet &&
+       git diff-index --cached --quiet HEAD &&
+       test "$(git rev-parse stash^)" = "$(git rev-parse HEAD)" &&
+       git diff stash^..stash > output &&
+       test_cmp output expect
+'
+
 test_done
index 781fd716815d90956575b042d72601e67834231c..70655c184886e35046f05e09ee093df48b59daa6 100755 (executable)
@@ -7,7 +7,8 @@ test_expect_success PERL 'setup' '
        mkdir dir &&
        echo parent > dir/foo &&
        echo dummy > bar &&
-       git add bar dir/foo &&
+       echo committed > HEAD &&
+       git add bar dir/foo HEAD &&
        git commit -m initial &&
        test_tick &&
        test_commit second dir/foo head &&
@@ -17,47 +18,57 @@ test_expect_success PERL 'setup' '
        save_head
 '
 
-# note: bar sorts before dir, so the first 'n' is always to skip 'bar'
+# note: order of files with unstaged changes: HEAD bar dir/foo
 
 test_expect_success PERL 'saying "n" does nothing' '
+       set_state HEAD HEADfile_work HEADfile_index &&
        set_state dir/foo work index &&
-       (echo n; echo n) | test_must_fail git stash save -p &&
-       verify_state dir/foo work index &&
-       verify_saved_state bar
+       (echo n; echo n; echo n) | test_must_fail git stash save -p &&
+       verify_state HEAD HEADfile_work HEADfile_index &&
+       verify_saved_state bar &&
+       verify_state dir/foo work index
 '
 
 test_expect_success PERL 'git stash -p' '
-       (echo n; echo y) | git stash save -p &&
-       verify_state dir/foo head index &&
+       (echo y; echo n; echo y) | git stash save -p &&
+       verify_state HEAD committed HEADfile_index &&
        verify_saved_state bar &&
+       verify_state dir/foo head index &&
        git reset --hard &&
        git stash apply &&
-       verify_state dir/foo work head &&
-       verify_state bar dummy dummy
+       verify_state HEAD HEADfile_work committed &&
+       verify_state bar dummy dummy &&
+       verify_state dir/foo work head
 '
 
 test_expect_success PERL 'git stash -p --no-keep-index' '
-       set_state dir/foo work index &&
+       set_state HEAD HEADfile_work HEADfile_index &&
        set_state bar bar_work bar_index &&
-       (echo n; echo y) | git stash save -p --no-keep-index &&
-       verify_state dir/foo head head &&
+       set_state dir/foo work index &&
+       (echo y; echo n; echo y) | git stash save -p --no-keep-index &&
+       verify_state HEAD committed committed &&
        verify_state bar bar_work dummy &&
+       verify_state dir/foo head head &&
        git reset --hard &&
        git stash apply --index &&
-       verify_state dir/foo work index &&
-       verify_state bar dummy bar_index
+       verify_state HEAD HEADfile_work HEADfile_index &&
+       verify_state bar dummy bar_index &&
+       verify_state dir/foo work index
 '
 
 test_expect_success PERL 'git stash --no-keep-index -p' '
-       set_state dir/foo work index &&
+       set_state HEAD HEADfile_work HEADfile_index &&
        set_state bar bar_work bar_index &&
-       (echo n; echo y) | git stash save --no-keep-index -p &&
+       set_state dir/foo work index &&
+       (echo y; echo n; echo y) | git stash save --no-keep-index -p &&
+       verify_state HEAD committed committed &&
        verify_state dir/foo head head &&
        verify_state bar bar_work dummy &&
        git reset --hard &&
        git stash apply --index &&
-       verify_state dir/foo work index &&
-       verify_state bar dummy bar_index
+       verify_state HEAD HEADfile_work HEADfile_index &&
+       verify_state bar dummy bar_index &&
+       verify_state dir/foo work index
 '
 
 test_expect_success PERL 'none of this moved HEAD' '
index ef44fb22601b03be78d8d5f5eaa858ca240ec4da..a5e7e6b2ba3138d06c400e965decf1389da3274a 100755 (executable)
@@ -17,6 +17,7 @@ test_expect_success 'stash save --include-untracked some dirty working directory
        echo 3 > file &&
        test_tick &&
        echo 1 > file2 &&
+       echo 1 > HEAD &&
        mkdir untracked &&
        echo untracked >untracked/untracked &&
        git stash --include-untracked &&
@@ -35,6 +36,13 @@ test_expect_success 'stash save --include-untracked cleaned the untracked files'
 '
 
 cat > expect.diff <<EOF
+diff --git a/HEAD b/HEAD
+new file mode 100644
+index 0000000..d00491f
+--- /dev/null
++++ b/HEAD
+@@ -0,0 +1 @@
++1
 diff --git a/file2 b/file2
 new file mode 100644
 index 0000000..d00491f
@@ -51,14 +59,16 @@ index 0000000..5a72eb2
 +untracked
 EOF
 cat > expect.lstree <<EOF
+HEAD
 file2
 untracked
 EOF
 
 test_expect_success 'stash save --include-untracked stashed the untracked files' '
-       test "!" -f file2 &&
-       test ! -e untracked &&
-       git diff HEAD stash^3 -- file2 untracked >actual &&
+       test_path_is_missing file2 &&
+       test_path_is_missing untracked &&
+       test_path_is_missing HEAD &&
+       git diff HEAD stash^3 -- HEAD file2 untracked >actual &&
        test_cmp expect.diff actual &&
        git ls-tree --name-only stash^3: >actual &&
        test_cmp expect.lstree actual
@@ -75,6 +85,7 @@ git clean --force --quiet
 
 cat > expect <<EOF
  M file
+?? HEAD
 ?? actual
 ?? expect
 ?? file2
@@ -116,10 +127,12 @@ test_expect_success 'stash save --include-untracked dirty index got stashed' '
 
 git reset > /dev/null
 
+# Must direct output somewhere where it won't be considered an untracked file
 test_expect_success 'stash save --include-untracked -q is quiet' '
        echo 1 > file5 &&
-       git stash save --include-untracked --quiet > output.out 2>&1 &&
-       test ! -s output.out
+       git stash save --include-untracked --quiet > .git/stash-output.out 2>&1 &&
+       test_line_count = 0 .git/stash-output.out &&
+       rm -f .git/stash-output.out
 '
 
 test_expect_success 'stash save --include-untracked removed files' '
@@ -133,7 +146,7 @@ rm -f expect
 
 test_expect_success 'stash save --include-untracked removed files got stashed' '
        git stash pop &&
-       test ! -f file
+       test_path_is_missing file
 '
 
 cat > .gitignore <<EOF
@@ -155,14 +168,14 @@ test_expect_success 'stash save --include-untracked respects .gitignore' '
 test_expect_success 'stash save -u can stash with only untracked files different' '
        echo 4 > file4 &&
        git stash -u &&
-       test "!" -f file4
+       test_path_is_missing file4
 '
 
 test_expect_success 'stash save --all does not respect .gitignore' '
        git stash -a &&
-       test "!" -f ignored &&
-       test "!" -e ignored.d &&
-       test "!" -f .gitignore
+       test_path_is_missing ignored &&
+       test_path_is_missing ignored.d &&
+       test_path_is_missing .gitignore
 '
 
 test_expect_success 'stash save --all is stash poppable' '
index fbc8cd8f05f4debeb30b935c1b1db86e94e49f0e..af5134b70c6b4898aa652907d58191d45aa30ac4 100755 (executable)
@@ -47,6 +47,14 @@ test_expect_success \
     'git diff-index --cached $tree -- path1/ >current &&
      compare_diff_raw current expected'
 
+cat >expected <<\EOF
+:100644 100644 766498d93a4b06057a8e49d23f4068f1170ff38f 0a41e115ab61be0328a19b29f18cdcb49338d516 M     path1/file1
+EOF
+test_expect_success \
+    '"*file1" should show path1/file1' \
+    'git diff-index --cached $tree -- "*file1" >current &&
+     compare_diff_raw current expected'
+
 cat >expected <<\EOF
 :100644 100644 766498d93a4b06057a8e49d23f4068f1170ff38f 0a41e115ab61be0328a19b29f18cdcb49338d516 M     file0
 EOF
index 6f1e5a2a15f0215e4ed7c19437f5d000edbbd255..5c2012111c28d338ad979fb7bcca871e744184fe 100755 (executable)
@@ -334,4 +334,18 @@ test_expect_success 'word-diff with diff.sbe' '
        word_diff --word-diff=plain
 '
 
+test_expect_success 'word-diff with no newline at EOF' '
+       cat >expect <<-\EOF &&
+       diff --git a/pre b/post
+       index 7bf316e..3dd0303 100644
+       --- a/pre
+       +++ b/post
+       @@ -1 +1 @@
+       a a [-a-]{+ab+} a a
+       EOF
+       printf "%s" "a a a a a" >pre &&
+       printf "%s" "a a ab a a" >post &&
+       word_diff --word-diff=plain
+'
+
 test_done
index d7d9ccc1c8c31ef3b2d4763532344d0637a18b5c..8807b602a51c58cd6fcc313441a0cc7339fc487c 100755 (executable)
@@ -237,7 +237,7 @@ test_expect_success 'am stays in branch' '
 
 test_expect_success 'am --signoff does not add Signed-off-by: line if already there' '
        git format-patch --stdout HEAD^ >patch3 &&
-       sed -e "/^Subject/ s,\[PATCH,Re: Re: Re: & 1/5 v2," patch3 >patch4 &&
+       sed -e "/^Subject/ s,\[PATCH,Re: Re: Re: & 1/5 v2] [foo," patch3 >patch4 &&
        rm -fr .git/rebase-apply &&
        git reset --hard &&
        git checkout HEAD^ &&
@@ -259,7 +259,17 @@ test_expect_success 'am --keep really keeps the subject' '
        git am --keep patch4 &&
        ! test -d .git/rebase-apply &&
        git cat-file commit HEAD >actual &&
-       grep "Re: Re: Re: \[PATCH 1/5 v2\] third" actual
+       grep "Re: Re: Re: \[PATCH 1/5 v2\] \[foo\] third" actual
+'
+
+test_expect_success 'am --keep-non-patch really keeps the non-patch part' '
+       rm -fr .git/rebase-apply &&
+       git reset --hard &&
+       git checkout HEAD^ &&
+       git am --keep-non-patch patch4 &&
+       ! test -d .git/rebase-apply &&
+       git cat-file commit HEAD >actual &&
+       grep "^\[foo\] third" actual
 '
 
 test_expect_success 'am -3 falls back to 3-way merge' '
index 9bf69e9a0f0bff632932929b5ba41f767baf3fca..7e85c71ad10186709b4210ca94d88953a270f123 100755 (executable)
@@ -114,8 +114,19 @@ pull_to_client 2nd "refs/heads/B" $((64*3))
 
 pull_to_client 3rd "refs/heads/A" $((1*3))
 
+test_expect_success 'single branch clone' '
+       git clone --single-branch "file://$(pwd)/." singlebranch
+'
+
+test_expect_success 'single branch object count' '
+       GIT_DIR=singlebranch/.git git count-objects -v |
+               grep "^in-pack:" > count.singlebranch &&
+       echo "in-pack: 198" >expected &&
+       test_cmp expected count.singlebranch
+'
+
 test_expect_success 'clone shallow' '
-       git clone --depth 2 "file://$(pwd)/." shallow
+       git clone --no-single-branch --depth 2 "file://$(pwd)/." shallow
 '
 
 test_expect_success 'clone shallow object count' '
@@ -248,4 +259,63 @@ test_expect_success 'clone shallow object count' '
        grep "^count: 52" count.shallow
 '
 
+test_expect_success 'clone shallow without --no-single-branch' '
+       git clone --depth 1 "file://$(pwd)/." shallow2
+'
+
+test_expect_success 'clone shallow object count' '
+       (
+               cd shallow2 &&
+               git count-objects -v
+       ) > count.shallow2 &&
+       grep "^in-pack: 6" count.shallow2
+'
+
+test_expect_success 'clone shallow with --branch' '
+       git clone --depth 1 --branch A "file://$(pwd)/." shallow3
+'
+
+test_expect_success 'clone shallow object count' '
+       echo "in-pack: 12" > count3.expected &&
+       GIT_DIR=shallow3/.git git count-objects -v |
+               grep "^in-pack" > count3.actual &&
+       test_cmp count3.expected count3.actual
+'
+
+test_expect_success 'clone shallow with nonexistent --branch' '
+       git clone --depth 1 --branch Z "file://$(pwd)/." shallow4 &&
+       GIT_DIR=shallow4/.git git rev-parse HEAD >actual &&
+       git rev-parse HEAD >expected &&
+       test_cmp expected actual
+'
+
+test_expect_success 'clone shallow with detached HEAD' '
+       git checkout HEAD^ &&
+       git clone --depth 1 "file://$(pwd)/." shallow5 &&
+       git checkout - &&
+       GIT_DIR=shallow5/.git git rev-parse HEAD >actual &&
+       git rev-parse HEAD^ >expected &&
+       test_cmp expected actual
+'
+
+test_expect_success 'shallow clone pulling tags' '
+       git tag -a -m A TAGA1 A &&
+       git tag -a -m B TAGB1 B &&
+       git tag TAGA2 A &&
+       git tag TAGB2 B &&
+       git clone --depth 1 "file://$(pwd)/." shallow6 &&
+
+       cat >taglist.expected <<\EOF &&
+TAGB1
+TAGB2
+EOF
+       GIT_DIR=shallow6/.git git tag -l >taglist.actual &&
+       test_cmp taglist.expected taglist.actual &&
+
+       echo "in-pack: 7" > count6.expected &&
+       GIT_DIR=shallow6/.git git count-objects -v |
+               grep "^in-pack" > count6.actual &&
+       test_cmp count6.expected count6.actual
+'
+
 test_done
index e88dbd50f3368296a0e0487d7ed1675646ee9594..79ee91313059bf2e6efc7fe3c15c6fd56e8fc671 100755 (executable)
@@ -70,8 +70,8 @@ test_expect_success "fetch test for-merge" '
        master_in_two=`cd ../two && git rev-parse master` &&
        one_in_two=`cd ../two && git rev-parse one` &&
        {
-               echo "$master_in_two    not-for-merge"
                echo "$one_in_two       "
+               echo "$master_in_two    not-for-merge"
        } >expected &&
        cut -f -2 .git/FETCH_HEAD >actual &&
        test_cmp expected actual'
index e3a41ae811fb9957e8ac91b96a5d0c538c0515e1..12ab08e8acb1ec04ac0033fc5874cdf1cd94f1c7 100644 (file)
@@ -1,6 +1,6 @@
 # br-branches-default-merge
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 0567da4d5edd2ff4bb292a465ba9e64dcad9536b               branch 'three' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
index 1f60561cb12b37d2c85662476d5e3a8d11c4f1f2..54427522dd5f350c11f7f160020cccc2060b7309 100644 (file)
@@ -1,6 +1,6 @@
 # br-branches-default-merge branches-default
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 0567da4d5edd2ff4bb292a465ba9e64dcad9536b               branch 'three' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
index f31e1b3080f3cc0344ba88674d9de1d968cd372f..498a761aae8f048dd82f577de5d7bc92d97fa8f1 100644 (file)
@@ -1,7 +1,7 @@
 # br-branches-default-octopus
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689               branch 'one' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8               branch 'two' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
index 7060bd9ae59d02f7d4457e90aee06407c30da976..0857f134e10d662e6712910bf273985fd4aa8471 100644 (file)
@@ -1,7 +1,7 @@
 # br-branches-default-octopus branches-default
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689               branch 'one' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8               branch 'two' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
index aa1c8a937ea1ab67e519adad0b5e3483c240411f..54a77420d5d05de53c59c63d10e4438f3ed2b4dc 100644 (file)
@@ -1,6 +1,6 @@
 # br-branches-one-merge
-8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   branch 'one' of ../
 0567da4d5edd2ff4bb292a465ba9e64dcad9536b               branch 'three' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   branch 'one' of ../
 6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
index c93310a730b0dca890d42525148196645a8ca869..b4d1bb0b0ba18a01ce5d0705b2822564e63c628c 100644 (file)
@@ -1,6 +1,6 @@
 # br-branches-one-merge branches-one
-8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   branch 'one' of ../
 0567da4d5edd2ff4bb292a465ba9e64dcad9536b               branch 'three' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   branch 'one' of ../
 6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
index f6475b717a0d42a8944ab1a9593fe226a519c7a5..5ce764a06e421e0134b0f6710863d34e565ffb94 100644 (file)
@@ -1,8 +1,8 @@
 # br-config-explicit-merge
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b               branch 'three' of ../
 754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   branch 'one' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   branch 'two' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b               branch 'three' of ../
 6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
index 018bdd752a500295a5f1685ed53d1489fe780bf3..b1152b76dc2ea66a8d1dc4e5259362a34009fe49 100644 (file)
@@ -1,8 +1,8 @@
 # br-config-explicit-merge config-explicit
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b               branch 'three' of ../
 754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   branch 'one' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   branch 'two' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b               branch 'three' of ../
 6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
index 36d0270502637ce0d54b29b3f3c3839c8ca8d50c..110577bb67921bc061718aafa7c4194454e9acd5 100644 (file)
@@ -1,7 +1,7 @@
 # br-config-explicit-octopus
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689               branch 'one' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8               branch 'two' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   branch 'three' of ../
 6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
index 6654ad0898dfa7573a0552ec40489fd5c212ca85..a29dd8baba047bfea7e44735c902b3b4d0acc7ae 100644 (file)
@@ -1,7 +1,7 @@
 # br-config-explicit-octopus config-explicit
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689               branch 'one' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8               branch 'two' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   branch 'three' of ../
 6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
index 8bb5e8bc4e3ed113b59c19f08316ffc2785d5209..89f2596cb9d7a9d586e4c51c4e232b2ddb48ce52 100644 (file)
@@ -1,7 +1,7 @@
 # br-config-glob-merge
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b               branch 'three' of ../
 754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   branch 'one' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b               branch 'three' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   branch 'two' of ../
 6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
index 113c08dabe284a2d8a4de2b0a6f9dae7fde87103..2ba4832160a3894f439d7d54105c32ba3c1b008b 100644 (file)
@@ -1,7 +1,7 @@
 # br-config-glob-merge config-glob
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b               branch 'three' of ../
 754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   branch 'one' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b               branch 'three' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   branch 'two' of ../
 6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
index 9bbd537579fe82441b6ae3cd93b97967b3f97763..64994df7e2561e18013d005d0918819b9eb1a87a 100644 (file)
@@ -1,8 +1,8 @@
 # br-config-glob-octopus
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689               branch 'one' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   branch 'three' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8               branch 'two' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   branch 'three' of ../
 6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
index 4e510437d912057c7c61165cf13e610037d67274..681a725adc989b427afc134267e16d8f85e71334 100644 (file)
@@ -1,8 +1,8 @@
 # br-config-glob-octopus config-glob
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689               branch 'one' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   branch 'three' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8               branch 'two' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   branch 'three' of ../
 6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
index 7421b2cb8617c8999491089bfd26bba8fec6f808..d018b3515f74acaa27c32022b865402d1e08ccd2 100644 (file)
@@ -1,8 +1,8 @@
 # br-remote-explicit-merge
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b               branch 'three' of ../
 754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   branch 'one' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   branch 'two' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b               branch 'three' of ../
 6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
index b6975d38906e721270af011cff37cf64b8ae2313..0d3d780dd0ccd5f82401adb2124d54d214d9e0b5 100644 (file)
@@ -1,8 +1,8 @@
 # br-remote-explicit-merge remote-explicit
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b               branch 'three' of ../
 754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   branch 'one' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   branch 'two' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b               branch 'three' of ../
 6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
index 76812812eef26415ad84653016b62aa92664eafb..6f843044ede1ad776cb7b0a0769fe9aff8d5332c 100644 (file)
@@ -1,7 +1,7 @@
 # br-remote-explicit-octopus
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689               branch 'one' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8               branch 'two' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   branch 'three' of ../
 6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
index 4c896cfc1a0d66322ba08bb2c2581625d9fa9019..3546a837136dfef38101faa79aabff89efbc42a9 100644 (file)
@@ -1,7 +1,7 @@
 # br-remote-explicit-octopus remote-explicit
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689               branch 'one' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8               branch 'two' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   branch 'three' of ../
 6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
index 4b62b01de4b38c37f3c34736851308dd239761b1..7e1a433a64f38123b342333a91a87ffab5a2c3b1 100644 (file)
@@ -1,7 +1,7 @@
 # br-remote-glob-merge
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b               branch 'three' of ../
 754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   branch 'one' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b               branch 'three' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   branch 'two' of ../
 6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
index 7478f1f158877452db84848c987184804758741c..53571bb4ec64bd59a258e69ac1762e1ed73bc321 100644 (file)
@@ -1,7 +1,7 @@
 # br-remote-glob-merge remote-glob
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b               branch 'three' of ../
 754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   branch 'one' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b               branch 'three' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8       not-for-merge   branch 'two' of ../
 6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
index 254342058192f04b34c67d763afb4614a952fe0f..c7c8b6d7f47df130b00a25ccb626496e55dd2ff4 100644 (file)
@@ -1,8 +1,8 @@
 # br-remote-glob-octopus
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689               branch 'one' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   branch 'three' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8               branch 'two' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   branch 'three' of ../
 6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
index 5ffde9c03c782e95c34f3624ddcbfac581327d4f..36076fba0cc70f5df4879afff225dfb0e1145884 100644 (file)
@@ -1,8 +1,8 @@
 # br-remote-glob-octopus remote-glob
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689               branch 'one' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   branch 'three' of ../
 6134ee8f857693b96ff1cc98d3e2fd62b199e5a8               branch 'two' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f       not-for-merge   branch 'master' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b       not-for-merge   branch 'three' of ../
 6c9dec2b923228c9ff994c6cfe4ae16c12408dc5       not-for-merge   tag 'tag-master' of ../
 8e32a6d901327a23ef831511badce7bf3bf46689       not-for-merge   tag 'tag-one' of ../
 22feea448b023a2d864ef94b013735af34d238ba       not-for-merge   tag 'tag-one-tree' of ../
index c229fe68f11007fbb96d7b3837c18fd2f306a0bf..9ee52cfc458b2914ee82da6ac1117a2b1ae45a64 100755 (executable)
@@ -108,4 +108,11 @@ test_expect_failure TTY 'push --no-progress suppresses progress' '
        ! grep "Writing objects" err
 '
 
+test_expect_success TTY 'quiet push' '
+       ensure_fresh_upstream &&
+
+       test_terminal git push --quiet --no-progress upstream master 2>&1 | tee output &&
+       test_cmp /dev/null output
+'
+
 test_done
index 9b85d420c39a0f2192b6b829a6c7912ae28d8e92..d66ed2450854c3091105a43bb2aa0f831140ed1f 100755 (executable)
@@ -14,6 +14,7 @@ fi
 ROOT_PATH="$PWD"
 LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5541'}
 . "$TEST_DIRECTORY"/lib-httpd.sh
+. "$TEST_DIRECTORY"/lib-terminal.sh
 start_httpd
 
 test_expect_success 'setup remote repository' '
@@ -95,6 +96,32 @@ test_expect_success 'create and delete remote branch' '
        test_must_fail git show-ref --verify refs/remotes/origin/dev
 '
 
+cat >"$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git/hooks/update" <<EOF
+#!/bin/sh
+exit 1
+EOF
+chmod a+x "$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git/hooks/update"
+
+cat >exp <<EOF
+remote: error: hook declined to update refs/heads/dev2
+To http://127.0.0.1:$LIB_HTTPD_PORT/smart/test_repo.git
+ ! [remote rejected] dev2 -> dev2 (hook declined)
+error: failed to push some refs to 'http://127.0.0.1:5541/smart/test_repo.git'
+EOF
+
+test_expect_success 'rejected update prints status' '
+       cd "$ROOT_PATH"/test_repo_clone &&
+       git checkout -b dev2 &&
+       : >path4 &&
+       git add path4 &&
+       test_tick &&
+       git commit -m dev2 &&
+       test_must_fail git push origin dev2 2>act &&
+       sed -e "/^remote: /s/ *$//" <act >cmp &&
+       test_cmp exp cmp
+'
+rm -f "$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git/hooks/update"
+
 cat >exp <<EOF
 
 GET  /smart/test_repo.git/info/refs?service=git-upload-pack HTTP/1.1 200
@@ -106,6 +133,8 @@ GET  /smart/test_repo.git/info/refs?service=git-receive-pack HTTP/1.1 200
 POST /smart/test_repo.git/git-receive-pack HTTP/1.1 200
 GET  /smart/test_repo.git/info/refs?service=git-receive-pack HTTP/1.1 200
 POST /smart/test_repo.git/git-receive-pack HTTP/1.1 200
+GET  /smart/test_repo.git/info/refs?service=git-receive-pack HTTP/1.1 200
+POST /smart/test_repo.git/git-receive-pack HTTP/1.1 200
 EOF
 test_expect_success 'used receive-pack service' '
        sed -e "
@@ -186,5 +215,12 @@ test_expect_success 'push --mirror to repo with alternates' '
        git push --mirror "$HTTPD_URL"/smart/alternates-mirror.git
 '
 
+test_expect_success TTY 'quiet push' '
+       cd "$ROOT_PATH"/test_repo_clone &&
+       test_commit quiet &&
+       test_terminal git push --quiet --no-progress 2>&1 | tee output &&
+       test_cmp /dev/null output
+'
+
 stop_httpd
 test_done
diff --git a/t/t5570-git-daemon.sh b/t/t5570-git-daemon.sh
new file mode 100755 (executable)
index 0000000..7cbc999
--- /dev/null
@@ -0,0 +1,148 @@
+#!/bin/sh
+
+test_description='test fetching over git protocol'
+. ./test-lib.sh
+
+LIB_GIT_DAEMON_PORT=${LIB_GIT_DAEMON_PORT-5570}
+. "$TEST_DIRECTORY"/lib-git-daemon.sh
+start_git_daemon
+
+test_expect_success 'setup repository' '
+       echo content >file &&
+       git add file &&
+       git commit -m one
+'
+
+test_expect_success 'create git-accessible bare repository' '
+       mkdir "$GIT_DAEMON_DOCUMENT_ROOT_PATH/repo.git" &&
+       (cd "$GIT_DAEMON_DOCUMENT_ROOT_PATH/repo.git" &&
+        git --bare init &&
+        : >git-daemon-export-ok
+       ) &&
+       git remote add public "$GIT_DAEMON_DOCUMENT_ROOT_PATH/repo.git" &&
+       git push public master:master
+'
+
+test_expect_success 'clone git repository' '
+       git clone "$GIT_DAEMON_URL/repo.git" clone &&
+       test_cmp file clone/file
+'
+
+test_expect_success 'fetch changes via git protocol' '
+       echo content >>file &&
+       git commit -a -m two &&
+       git push public &&
+       (cd clone && git pull) &&
+       test_cmp file clone/file
+'
+
+test_expect_failure 'remote detects correct HEAD' '
+       git push public master:other &&
+       (cd clone &&
+        git remote set-head -d origin &&
+        git remote set-head -a origin &&
+        git symbolic-ref refs/remotes/origin/HEAD > output &&
+        echo refs/remotes/origin/master > expect &&
+        test_cmp expect output
+       )
+'
+
+test_expect_success 'prepare pack objects' '
+       cp -R "$GIT_DAEMON_DOCUMENT_ROOT_PATH"/repo.git "$GIT_DAEMON_DOCUMENT_ROOT_PATH"/repo_pack.git &&
+       (cd "$GIT_DAEMON_DOCUMENT_ROOT_PATH"/repo_pack.git &&
+        git --bare repack -a -d
+       )
+'
+
+test_expect_success 'fetch notices corrupt pack' '
+       cp -R "$GIT_DAEMON_DOCUMENT_ROOT_PATH"/repo_pack.git "$GIT_DAEMON_DOCUMENT_ROOT_PATH"/repo_bad1.git &&
+       (cd "$GIT_DAEMON_DOCUMENT_ROOT_PATH"/repo_bad1.git &&
+        p=`ls objects/pack/pack-*.pack` &&
+        chmod u+w $p &&
+        printf %0256d 0 | dd of=$p bs=256 count=1 seek=1 conv=notrunc
+       ) &&
+       mkdir repo_bad1.git &&
+       (cd repo_bad1.git &&
+        git --bare init &&
+        test_must_fail git --bare fetch "$GIT_DAEMON_URL/repo_bad1.git" &&
+        test 0 = `ls objects/pack/pack-*.pack | wc -l`
+       )
+'
+
+test_expect_success 'fetch notices corrupt idx' '
+       cp -R "$GIT_DAEMON_DOCUMENT_ROOT_PATH"/repo_pack.git "$GIT_DAEMON_DOCUMENT_ROOT_PATH"/repo_bad2.git &&
+       (cd "$GIT_DAEMON_DOCUMENT_ROOT_PATH"/repo_bad2.git &&
+        p=`ls objects/pack/pack-*.idx` &&
+        chmod u+w $p &&
+        printf %0256d 0 | dd of=$p bs=256 count=1 seek=1 conv=notrunc
+       ) &&
+       mkdir repo_bad2.git &&
+       (cd repo_bad2.git &&
+        git --bare init &&
+        test_must_fail git --bare fetch "$GIT_DAEMON_URL/repo_bad2.git" &&
+        test 0 = `ls objects/pack | wc -l`
+       )
+'
+
+test_remote_error()
+{
+       do_export=YesPlease
+       while test $# -gt 0
+       do
+               case $1 in
+               -x)
+                       shift
+                       chmod -x "$GIT_DAEMON_DOCUMENT_ROOT_PATH/repo.git"
+                       ;;
+               -n)
+                       shift
+                       do_export=
+                       ;;
+               *)
+                       break
+               esac
+       done
+
+       if test $# -ne 3
+       then
+               error "invalid number of arguments"
+       fi
+
+       cmd=$1
+       repo=$2
+       msg=$3
+
+       if test -x "$GIT_DAEMON_DOCUMENT_ROOT_PATH/$repo"
+       then
+               if test -n "$do_export"
+               then
+                       : >"$GIT_DAEMON_DOCUMENT_ROOT_PATH/$repo/git-daemon-export-ok"
+               else
+                       rm -f "$GIT_DAEMON_DOCUMENT_ROOT_PATH/$repo/git-daemon-export-ok"
+               fi
+       fi
+
+       test_must_fail git "$cmd" "$GIT_DAEMON_URL/$repo" 2>output &&
+       echo "fatal: remote error: $msg: /$repo" >expect &&
+       test_cmp expect output
+       ret=$?
+       chmod +x "$GIT_DAEMON_DOCUMENT_ROOT_PATH/repo.git"
+       (exit $ret)
+}
+
+msg="access denied or repository not exported"
+test_expect_success 'clone non-existent' "test_remote_error    clone nowhere.git '$msg'"
+test_expect_success 'push disabled'      "test_remote_error    push  repo.git    '$msg'"
+test_expect_success 'read access denied' "test_remote_error -x fetch repo.git    '$msg'"
+test_expect_success 'not exported'       "test_remote_error -n fetch repo.git    '$msg'"
+
+stop_git_daemon
+start_git_daemon --informative-errors
+
+test_expect_success 'clone non-existent' "test_remote_error    clone nowhere.git 'no such repository'"
+test_expect_success 'push disabled'      "test_remote_error    push  repo.git    'service not enabled'"
+test_expect_success 'read access denied' "test_remote_error -x fetch repo.git    'no such repository'"
+test_expect_success 'not exported'       "test_remote_error -n fetch repo.git    'repository not exported'"
+
+stop_git_daemon
+test_done
index af34a1e81711ca183680c9882353bd786b55e79f..839ad97b791c6aa757d0b82eea7fc16369ad4586 100755 (executable)
@@ -86,5 +86,6 @@ check_result 'I H E C B A' --full-history --date-order -- file
 check_result 'I E C B A' --simplify-merges -- file
 check_result 'I B A' -- file
 check_result 'I B A' --topo-order -- file
+check_result 'H' --first-parent -- another-file
 
 test_done
diff --git a/t/t7510-signed-commit.sh b/t/t7510-signed-commit.sh
new file mode 100755 (executable)
index 0000000..1d3c56f
--- /dev/null
@@ -0,0 +1,80 @@
+#!/bin/sh
+
+test_description='signed commit tests'
+. ./test-lib.sh
+. "$TEST_DIRECTORY/lib-gpg.sh"
+
+test_expect_success GPG 'create signed commits' '
+       echo 1 >file && git add file &&
+       test_tick && git commit -S -m initial &&
+       git tag initial &&
+       git branch side &&
+
+       echo 2 >file && test_tick && git commit -a -S -m second &&
+       git tag second &&
+
+       git checkout side &&
+       echo 3 >elif && git add elif &&
+       test_tick && git commit -m "third on side" &&
+
+       git checkout master &&
+       test_tick && git merge -S side &&
+       git tag merge &&
+
+       echo 4 >file && test_tick && git commit -a -m "fourth unsigned" &&
+       git tag fourth-unsigned &&
+
+       test_tick && git commit --amend -S -m "fourth signed" &&
+       git tag fourth-signed
+'
+
+test_expect_success GPG 'show signatures' '
+       (
+               for commit in initial second merge master
+               do
+                       git show --pretty=short --show-signature $commit >actual &&
+                       grep "Good signature from" actual || exit 1
+                       ! grep "BAD signature from" actual || exit 1
+                       echo $commit OK
+               done
+       ) &&
+       (
+               for commit in merge^2 fourth-unsigned
+               do
+                       git show --pretty=short --show-signature $commit >actual &&
+                       grep "Good signature from" actual && exit 1
+                       ! grep "BAD signature from" actual || exit 1
+                       echo $commit OK
+               done
+       )
+'
+
+test_expect_success GPG 'detect fudged signature' '
+       git cat-file commit master >raw &&
+
+       sed -e "s/fourth signed/4th forged/" raw >forged1 &&
+       git hash-object -w -t commit forged1 >forged1.commit &&
+       git show --pretty=short --show-signature $(cat forged1.commit) >actual1 &&
+       grep "BAD signature from" actual1 &&
+       ! grep "Good signature from" actual1
+'
+
+test_expect_success GPG 'detect fudged signature with NUL' '
+       git cat-file commit master >raw &&
+       cat raw >forged2 &&
+       echo Qwik | tr "Q" "\000" >>forged2 &&
+       git hash-object -w -t commit forged2 >forged2.commit &&
+       git show --pretty=short --show-signature $(cat forged2.commit) >actual2 &&
+       grep "BAD signature from" actual2 &&
+       ! grep "Good signature from" actual2
+'
+
+test_expect_success GPG 'amending already signed commit' '
+       git checkout fourth-signed^0 &&
+       git commit --amend -S --no-edit &&
+       git show -s --show-signature HEAD >actual &&
+       grep "Good signature from" actual &&
+       ! grep "BAD signature from" actual
+'
+
+test_done
index 4ee42f12f0af6bc7e4b072350f88988b85e40cbb..c3c22f7764adc1542579dfddf1b47a687a93607a 100755 (executable)
@@ -10,7 +10,7 @@ find_blame() {
 cat >helper <<'EOF'
 #!/bin/sh
 grep -q '^bin: ' "$1" || { echo "E: $1 is not \"binary\" file" 1>&2; exit 1; }
-sed 's/^bin: /converted: /' "$1"
+perl -p -e 's/^bin: /converted: /' "$1"
 EOF
 chmod +x helper
 
index 41db05cb4af6f42ef3065ba0576d8cfa3509c0ca..518358aa64790bd65d0b46789dfdaa80285863ed 100755 (executable)
@@ -19,9 +19,9 @@ then
     test_done
 fi
 
-CVSROOT=$(pwd)/cvsroot
-CVSWORK=$(pwd)/cvswork
-GIT_DIR=$(pwd)/.git
+CVSROOT=$PWD/cvsroot
+CVSWORK=$PWD/cvswork
+GIT_DIR=$PWD/.git
 export CVSROOT CVSWORK GIT_DIR
 
 rm -rf "$CVSROOT" "$CVSWORK"
index ab249178123653943bb087ccc40f2b420cc1e67c..0f771c673d58009e2bcde45254d4b4e9fa68efb9 100755 (executable)
@@ -474,6 +474,14 @@ test_expect_success \
        'path_info: project/branch:dir/' \
        'gitweb_run "" "/.git/master:foo/"'
 
+test_expect_success \
+       'path_info: project/branch (non-existent)' \
+       'gitweb_run "" "/.git/non-existent"'
+
+test_expect_success \
+       'path_info: project/branch:filename (non-existent branch)' \
+       'gitweb_run "" "/.git/non-existent:non-existent"'
+
 test_expect_success \
        'path_info: project/branch:file (non-existent)' \
        'gitweb_run "" "/.git/master:non-existent"'
index 1f1952a6573d39f0627be844b06a76eb8d98ce0a..0571602129306f89292c482a7dc4858ea08a9867 100755 (executable)
@@ -146,7 +146,7 @@ test_expect_success 'clone --use-client-spec' '
        (
                cd "$git" &&
                test_path_is_file bus/dir/f4 &&
-               test_path_is_file file1
+               test_path_is_missing file1
        ) &&
        cleanup_git &&
 
@@ -159,7 +159,7 @@ test_expect_success 'clone --use-client-spec' '
                "$GITP4" sync //depot/... &&
                git checkout -b master p4/master &&
                test_path_is_file bus/dir/f4 &&
-               test_path_is_file file1
+               test_path_is_missing file1
        )
 '
 
diff --git a/t/t9809-git-p4-client-view.sh b/t/t9809-git-p4-client-view.sh
new file mode 100755 (executable)
index 0000000..ae9145e
--- /dev/null
@@ -0,0 +1,683 @@
+#!/bin/sh
+
+test_description='git-p4 client view'
+
+. ./lib-git-p4.sh
+
+test_expect_success 'start p4d' '
+       start_p4d
+'
+
+#
+# Construct a client with this list of View lines
+#
+client_view() {
+       (
+               cat <<-EOF &&
+               Client: client
+               Description: client
+               Root: $cli
+               View:
+               EOF
+               for arg ; do
+                       printf "\t$arg\n"
+               done
+       ) | p4 client -i
+}
+
+#
+# Verify these files exist, exactly.  Caller creates
+# a list of files in file "files".
+#
+check_files_exist() {
+       ok=0 &&
+       num=${#@} &&
+       for arg ; do
+               test_path_is_file "$arg" &&
+               ok=$(($ok + 1))
+       done &&
+       test $ok -eq $num &&
+       test_line_count = $num files
+}
+
+#
+# Sync up the p4 client, make sure the given files (and only
+# those) exist.
+#
+client_verify() {
+       (
+               cd "$cli" &&
+               p4 sync &&
+               find . -type f ! -name files >files &&
+               check_files_exist "$@"
+       )
+}
+
+#
+# Make sure the named files, exactly, exist.
+#
+git_verify() {
+       (
+               cd "$git" &&
+               git ls-files >files &&
+               check_files_exist "$@"
+       )
+}
+
+# //depot
+#   - dir1
+#     - file11
+#     - file12
+#   - dir2
+#     - file21
+#     - file22
+test_expect_success 'init depot' '
+       (
+               cd "$cli" &&
+               for d in 1 2 ; do
+                       mkdir -p dir$d &&
+                       for f in 1 2 ; do
+                               echo dir$d/file$d$f >dir$d/file$d$f &&
+                               p4 add dir$d/file$d$f &&
+                               p4 submit -d "dir$d/file$d$f"
+                       done
+               done &&
+               find . -type f ! -name files >files &&
+               check_files_exist dir1/file11 dir1/file12 \
+                                 dir2/file21 dir2/file22
+       )
+'
+
+# double % for printf
+test_expect_success 'unsupported view wildcard %%n' '
+       client_view "//depot/%%%%1/sub/... //client/sub/%%%%1/..." &&
+       test_when_finished cleanup_git &&
+       test_must_fail "$GITP4" clone --use-client-spec --dest="$git" //depot
+'
+
+test_expect_success 'unsupported view wildcard *' '
+       client_view "//depot/*/bar/... //client/*/bar/..." &&
+       test_when_finished cleanup_git &&
+       test_must_fail "$GITP4" clone --use-client-spec --dest="$git" //depot
+'
+
+test_expect_success 'wildcard ... only supported at end of spec 1' '
+       client_view "//depot/.../file11 //client/.../file11" &&
+       test_when_finished cleanup_git &&
+       test_must_fail "$GITP4" clone --use-client-spec --dest="$git" //depot
+'
+
+test_expect_success 'wildcard ... only supported at end of spec 2' '
+       client_view "//depot/.../a/... //client/.../a/..." &&
+       test_when_finished cleanup_git &&
+       test_must_fail "$GITP4" clone --use-client-spec --dest="$git" //depot
+'
+
+test_expect_success 'basic map' '
+       client_view "//depot/dir1/... //client/cli1/..." &&
+       files="cli1/file11 cli1/file12" &&
+       client_verify $files &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git_verify $files
+'
+
+test_expect_success 'client view with no mappings' '
+       client_view &&
+       client_verify &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git_verify
+'
+
+test_expect_success 'single file map' '
+       client_view "//depot/dir1/file11 //client/file11" &&
+       files="file11" &&
+       client_verify $files &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git_verify $files
+'
+
+test_expect_success 'later mapping takes precedence (entire repo)' '
+       client_view "//depot/dir1/... //client/cli1/..." \
+                   "//depot/... //client/cli2/..." &&
+       files="cli2/dir1/file11 cli2/dir1/file12
+              cli2/dir2/file21 cli2/dir2/file22" &&
+       client_verify $files &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git_verify $files
+'
+
+test_expect_success 'later mapping takes precedence (partial repo)' '
+       client_view "//depot/dir1/... //client/..." \
+                   "//depot/dir2/... //client/..." &&
+       files="file21 file22" &&
+       client_verify $files &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git_verify $files
+'
+
+# Reading the view backwards,
+#   dir2 goes to cli12
+#   dir1 cannot go to cli12 since it was filled by dir2
+#   dir1 also does not go to cli3, since the second rule
+#     noticed that it matched, but was already filled
+test_expect_success 'depot path matching rejected client path' '
+       client_view "//depot/dir1/... //client/cli3/..." \
+                   "//depot/dir1/... //client/cli12/..." \
+                   "//depot/dir2/... //client/cli12/..." &&
+       files="cli12/file21 cli12/file22" &&
+       client_verify $files &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git_verify $files
+'
+
+# since both have the same //client/..., the exclusion
+# rule keeps everything out
+test_expect_success 'exclusion wildcard, client rhs same (odd)' '
+       client_view "//depot/... //client/..." \
+                   "-//depot/dir2/... //client/..." &&
+       client_verify &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git_verify
+'
+
+test_expect_success 'exclusion wildcard, client rhs different (normal)' '
+       client_view "//depot/... //client/..." \
+                   "-//depot/dir2/... //client/dir2/..." &&
+       files="dir1/file11 dir1/file12" &&
+       client_verify $files &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git_verify $files
+'
+
+test_expect_success 'exclusion single file' '
+       client_view "//depot/... //client/..." \
+                   "-//depot/dir2/file22 //client/file22" &&
+       files="dir1/file11 dir1/file12 dir2/file21" &&
+       client_verify $files &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git_verify $files
+'
+
+test_expect_success 'overlay wildcard' '
+       client_view "//depot/dir1/... //client/cli/..." \
+                   "+//depot/dir2/... //client/cli/...\n" &&
+       files="cli/file11 cli/file12 cli/file21 cli/file22" &&
+       client_verify $files &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git_verify $files
+'
+
+test_expect_success 'overlay single file' '
+       client_view "//depot/dir1/... //client/cli/..." \
+                   "+//depot/dir2/file21 //client/cli/file21" &&
+       files="cli/file11 cli/file12 cli/file21" &&
+       client_verify $files &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git_verify $files
+'
+
+test_expect_success 'exclusion with later inclusion' '
+       client_view "//depot/... //client/..." \
+                   "-//depot/dir2/... //client/dir2/..." \
+                   "//depot/dir2/... //client/dir2incl/..." &&
+       files="dir1/file11 dir1/file12 dir2incl/file21 dir2incl/file22" &&
+       client_verify $files &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git_verify $files
+'
+
+test_expect_success 'quotes on rhs only' '
+       client_view "//depot/dir1/... \"//client/cdir 1/...\"" &&
+       client_verify "cdir 1/file11" "cdir 1/file12" &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git_verify "cdir 1/file11" "cdir 1/file12"
+'
+
+#
+# What happens when two files of the same name are overlayed together?
+# The last-listed file should take preference.
+#
+# //depot
+#   - dir1
+#     - file11
+#     - file12
+#     - filecollide
+#   - dir2
+#     - file21
+#     - file22
+#     - filecollide
+#
+test_expect_success 'overlay collision setup' '
+       client_view "//depot/... //client/..." &&
+       (
+               cd "$cli" &&
+               p4 sync &&
+               echo dir1/filecollide >dir1/filecollide &&
+               p4 add dir1/filecollide &&
+               p4 submit -d dir1/filecollide &&
+               echo dir2/filecollide >dir2/filecollide &&
+               p4 add dir2/filecollide &&
+               p4 submit -d dir2/filecollide
+       )
+'
+
+test_expect_success 'overlay collision 1 to 2' '
+       client_view "//depot/dir1/... //client/..." \
+                   "+//depot/dir2/... //client/..." &&
+       files="file11 file12 file21 file22 filecollide" &&
+       echo dir2/filecollide >actual &&
+       client_verify $files &&
+       test_cmp actual "$cli"/filecollide &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git_verify $files &&
+       test_cmp actual "$git"/filecollide
+'
+
+test_expect_failure 'overlay collision 2 to 1' '
+       client_view "//depot/dir2/... //client/..." \
+                   "+//depot/dir1/... //client/..." &&
+       files="file11 file12 file21 file22 filecollide" &&
+       echo dir1/filecollide >actual &&
+       client_verify $files &&
+       test_cmp actual "$cli"/filecollide &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git_verify $files &&
+       test_cmp actual "$git"/filecollide
+'
+
+test_expect_success 'overlay collision delete 2' '
+       client_view "//depot/... //client/..." &&
+       (
+               cd "$cli" &&
+               p4 sync &&
+               p4 delete dir2/filecollide &&
+               p4 submit -d "remove dir2/filecollide"
+       )
+'
+
+# no filecollide, got deleted with dir2
+test_expect_failure 'overlay collision 1 to 2, but 2 deleted' '
+       client_view "//depot/dir1/... //client/..." \
+                   "+//depot/dir2/... //client/..." &&
+       files="file11 file12 file21 file22" &&
+       client_verify $files &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git_verify $files
+'
+
+test_expect_success 'overlay collision update 1' '
+       client_view "//depot/dir1/... //client/dir1/..." &&
+       (
+               cd "$cli" &&
+               p4 sync &&
+               p4 open dir1/filecollide &&
+               echo dir1/filecollide update >dir1/filecollide &&
+               p4 submit -d "update dir1/filecollide"
+       )
+'
+
+# still no filecollide, dir2 still wins with the deletion even though the
+# change to dir1 is more recent
+test_expect_failure 'overlay collision 1 to 2, but 2 deleted, then 1 updated' '
+       client_view "//depot/dir1/... //client/..." \
+                   "+//depot/dir2/... //client/..." &&
+       files="file11 file12 file21 file22" &&
+       client_verify $files &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git_verify $files
+'
+
+test_expect_success 'overlay collision delete filecollides' '
+       client_view "//depot/... //client/..." &&
+       (
+               cd "$cli" &&
+               p4 sync &&
+               p4 delete dir1/filecollide dir2/filecollide &&
+               p4 submit -d "remove filecollides"
+       )
+'
+
+#
+# Overlays as part of sync, rather than initial checkout:
+#   1.  add a file in dir1
+#   2.  sync to include it
+#   3.  add same file in dir2
+#   4.  sync, make sure content switches as dir2 has priority
+#   5.  add another file in dir1
+#   6.  sync
+#   7.  add/delete same file in dir2
+#   8.  sync, make sure it disappears, again dir2 wins
+#   9.  cleanup
+#
+# //depot
+#   - dir1
+#     - file11
+#     - file12
+#     - colA
+#     - colB
+#   - dir2
+#     - file21
+#     - file22
+#     - colA
+#     - colB
+#
+test_expect_success 'overlay sync: add colA in dir1' '
+       client_view "//depot/dir1/... //client/dir1/..." &&
+       (
+               cd "$cli" &&
+               p4 sync &&
+               echo dir1/colA >dir1/colA &&
+               p4 add dir1/colA &&
+               p4 submit -d dir1/colA
+       )
+'
+
+test_expect_success 'overlay sync: initial git checkout' '
+       client_view "//depot/dir1/... //client/..." \
+                   "+//depot/dir2/... //client/..." &&
+       files="file11 file12 file21 file22 colA" &&
+       echo dir1/colA >actual &&
+       client_verify $files &&
+       test_cmp actual "$cli"/colA &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git_verify $files &&
+       test_cmp actual "$git"/colA
+'
+
+test_expect_success 'overlay sync: add colA in dir2' '
+       client_view "//depot/dir2/... //client/dir2/..." &&
+       (
+               cd "$cli" &&
+               p4 sync &&
+               echo dir2/colA >dir2/colA &&
+               p4 add dir2/colA &&
+               p4 submit -d dir2/colA
+       )
+'
+
+test_expect_success 'overlay sync: colA content switch' '
+       client_view "//depot/dir1/... //client/..." \
+                   "+//depot/dir2/... //client/..." &&
+       files="file11 file12 file21 file22 colA" &&
+       echo dir2/colA >actual &&
+       client_verify $files &&
+       test_cmp actual "$cli"/colA &&
+       (
+               cd "$git" &&
+               "$GITP4" sync --use-client-spec &&
+               git merge --ff-only p4/master
+       ) &&
+       git_verify $files &&
+       test_cmp actual "$git"/colA
+'
+
+test_expect_success 'overlay sync: add colB in dir1' '
+       client_view "//depot/dir1/... //client/dir1/..." &&
+       (
+               cd "$cli" &&
+               p4 sync &&
+               echo dir1/colB >dir1/colB &&
+               p4 add dir1/colB &&
+               p4 submit -d dir1/colB
+       )
+'
+
+test_expect_success 'overlay sync: colB appears' '
+       client_view "//depot/dir1/... //client/..." \
+                   "+//depot/dir2/... //client/..." &&
+       files="file11 file12 file21 file22 colA colB" &&
+       echo dir1/colB >actual &&
+       client_verify $files &&
+       test_cmp actual "$cli"/colB &&
+       (
+               cd "$git" &&
+               "$GITP4" sync --use-client-spec &&
+               git merge --ff-only p4/master
+       ) &&
+       git_verify $files &&
+       test_cmp actual "$git"/colB
+'
+
+test_expect_success 'overlay sync: add/delete colB in dir2' '
+       client_view "//depot/dir2/... //client/dir2/..." &&
+       (
+               cd "$cli" &&
+               p4 sync &&
+               echo dir2/colB >dir2/colB &&
+               p4 add dir2/colB &&
+               p4 submit -d dir2/colB &&
+               p4 delete dir2/colB &&
+               p4 submit -d "delete dir2/colB"
+       )
+'
+
+test_expect_success 'overlay sync: colB disappears' '
+       client_view "//depot/dir1/... //client/..." \
+                   "+//depot/dir2/... //client/..." &&
+       files="file11 file12 file21 file22 colA" &&
+       client_verify $files &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               "$GITP4" sync --use-client-spec &&
+               git merge --ff-only p4/master
+       ) &&
+       git_verify $files
+'
+
+test_expect_success 'overlay sync: cleanup' '
+       client_view "//depot/... //client/..." &&
+       (
+               cd "$cli" &&
+               p4 sync &&
+               p4 delete dir1/colA dir2/colA dir1/colB &&
+               p4 submit -d "remove overlay sync files"
+       )
+'
+
+#
+# Overlay tests again, but swapped so dir1 has priority.
+#   1.  add a file in dir1
+#   2.  sync to include it
+#   3.  add same file in dir2
+#   4.  sync, make sure content does not switch
+#   5.  add another file in dir1
+#   6.  sync
+#   7.  add/delete same file in dir2
+#   8.  sync, make sure it is still there
+#   9.  cleanup
+#
+# //depot
+#   - dir1
+#     - file11
+#     - file12
+#     - colA
+#     - colB
+#   - dir2
+#     - file21
+#     - file22
+#     - colA
+#     - colB
+#
+test_expect_success 'overlay sync swap: add colA in dir1' '
+       client_view "//depot/dir1/... //client/dir1/..." &&
+       (
+               cd "$cli" &&
+               p4 sync &&
+               echo dir1/colA >dir1/colA &&
+               p4 add dir1/colA &&
+               p4 submit -d dir1/colA
+       )
+'
+
+test_expect_success 'overlay sync swap: initial git checkout' '
+       client_view "//depot/dir2/... //client/..." \
+                   "+//depot/dir1/... //client/..." &&
+       files="file11 file12 file21 file22 colA" &&
+       echo dir1/colA >actual &&
+       client_verify $files &&
+       test_cmp actual "$cli"/colA &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git_verify $files &&
+       test_cmp actual "$git"/colA
+'
+
+test_expect_success 'overlay sync swap: add colA in dir2' '
+       client_view "//depot/dir2/... //client/dir2/..." &&
+       (
+               cd "$cli" &&
+               p4 sync &&
+               echo dir2/colA >dir2/colA &&
+               p4 add dir2/colA &&
+               p4 submit -d dir2/colA
+       )
+'
+
+test_expect_failure 'overlay sync swap: colA no content switch' '
+       client_view "//depot/dir2/... //client/..." \
+                   "+//depot/dir1/... //client/..." &&
+       files="file11 file12 file21 file22 colA" &&
+       echo dir1/colA >actual &&
+       client_verify $files &&
+       test_cmp actual "$cli"/colA &&
+       (
+               cd "$git" &&
+               "$GITP4" sync --use-client-spec &&
+               git merge --ff-only p4/master
+       ) &&
+       git_verify $files &&
+       test_cmp actual "$git"/colA
+'
+
+test_expect_success 'overlay sync swap: add colB in dir1' '
+       client_view "//depot/dir1/... //client/dir1/..." &&
+       (
+               cd "$cli" &&
+               p4 sync &&
+               echo dir1/colB >dir1/colB &&
+               p4 add dir1/colB &&
+               p4 submit -d dir1/colB
+       )
+'
+
+test_expect_success 'overlay sync swap: colB appears' '
+       client_view "//depot/dir2/... //client/..." \
+                   "+//depot/dir1/... //client/..." &&
+       files="file11 file12 file21 file22 colA colB" &&
+       echo dir1/colB >actual &&
+       client_verify $files &&
+       test_cmp actual "$cli"/colB &&
+       (
+               cd "$git" &&
+               "$GITP4" sync --use-client-spec &&
+               git merge --ff-only p4/master
+       ) &&
+       git_verify $files &&
+       test_cmp actual "$git"/colB
+'
+
+test_expect_success 'overlay sync swap: add/delete colB in dir2' '
+       client_view "//depot/dir2/... //client/dir2/..." &&
+       (
+               cd "$cli" &&
+               p4 sync &&
+               echo dir2/colB >dir2/colB &&
+               p4 add dir2/colB &&
+               p4 submit -d dir2/colB &&
+               p4 delete dir2/colB &&
+               p4 submit -d "delete dir2/colB"
+       )
+'
+
+test_expect_failure 'overlay sync swap: colB no change' '
+       client_view "//depot/dir2/... //client/..." \
+                   "+//depot/dir1/... //client/..." &&
+       files="file11 file12 file21 file22 colA colB" &&
+       echo dir1/colB >actual &&
+       client_verify $files &&
+       test_cmp actual "$cli"/colB &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               "$GITP4" sync --use-client-spec &&
+               git merge --ff-only p4/master
+       ) &&
+       git_verify $files &&
+       test_cmp actual "$cli"/colB
+'
+
+test_expect_success 'overlay sync swap: cleanup' '
+       client_view "//depot/... //client/..." &&
+       (
+               cd "$cli" &&
+               p4 sync &&
+               p4 delete dir1/colA dir2/colA dir1/colB &&
+               p4 submit -d "remove overlay sync files"
+       )
+'
+
+#
+# Rename directories to test quoting in depot-side mappings
+# //depot
+#    - "dir 1"
+#       - file11
+#       - file12
+#    - "dir 2"
+#       - file21
+#       - file22
+#
+test_expect_success 'rename files to introduce spaces' '
+       client_view "//depot/... //client/..." &&
+       client_verify dir1/file11 dir1/file12 \
+                     dir2/file21 dir2/file22 &&
+       (
+               cd "$cli" &&
+               p4 open dir1/... &&
+               p4 move dir1/... "dir 1"/... &&
+               p4 open dir2/... &&
+               p4 move dir2/... "dir 2"/... &&
+               p4 submit -d "rename with spaces"
+       ) &&
+       client_verify "dir 1/file11" "dir 1/file12" \
+                     "dir 2/file21" "dir 2/file22"
+'
+
+test_expect_success 'quotes on lhs only' '
+       client_view "\"//depot/dir 1/...\" //client/cdir1/..." &&
+       files="cdir1/file11 cdir1/file12" &&
+       client_verify $files &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       client_verify $files
+'
+
+test_expect_success 'quotes on both sides' '
+       client_view "\"//depot/dir 1/...\" \"//client/cdir 1/...\"" &&
+       client_verify "cdir 1/file11" "cdir 1/file12" &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git_verify "cdir 1/file11" "cdir 1/file12"
+'
+
+test_expect_success 'kill p4d' '
+       kill_p4d
+'
+
+test_done
index a65dfc7ea93b3ec95e6ab7cd5b758029fe599d22..709a30067e5486526095cad98c4ade7334613fc8 100644 (file)
@@ -329,6 +329,19 @@ test_tick () {
        export GIT_COMMITTER_DATE GIT_AUTHOR_DATE
 }
 
+# Stop execution and start a shell. This is useful for debugging tests and
+# only makes sense together with "-v".
+#
+# Be sure to remove all invocations of this command before submitting.
+
+test_pause () {
+       if test "$verbose" = t; then
+               "$SHELL_PATH" <&6 >&3 2>&4
+       else
+               error >&5 "test_pause requires --verbose"
+       fi
+}
+
 # Call test_commit with the arguments "<message> [<file> [<contents>]]"
 #
 # This will commit a file with the given contents and the given commit
index f82dba6a1f43bf2a259952a4fd6db94d6335deb7..492c7cd744d5f0739c8393ab67705c0be1254783 100644 (file)
@@ -661,6 +661,9 @@ match_wildcards:
                /*
                 * Match all directories. We'll try to match files
                 * later on.
+                * max_depth is ignored but we may consider support it
+                * in future, see
+                * http://thread.gmane.org/gmane.comp.version-control.git/163757/focus=163840
                 */
                if (ps->recursive && S_ISDIR(entry->mode))
                        return entry_interesting;
index 84b15099f2fbf89dedf5241a6176b39abd4b1723..01f119f9700791ddc59a81cdc80fd365e678622a 100644 (file)
@@ -9,48 +9,114 @@ static int unix_stream_socket(void)
        return fd;
 }
 
-static void unix_sockaddr_init(struct sockaddr_un *sa, const char *path)
+static int chdir_len(const char *orig, int len)
+{
+       char *path = xmemdupz(orig, len);
+       int r = chdir(path);
+       free(path);
+       return r;
+}
+
+struct unix_sockaddr_context {
+       char orig_dir[PATH_MAX];
+};
+
+static void unix_sockaddr_cleanup(struct unix_sockaddr_context *ctx)
+{
+       if (!ctx->orig_dir[0])
+               return;
+       /*
+        * If we fail, we can't just return an error, since we have
+        * moved the cwd of the whole process, which could confuse calling
+        * code.  We are better off to just die.
+        */
+       if (chdir(ctx->orig_dir) < 0)
+               die("unable to restore original working directory");
+}
+
+static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path,
+                             struct unix_sockaddr_context *ctx)
 {
        int size = strlen(path) + 1;
-       if (size > sizeof(sa->sun_path))
-               die("socket path is too long to fit in sockaddr");
+
+       ctx->orig_dir[0] = '\0';
+       if (size > sizeof(sa->sun_path)) {
+               const char *slash = find_last_dir_sep(path);
+               const char *dir;
+
+               if (!slash) {
+                       errno = ENAMETOOLONG;
+                       return -1;
+               }
+
+               dir = path;
+               path = slash + 1;
+               size = strlen(path) + 1;
+               if (size > sizeof(sa->sun_path)) {
+                       errno = ENAMETOOLONG;
+                       return -1;
+               }
+
+               if (!getcwd(ctx->orig_dir, sizeof(ctx->orig_dir))) {
+                       errno = ENAMETOOLONG;
+                       return -1;
+               }
+               if (chdir_len(dir, slash - dir) < 0)
+                       return -1;
+       }
+
        memset(sa, 0, sizeof(*sa));
        sa->sun_family = AF_UNIX;
        memcpy(sa->sun_path, path, size);
+       return 0;
 }
 
 int unix_stream_connect(const char *path)
 {
-       int fd;
+       int fd, saved_errno;
        struct sockaddr_un sa;
+       struct unix_sockaddr_context ctx;
 
-       unix_sockaddr_init(&sa, path);
-       fd = unix_stream_socket();
-       if (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
-               close(fd);
+       if (unix_sockaddr_init(&sa, path, &ctx) < 0)
                return -1;
-       }
+       fd = unix_stream_socket();
+       if (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)
+               goto fail;
+       unix_sockaddr_cleanup(&ctx);
        return fd;
+
+fail:
+       saved_errno = errno;
+       unix_sockaddr_cleanup(&ctx);
+       close(fd);
+       errno = saved_errno;
+       return -1;
 }
 
 int unix_stream_listen(const char *path)
 {
-       int fd;
+       int fd, saved_errno;
        struct sockaddr_un sa;
+       struct unix_sockaddr_context ctx;
 
-       unix_sockaddr_init(&sa, path);
+       if (unix_sockaddr_init(&sa, path, &ctx) < 0)
+               return -1;
        fd = unix_stream_socket();
 
        unlink(path);
-       if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
-               close(fd);
-               return -1;
-       }
+       if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)
+               goto fail;
 
-       if (listen(fd, 5) < 0) {
-               close(fd);
-               return -1;
-       }
+       if (listen(fd, 5) < 0)
+               goto fail;
 
+       unix_sockaddr_cleanup(&ctx);
        return fd;
+
+fail:
+       saved_errno = errno;
+       unix_sockaddr_cleanup(&ctx);
+       close(fd);
+       errno = saved_errno;
+       return -1;
 }
index c01e161a9d713b64951210416d44e96835d12a64..bb08e2eb0dc52f68807a83b671fa61a2c2224f41 100644 (file)
@@ -585,6 +585,7 @@ static void receive_needs(void)
                write_str_in_full(debug_fd, "#S\n");
        for (;;) {
                struct object *o;
+               const char *features;
                unsigned char sha1_buf[20];
                len = packet_read_line(0, line, sizeof(line));
                reset_timeout();
@@ -616,23 +617,26 @@ static void receive_needs(void)
                    get_sha1_hex(line+5, sha1_buf))
                        die("git upload-pack: protocol error, "
                            "expected to get sha, not '%s'", line);
-               if (strstr(line+45, "multi_ack_detailed"))
+
+               features = line + 45;
+
+               if (parse_feature_request(features, "multi_ack_detailed"))
                        multi_ack = 2;
-               else if (strstr(line+45, "multi_ack"))
+               else if (parse_feature_request(features, "multi_ack"))
                        multi_ack = 1;
-               if (strstr(line+45, "no-done"))
+               if (parse_feature_request(features, "no-done"))
                        no_done = 1;
-               if (strstr(line+45, "thin-pack"))
+               if (parse_feature_request(features, "thin-pack"))
                        use_thin_pack = 1;
-               if (strstr(line+45, "ofs-delta"))
+               if (parse_feature_request(features, "ofs-delta"))
                        use_ofs_delta = 1;
-               if (strstr(line+45, "side-band-64k"))
+               if (parse_feature_request(features, "side-band-64k"))
                        use_sideband = LARGE_PACKET_MAX;
-               else if (strstr(line+45, "side-band"))
+               else if (parse_feature_request(features, "side-band"))
                        use_sideband = DEFAULT_PACKET_MAX;
-               if (strstr(line+45, "no-progress"))
+               if (parse_feature_request(features, "no-progress"))
                        no_progress = 1;
-               if (strstr(line+45, "include-tag"))
+               if (parse_feature_request(features, "include-tag"))
                        use_include_tag = 1;
 
                o = lookup_object(sha1_buf);