]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'mt/test-lib-bundled-short-options'
authorJunio C Hamano <gitster@pobox.com>
Wed, 22 Apr 2020 20:42:43 +0000 (13:42 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 22 Apr 2020 20:42:43 +0000 (13:42 -0700)
Minor test usability improvement.

* mt/test-lib-bundled-short-options:
  test-lib: allow short options to be bundled

157 files changed:
.gitignore
Documentation/RelNotes/2.17.4.txt [new file with mode: 0644]
Documentation/RelNotes/2.17.5.txt [new file with mode: 0644]
Documentation/RelNotes/2.18.3.txt [new file with mode: 0644]
Documentation/RelNotes/2.18.4.txt [new file with mode: 0644]
Documentation/RelNotes/2.19.4.txt [new file with mode: 0644]
Documentation/RelNotes/2.19.5.txt [new file with mode: 0644]
Documentation/RelNotes/2.20.3.txt [new file with mode: 0644]
Documentation/RelNotes/2.20.4.txt [new file with mode: 0644]
Documentation/RelNotes/2.21.2.txt [new file with mode: 0644]
Documentation/RelNotes/2.21.3.txt [new file with mode: 0644]
Documentation/RelNotes/2.22.3.txt [new file with mode: 0644]
Documentation/RelNotes/2.22.4.txt [new file with mode: 0644]
Documentation/RelNotes/2.23.2.txt [new file with mode: 0644]
Documentation/RelNotes/2.23.3.txt [new file with mode: 0644]
Documentation/RelNotes/2.24.2.txt [new file with mode: 0644]
Documentation/RelNotes/2.24.3.txt [new file with mode: 0644]
Documentation/RelNotes/2.25.3.txt [new file with mode: 0644]
Documentation/RelNotes/2.25.4.txt [new file with mode: 0644]
Documentation/RelNotes/2.26.1.txt [new file with mode: 0644]
Documentation/RelNotes/2.26.2.txt [new file with mode: 0644]
Documentation/RelNotes/2.27.0.txt [new file with mode: 0644]
Documentation/config.txt
Documentation/config/feature.txt
Documentation/config/http.txt
Documentation/config/pack.txt
Documentation/config/stash.txt
Documentation/config/tag.txt
Documentation/config/tar.txt [new file with mode: 0644]
Documentation/fetch-options.txt
Documentation/git-clone.txt
Documentation/git-fast-import.txt
Documentation/git-init.txt
Documentation/git-pack-objects.txt
Documentation/git.txt
Documentation/howto/maintain-git.txt
GIT-VERSION-GEN
Makefile
RelNotes
abspath.c
advice.c
advice.h
apply.c
archive.c
archive.h
builtin/cat-file.c
builtin/checkout.c
builtin/clone.c
builtin/commit-graph.c
builtin/commit.c
builtin/describe.c
builtin/fmt-merge-msg.c
builtin/init-db.c
builtin/merge-base.c
builtin/pack-objects.c
builtin/pull.c
builtin/rebase.c
builtin/reset.c
builtin/rev-parse.c
builtin/stash.c
builtin/submodule--helper.c
builtin/tag.c
builtin/worktree.c
cache.h
ci/lib.sh
commit-slab.h
commit.c
config.mak.dev
connected.c
contrib/fast-import/import-tars.perl
convert.c
convert.h
credential.c
credential.h
csum-file.c
diff.c
editor.c
entry.c
environment.c
fast-import.c
fsck.c
git-legacy-stash.sh [deleted file]
git-p4.py
git.c
gpg-interface.c
gpg-interface.h
hash.h
hex.c
http.c
log-tree.c
merge-recursive.c
merge.c
oidset.h
path.c
repo-settings.c
repository.c
sequencer.c
sequencer.h
setup.c
sha1-file.c
sha256/gcrypt.h
submodule.c
submodule.h
t/README
t/helper/test-advise.c [new file with mode: 0644]
t/helper/test-dump-split-index.c
t/helper/test-path-utils.c
t/helper/test-repository.c
t/helper/test-tool.c
t/helper/test-tool.h
t/lib-credential.sh
t/lib-gpg.sh
t/lib-submodule-update.sh
t/t0000-basic.sh
t/t0018-advice.sh [new file with mode: 0755]
t/t0021-conversion.sh
t/t0021/rot13-filter.pl
t/t0300-credentials.sh
t/t1450-fsck.sh
t/t2402-worktree-list.sh
t/t3403-rebase-skip.sh
t/t3404-rebase-interactive.sh
t/t3417-rebase-whitespace-fix.sh
t/t3419-rebase-patch-id.sh
t/t3431-rebase-fork-point.sh
t/t3507-cherry-pick-conflict.sh
t/t3510-cherry-pick-sequence.sh
t/t3700-add.sh
t/t3701-add-interactive.sh
t/t3903-stash.sh
t/t4057-diff-combined-paths.sh
t/t4150-am.sh
t/t4202-log.sh
t/t5322-pack-objects-sparse.sh
t/t5521-pull-options.sh
t/t5537-fetch-shallow.sh
t/t5550-http-fetch-dumb.sh
t/t5604-clone-reference.sh
t/t6120-describe.sh
t/t6200-fmt-merge-msg.sh
t/t6300-for-each-ref.sh
t/t7004-tag.sh
t/t7112-reset-submodule.sh
t/t7400-submodule-basic.sh
t/t7416-submodule-dash-url.sh
t/t7510-signed-commit.sh
t/t7601-merge-pull-config.sh
t/t7609-merge-co-error-msgs.sh
t/t9010-svn-fe.sh
t/t9300-fast-import.sh
t/t9500-gitweb-standalone-no-errors.sh
t/t9831-git-p4-triggers.sh
t/test-lib.sh
unpack-trees.c
unpack-trees.h
worktree.c
wt-status.h

index aebe7c0908f16889ea7dac2b634729c013b6fc99..188bd1c3de13ac2c71765a475f1117ccf954fe56 100644 (file)
@@ -83,7 +83,6 @@
 /git-init-db
 /git-interpret-trailers
 /git-instaweb
-/git-legacy-stash
 /git-log
 /git-ls-files
 /git-ls-remote
diff --git a/Documentation/RelNotes/2.17.4.txt b/Documentation/RelNotes/2.17.4.txt
new file mode 100644 (file)
index 0000000..7d794ca
--- /dev/null
@@ -0,0 +1,16 @@
+Git v2.17.4 Release Notes
+=========================
+
+This release is to address the security issue: CVE-2020-5260
+
+Fixes since v2.17.3
+-------------------
+
+ * With a crafted URL that contains a newline in it, the credential
+   helper machinery can be fooled to give credential information for
+   a wrong host.  The attack has been made impossible by forbidding
+   a newline character in any value passed via the credential
+   protocol.
+
+Credit for finding the vulnerability goes to Felix Wilhelm of Google
+Project Zero.
diff --git a/Documentation/RelNotes/2.17.5.txt b/Documentation/RelNotes/2.17.5.txt
new file mode 100644 (file)
index 0000000..2abb821
--- /dev/null
@@ -0,0 +1,22 @@
+Git v2.17.5 Release Notes
+=========================
+
+This release is to address a security issue: CVE-2020-11008
+
+Fixes since v2.17.4
+-------------------
+
+ * With a crafted URL that contains a newline or empty host, or lacks
+   a scheme, the credential helper machinery can be fooled into
+   providing credential information that is not appropriate for the
+   protocol in use and host being contacted.
+
+   Unlike the vulnerability CVE-2020-5260 fixed in v2.17.4, the
+   credentials are not for a host of the attacker's choosing; instead,
+   they are for some unspecified host (based on how the configured
+   credential helper handles an absent "host" parameter).
+
+   The attack has been made impossible by refusing to work with
+   under-specified credential patterns.
+
+Credit for finding the vulnerability goes to Carlo Arenas.
diff --git a/Documentation/RelNotes/2.18.3.txt b/Documentation/RelNotes/2.18.3.txt
new file mode 100644 (file)
index 0000000..25143f0
--- /dev/null
@@ -0,0 +1,5 @@
+Git v2.18.3 Release Notes
+=========================
+
+This release merges the security fix that appears in v2.17.4; see
+the release notes for that version for details.
diff --git a/Documentation/RelNotes/2.18.4.txt b/Documentation/RelNotes/2.18.4.txt
new file mode 100644 (file)
index 0000000..e8ef858
--- /dev/null
@@ -0,0 +1,5 @@
+Git v2.18.4 Release Notes
+=========================
+
+This release merges the security fix that appears in v2.17.5; see
+the release notes for that version for details.
diff --git a/Documentation/RelNotes/2.19.4.txt b/Documentation/RelNotes/2.19.4.txt
new file mode 100644 (file)
index 0000000..35d0ae5
--- /dev/null
@@ -0,0 +1,5 @@
+Git v2.19.4 Release Notes
+=========================
+
+This release merges the security fix that appears in v2.17.4; see
+the release notes for that version for details.
diff --git a/Documentation/RelNotes/2.19.5.txt b/Documentation/RelNotes/2.19.5.txt
new file mode 100644 (file)
index 0000000..18a4dcb
--- /dev/null
@@ -0,0 +1,5 @@
+Git v2.19.5 Release Notes
+=========================
+
+This release merges the security fix that appears in v2.17.5; see
+the release notes for that version for details.
diff --git a/Documentation/RelNotes/2.20.3.txt b/Documentation/RelNotes/2.20.3.txt
new file mode 100644 (file)
index 0000000..f6eccd1
--- /dev/null
@@ -0,0 +1,5 @@
+Git v2.20.3 Release Notes
+=========================
+
+This release merges the security fix that appears in v2.17.4; see
+the release notes for that version for details.
diff --git a/Documentation/RelNotes/2.20.4.txt b/Documentation/RelNotes/2.20.4.txt
new file mode 100644 (file)
index 0000000..5a9e24e
--- /dev/null
@@ -0,0 +1,5 @@
+Git v2.20.4 Release Notes
+=========================
+
+This release merges the security fix that appears in v2.17.5; see
+the release notes for that version for details.
diff --git a/Documentation/RelNotes/2.21.2.txt b/Documentation/RelNotes/2.21.2.txt
new file mode 100644 (file)
index 0000000..a0fb83b
--- /dev/null
@@ -0,0 +1,5 @@
+Git v2.21.2 Release Notes
+=========================
+
+This release merges the security fix that appears in v2.17.4; see
+the release notes for that version for details.
diff --git a/Documentation/RelNotes/2.21.3.txt b/Documentation/RelNotes/2.21.3.txt
new file mode 100644 (file)
index 0000000..2ca0aa5
--- /dev/null
@@ -0,0 +1,5 @@
+Git v2.21.3 Release Notes
+=========================
+
+This release merges the security fix that appears in v2.17.5; see
+the release notes for that version for details.
diff --git a/Documentation/RelNotes/2.22.3.txt b/Documentation/RelNotes/2.22.3.txt
new file mode 100644 (file)
index 0000000..57296f6
--- /dev/null
@@ -0,0 +1,5 @@
+Git v2.22.3 Release Notes
+=========================
+
+This release merges the security fix that appears in v2.17.4; see
+the release notes for that version for details.
diff --git a/Documentation/RelNotes/2.22.4.txt b/Documentation/RelNotes/2.22.4.txt
new file mode 100644 (file)
index 0000000..8b5f3e3
--- /dev/null
@@ -0,0 +1,5 @@
+Git v2.22.4 Release Notes
+=========================
+
+This release merges the security fix that appears in v2.17.5; see
+the release notes for that version for details.
diff --git a/Documentation/RelNotes/2.23.2.txt b/Documentation/RelNotes/2.23.2.txt
new file mode 100644 (file)
index 0000000..b697cbe
--- /dev/null
@@ -0,0 +1,5 @@
+Git v2.23.2 Release Notes
+=========================
+
+This release merges the security fix that appears in v2.17.4; see
+the release notes for that version for details.
diff --git a/Documentation/RelNotes/2.23.3.txt b/Documentation/RelNotes/2.23.3.txt
new file mode 100644 (file)
index 0000000..2e35490
--- /dev/null
@@ -0,0 +1,5 @@
+Git v2.23.3 Release Notes
+=========================
+
+This release merges the security fix that appears in v2.17.5; see
+the release notes for that version for details.
diff --git a/Documentation/RelNotes/2.24.2.txt b/Documentation/RelNotes/2.24.2.txt
new file mode 100644 (file)
index 0000000..0049f65
--- /dev/null
@@ -0,0 +1,5 @@
+Git v2.24.2 Release Notes
+=========================
+
+This release merges the security fix that appears in v2.17.4; see
+the release notes for that version for details.
diff --git a/Documentation/RelNotes/2.24.3.txt b/Documentation/RelNotes/2.24.3.txt
new file mode 100644 (file)
index 0000000..5302e0f
--- /dev/null
@@ -0,0 +1,5 @@
+Git v2.24.3 Release Notes
+=========================
+
+This release merges the security fix that appears in v2.17.5; see
+the release notes for that version for details.
diff --git a/Documentation/RelNotes/2.25.3.txt b/Documentation/RelNotes/2.25.3.txt
new file mode 100644 (file)
index 0000000..15f7f21
--- /dev/null
@@ -0,0 +1,5 @@
+Git v2.25.3 Release Notes
+=========================
+
+This release merges the security fix that appears in v2.17.4; see
+the release notes for that version for details.
diff --git a/Documentation/RelNotes/2.25.4.txt b/Documentation/RelNotes/2.25.4.txt
new file mode 100644 (file)
index 0000000..0dbb5da
--- /dev/null
@@ -0,0 +1,5 @@
+Git v2.25.4 Release Notes
+=========================
+
+This release merges the security fix that appears in v2.17.5; see
+the release notes for that version for details.
diff --git a/Documentation/RelNotes/2.26.1.txt b/Documentation/RelNotes/2.26.1.txt
new file mode 100644 (file)
index 0000000..1b4ecb3
--- /dev/null
@@ -0,0 +1,5 @@
+Git v2.26.1 Release Notes
+=========================
+
+This release merges the security fix that appears in v2.17.4; see
+the release notes for that version for details.
diff --git a/Documentation/RelNotes/2.26.2.txt b/Documentation/RelNotes/2.26.2.txt
new file mode 100644 (file)
index 0000000..d434d0c
--- /dev/null
@@ -0,0 +1,5 @@
+Git v2.26.2 Release Notes
+=========================
+
+This release merges the security fix that appears in v2.17.5; see
+the release notes for that version for details.
diff --git a/Documentation/RelNotes/2.27.0.txt b/Documentation/RelNotes/2.27.0.txt
new file mode 100644 (file)
index 0000000..7a5c7ff
--- /dev/null
@@ -0,0 +1,94 @@
+Git 2.27 Release Notes
+======================
+
+Updates since v2.26
+-------------------
+
+Backward compatibility notes
+
+ * When "git describe C" finds that commit C is pointed by a signed or
+   annotated tag, which records T as its tagname in the object, the
+   command gives T as its answer.  Even if the user renames or moves
+   such a tag from its natural location in the "refs/tags/" hierarchy,
+   "git describe C" would still give T as the answer, but in such a
+   case "git show T^0" would no longer work as expected.  There may be
+   nothing at "refs/tags/T" or even worse there may be a different tag
+   instead.
+
+   Starting from this version, "git describe" will always use the
+   "long" version, as if the "--long" option were given, when giving
+   its output based on such a misplaced tag to work around the problem.
+
+ * "git pull" issues a warning message until the pull.rebase
+   configuration variable is explicitly given, which some existing
+   users may find annoying---those who prefer not to rebase need to
+   set the variable to false to squelch the warning.
+
+
+UI, Workflows & Features
+
+ * A handful of options to configure SSL when talking to proxies have
+   been added.
+
+ * Smudge/clean conversion filters are now given more information
+   (e.g. the object of the tree-ish in which the blob being converted
+   appears, in addition to its path, which has already been given).
+
+ * When "git describe C" finds an annotated tag with tagname A to be
+   the best name to explain commit C, and the tag is stored in a
+   "wrong" place in the refs/tags hierarchy, e.g. refs/tags/B, the
+   command gave a warning message but used A (not B) to describe C.
+   If C is exactly at the tag, the describe output would be "A", but
+   "git rev-parse A^0" would not be equal as "git rev-parse C^0".  The
+   behavior of the command has been changed to use the "long" form
+   i.e. A-0-gOBJECTNAME, which is correctly interpreted by rev-parse.
+
+ * "git pull" learned to warn when no pull.rebase configuration
+   exists, and neither --[no-]rebase nor --ff-only is given (which
+   would result a merge).
+
+
+Performance, Internal Implementation, Development Support etc.
+
+ * The advise API has been revamped to allow more systematic enumeration of
+   advice knobs in the future.
+
+ * SHA-256 transition continues.
+
+ * The code to interface with GnuPG has been refactored.
+
+ * "git stash" has kept an escape hatch to use the scripted version
+   for a few releases, which got stale.  It has been removed.
+
+
+Fixes since v2.26
+-----------------
+
+ * The real_path() convenience function can easily be misused; with a
+   bit of code refactoring in the callers' side, its use has been
+   eliminated.
+   (merge 49d3c4b481 am/real-path-fix later to maint).
+
+ * Update "git p4" to work with Python 3.
+   (merge 6bb40ed20a yz/p4-py3 later to maint).
+
+ * The mechanism to prevent "git commit" from making an empty commit
+   or amending during an interrupted cherry-pick was broken during the
+   rewrite of "git rebase" in C, which has been corrected.
+   (merge 430b75f720 pw/advise-rebase-skip later to maint).
+
+ * Fix "git checkout --recurse-submodules" of a nested submodule
+   hierarchy.
+   (merge 846f34d351 pb/recurse-submodules-fix later to maint).
+
+ * The "--fork-point" mode of "git rebase" regressed when the command
+   was rewritten in C back in 2.20 era, which has been corrected.
+   (merge f08132f889 at/rebase-fork-point-regression-fix later to maint).
+
+ * Other code cleanup, docfix, build fix, etc.
+   (merge 564956f358 jc/maintain-doc later to maint).
+   (merge 7422b2a0a1 sg/commit-slab-clarify-peek later to maint).
+   (merge 9c688735f6 rs/doc-passthru-fetch-options later to maint).
+   (merge 757c2ba3e2 en/oidset-uninclude-hashmap later to maint).
+   (merge 8312aa7d74 jc/config-tar later to maint).
+   (merge d00a5bdd50 ss/submodule-foreach-cb later to maint).
index 08b13ba72be53bf36e05f7095964c5206d795945..2450589a0edb7f6f22ade39cb75d2fb7a8175b93 100644 (file)
@@ -447,6 +447,8 @@ include::config/submodule.txt[]
 
 include::config/tag.txt[]
 
+include::config/tar.txt[]
+
 include::config/trace2.txt[]
 
 include::config/transfer.txt[]
index 875f8c8a66f36464314d47decf433a82121a395b..4e3a5c0cebc90d1222de23d89db10d6eebfcc4d4 100644 (file)
@@ -12,9 +12,6 @@ feature.experimental::
        setting if you are interested in providing feedback on experimental
        features. The new default values are:
 +
-* `pack.useSparse=true` uses a new algorithm when constructing a pack-file
-which can improve `git push` performance in repos with many files.
-+
 * `fetch.negotiationAlgorithm=skipping` may improve fetch negotiation times by
 skipping more commits at a time, reducing the number of round trips.
 +
index e806033aab86ae7efd52a26a1efd2067178c28dc..3968fbb697aea209affaf7f22594c598bb698ae1 100644 (file)
@@ -29,6 +29,27 @@ http.proxyAuthMethod::
 * `ntlm` - NTLM authentication (compare the --ntlm option of `curl(1)`)
 --
 
+http.proxySSLCert::
+       The pathname of a file that stores a client certificate to use to authenticate
+       with an HTTPS proxy. Can be overridden by the `GIT_PROXY_SSL_CERT` environment
+       variable.
+
+http.proxySSLKey::
+       The pathname of a file that stores a private key to use to authenticate with
+       an HTTPS proxy. Can be overridden by the `GIT_PROXY_SSL_KEY` environment
+       variable.
+
+http.proxySSLCertPasswordProtected::
+       Enable Git's password prompt for the proxy SSL certificate.  Otherwise OpenSSL
+       will prompt the user, possibly many times, if the certificate or private key
+       is encrypted. Can be overriden by the `GIT_PROXY_SSL_CERT_PASSWORD_PROTECTED`
+       environment variable.
+
+http.proxySSLCAInfo::
+       Pathname to the file containing the certificate bundle that should be used to
+       verify the proxy with when using an HTTPS proxy. Can be overriden by the
+       `GIT_PROXY_SSL_CAINFO` environment variable.
+
 http.emptyAuth::
        Attempt authentication without seeking a username or password.  This
        can be used to attempt GSS-Negotiate authentication without specifying
index 0dac5805816ff6e9c133097be5cd9ae14e67da41..837f1b16792fe2200afe011cc1dc0868b6590fe6 100644 (file)
@@ -119,8 +119,8 @@ pack.useSparse::
        objects. This can have significant performance benefits when
        computing a pack to send a small change. However, it is possible
        that extra objects are added to the pack-file if the included
-       commits contain certain types of direct renames. Default is `false`
-       unless `feature.experimental` is enabled.
+       commits contain certain types of direct renames. Default is
+       `true`.
 
 pack.writeBitmaps (deprecated)::
        This is a deprecated synonym for `repack.writeBitmaps`.
index abc7ef4a3a5c64f634dbe0a95bbef01d65151321..00eb35434e883d917d895296e4b1bfd726bac29b 100644 (file)
@@ -1,17 +1,9 @@
 stash.useBuiltin::
-       Set to `false` to use the legacy shell script implementation of
-       linkgit:git-stash[1]. Is `true` by default, which means use
-       the built-in rewrite of it in C.
-+
-The C rewrite is first included with Git version 2.22 (and Git for Windows
-version 2.19). This option serves as an escape hatch to re-enable the
-legacy version in case any bugs are found in the rewrite. This option and
-the shell script version of linkgit:git-stash[1] will be removed in some
-future release.
-+
-If you find some reason to set this option to `false`, other than
-one-off testing, you should report the behavior difference as a bug in
-Git (see https://git-scm.com/community for details).
+       Unused configuration variable.  Used in Git versions 2.22 to
+       2.26 as an escape hatch to enable the legacy shellscript
+       implementation of stash.  Now the built-in rewrite of it in C
+       is always used. Setting this will emit a warning, to alert any
+       remaining users that setting this now does nothing.
 
 stash.showPatch::
        If this is set to true, the `git stash show` command without an
index 6d9110d84ce74f4de5782e9fcd5531719ed4627a..5062a057ffc6f59ed59fd87cdd3bd2ee98560b16 100644 (file)
@@ -15,10 +15,3 @@ tag.gpgSign::
        convenient to use an agent to avoid typing your gpg passphrase
        several times. Note that this option doesn't affect tag signing
        behavior enabled by "-u <keyid>" or "--local-user=<keyid>" options.
-
-tar.umask::
-       This variable can be used to restrict the permission bits of
-       tar archive entries.  The default is 0002, which turns off the
-       world write bit.  The special value "user" indicates that the
-       archiving user's umask will be used instead.  See umask(2) and
-       linkgit:git-archive[1].
diff --git a/Documentation/config/tar.txt b/Documentation/config/tar.txt
new file mode 100644 (file)
index 0000000..de8ff48
--- /dev/null
@@ -0,0 +1,6 @@
+tar.umask::
+       This variable can be used to restrict the permission bits of
+       tar archive entries.  The default is 0002, which turns off the
+       world write bit.  The special value "user" indicates that the
+       archiving user's umask will be used instead.  See umask(2) and
+       linkgit:git-archive[1].
index a115a1ae0e973dc0375e82cbc1de5eff182aa9a1..00d03ec8c31ab2fd05d58248c5d811167309d780 100644 (file)
@@ -61,10 +61,8 @@ this option multiple times, one for each matching ref name.
 See also the `fetch.negotiationAlgorithm` configuration variable
 documented in linkgit:git-config[1].
 
-ifndef::git-pull[]
 --dry-run::
        Show what would be done, without making any changes.
-endif::git-pull[]
 
 -f::
 --force::
@@ -95,6 +93,7 @@ ifndef::git-pull[]
 --[no-]write-commit-graph::
        Write a commit-graph after fetching. This overrides the config
        setting `fetch.writeCommitGraph`.
+endif::git-pull[]
 
 -p::
 --prune::
@@ -107,6 +106,7 @@ ifndef::git-pull[]
        was cloned with the --mirror option), then they are also
        subject to pruning. Supplying `--prune-tags` is a shorthand for
        providing the tag refspec.
+ifndef::git-pull[]
 +
 See the PRUNING section below for more details.
 
@@ -133,7 +133,6 @@ endif::git-pull[]
        behavior for a remote may be specified with the remote.<name>.tagOpt
        setting. See linkgit:git-config[1].
 
-ifndef::git-pull[]
 --refmap=<refspec>::
        When fetching refs listed on the command line, use the
        specified refspec (can be given more than once) to map the
@@ -154,6 +153,7 @@ ifndef::git-pull[]
        is used (though tags may be pruned anyway if they are also the
        destination of an explicit refspec; see `--prune`).
 
+ifndef::git-pull[]
 --recurse-submodules[=yes|on-demand|no]::
        This option controls if and under what conditions new commits of
        populated submodules should be fetched too. It can be used as a
@@ -164,6 +164,7 @@ ifndef::git-pull[]
        when the superproject retrieves a commit that updates the submodule's
        reference to a commit that isn't already in the local submodule
        clone.
+endif::git-pull[]
 
 -j::
 --jobs=<n>::
@@ -177,9 +178,11 @@ parallel. To control them independently, use the config settings
 Typically, parallel recursive and multi-remote fetches will be faster. By
 default fetches are performed sequentially, not in parallel.
 
+ifndef::git-pull[]
 --no-recurse-submodules::
        Disable recursive fetching of submodules (this has the same effect as
        using the `--recurse-submodules=no` option).
+endif::git-pull[]
 
 --set-upstream::
        If the remote is fetched successfully, pull and add upstream
@@ -188,6 +191,7 @@ default fetches are performed sequentially, not in parallel.
        see `branch.<name>.merge` and `branch.<name>.remote` in
        linkgit:git-config[1].
 
+ifndef::git-pull[]
 --submodule-prefix=<path>::
        Prepend <path> to paths printed in informative messages
        such as "Fetching submodule foo".  This option is used
index bf24f1813adc3dd9173dfaa9fd845ba80a189eb9..08d6045c4a835dd5032c594dfc112b4adf4c6d5e 100644 (file)
@@ -15,7 +15,8 @@ SYNOPSIS
          [--dissociate] [--separate-git-dir <git dir>]
          [--depth <depth>] [--[no-]single-branch] [--no-tags]
          [--recurse-submodules[=<pathspec>]] [--[no-]shallow-submodules]
-         [--[no-]remote-submodules] [--jobs <n>] [--sparse] [--] <repository>
+         [--[no-]remote-submodules] [--jobs <n>] [--sparse]
+         [--filter=<filter>] [--] <repository>
          [<directory>]
 
 DESCRIPTION
@@ -162,6 +163,16 @@ objects from the source repository into a pack in the cloned repository.
        of the repository. The sparse-checkout file can be
        modified to grow the working directory as needed.
 
+--filter=<filter-spec>::
+       Use the partial clone feature and request that the server sends
+       a subset of reachable objects according to a given object filter.
+       When using `--filter`, the supplied `<filter-spec>` is used for
+       the partial clone filter. For example, `--filter=blob:none` will
+       filter out all blobs (file contents) until needed by Git. Also,
+       `--filter=blob:limit=<size>` will filter out all blobs of size
+       at least `<size>`. For more details on filter specifications, see
+       the `--filter` option in linkgit:git-rev-list[1].
+
 --mirror::
        Set up a mirror of the source repository.  This implies `--bare`.
        Compared to `--bare`, `--mirror` not only maps local branches of the
index 7889f95940544a016c5ddf696f0995177f5b36e6..77c6b3d0019de2d37be64f03b6f49fa93c3ce4ad 100644 (file)
@@ -122,6 +122,26 @@ Locations of Marks Files
 Relative and non-relative marks may be combined by interweaving
 --(no-)-relative-marks with the --(import|export)-marks= options.
 
+Submodule Rewriting
+~~~~~~~~~~~~~~~~~~~
+
+--rewrite-submodules-from=<name>:<file>::
+--rewrite-submodules-to=<name>:<file>::
+  Rewrite the object IDs for the submodule specified by <name> from the values
+       used in the from <file> to those used in the to <file>. The from marks should
+       have been created by `git fast-export`, and the to marks should have been
+       created by `git fast-import` when importing that same submodule.
++
+<name> may be any arbitrary string not containing a colon character, but the
+same value must be used with both options when specifying corresponding marks.
+Multiple submodules may be specified with different values for <name>. It is an
+error not to use these options in corresponding pairs.
++
+These options are primarily useful when converting a repository from one hash
+algorithm to another; without them, fast-import will fail if it encounters a
+submodule because it has no way of writing the object ID into the new hash
+algorithm.
+
 Performance and Compression Tuning
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
index 32880aafb0c55ce29f35490fd734587c38095066..adc6adfd380beb88cfc9e95ad9be39d874b4890e 100644 (file)
@@ -10,7 +10,7 @@ SYNOPSIS
 --------
 [verse]
 'git init' [-q | --quiet] [--bare] [--template=<template_directory>]
-         [--separate-git-dir <git dir>]
+         [--separate-git-dir <git dir>] [--object-format=<format]
          [--shared[=<permissions>]] [directory]
 
 
@@ -48,6 +48,11 @@ Only print error and warning messages; all other output will be suppressed.
 Create a bare repository. If `GIT_DIR` environment is not set, it is set to the
 current working directory.
 
+--object-format=<format>::
+
+Specify the given object format (hash algorithm) for the repository.  The valid
+values are 'sha1' and (if enabled) 'sha256'.  'sha1' is the default.
+
 --template=<template_directory>::
 
 Specify the directory from which templates will be used.  (See the "TEMPLATE
index fecdf2600cc9e2c5fe7535231deaab79fc0a7278..eaa2f2a4041f2eed78aea08131b1120fb195aaf7 100644 (file)
@@ -14,7 +14,7 @@ SYNOPSIS
        [--local] [--incremental] [--window=<n>] [--depth=<n>]
        [--revs [--unpacked | --all]] [--keep-pack=<pack-name>]
        [--stdout [--filter=<filter-spec>] | base-name]
-       [--shallow] [--keep-true-parents] [--sparse] < object-list
+       [--shallow] [--keep-true-parents] [--[no-]sparse] < object-list
 
 
 DESCRIPTION
@@ -196,14 +196,16 @@ depth is 4095.
        Add --no-reuse-object if you want to force a uniform compression
        level on all data no matter the source.
 
---sparse::
-       Use the "sparse" algorithm to determine which objects to include in
+--[no-]sparse::
+       Toggle the "sparse" algorithm to determine which objects to include in
        the pack, when combined with the "--revs" option. This algorithm
        only walks trees that appear in paths that introduce new objects.
        This can have significant performance benefits when computing
        a pack to send a small change. However, it is possible that extra
        objects are added to the pack-file if the included commits contain
-       certain types of direct renames.
+       certain types of direct renames. If this option is not included,
+       it defaults to the value of `pack.useSparse`, which is true unless
+       otherwise specified.
 
 --thin::
        Create a "thin" pack by omitting the common objects between a
index b0672bd8065fe0cb61ca9e4563b8932a501fb3d9..9d6769e95ab1d356e6c8b07671ac11446a2acc96 100644 (file)
@@ -493,6 +493,12 @@ double-quotes and respecting backslash escapes. E.g., the value
        details. This variable has lower precedence than other path
        variables such as GIT_INDEX_FILE, GIT_OBJECT_DIRECTORY...
 
+`GIT_DEFAULT_HASH_ALGORITHM`::
+       If this variable is set, the default hash algorithm for new
+       repositories will be set to this value. This value is currently
+       ignored when cloning; the setting of the remote repository
+       is used instead. The default is "sha1".
+
 Git Commits
 ~~~~~~~~~~~
 `GIT_AUTHOR_NAME`::
index ca4378740c6a5cd396c9105362078d16836e1c25..73be8b49f8495a04dbbba0813638abefe4c6b2b0 100644 (file)
@@ -154,15 +154,17 @@ by doing the following:
    - Anything unobvious that is applicable to 'master' (in other
      words, does not depend on anything that is still in 'next'
      and not in 'master') is applied to a new topic branch that
-     is forked from the tip of 'master'.  This includes both
+     is forked from the tip of 'master' (or the last feature release,
+     which is a bit older than 'master').  This includes both
      enhancements and unobvious fixes to 'master'.  A topic
      branch is named as ai/topic where "ai" is two-letter string
      named after author's initial and "topic" is a descriptive name
      of the topic (in other words, "what's the series is about").
 
    - An unobvious fix meant for 'maint' is applied to a new
-     topic branch that is forked from the tip of 'maint'.  The
-     topic is named as ai/maint-topic.
+     topic branch that is forked from the tip of 'maint' (or the
+     oldest and still relevant maintenance branch).  The
+     topic may be named as ai/maint-topic.
 
    - Changes that pertain to an existing topic are applied to
      the branch, but:
@@ -174,24 +176,40 @@ by doing the following:
    - Replacement patches to an existing topic are accepted only
      for commits not in 'next'.
 
-   The above except the "replacement" are all done with:
+   The initial round is done with:
 
      $ git checkout ai/topic ;# or "git checkout -b ai/topic master"
      $ git am -sc3 mailbox
 
-   while patch replacement is often done by:
+   and replacing an existing topic with subsequent round is done with:
 
-     $ git format-patch ai/topic~$n..ai/topic ;# export existing
+     $ git checkout master...ai/topic ;# try to reapply to the same base
+     $ git am -sc3 mailbox
+
+   to prepare the new round on a detached HEAD, and then
+
+     $ git range-diff @{-1}...
+     $ git diff @{-1}
 
-   then replace some parts with the new patch, and reapplying:
+   to double check what changed since the last round, and finally
 
-     $ git checkout ai/topic
-     $ git reset --hard ai/topic~$n
-     $ git am -sc3 -s 000*.txt
+     $ git checkout -B @{-1}
+
+   to conclude (the last step is why a topic already in 'next' is
+   not replaced but updated incrementally).
+
+   Whether it is the initial round or a subsequent round, the topic
+   may not build even in isolation, or may break the build when
+   merged to integration branches due to bugs.  There may already
+   be obvious and trivial improvements suggested on the list.  The
+   maintainer often adds an extra commit, with "SQUASH???" in its
+   title, to fix things up, before publishing the integration
+   branches to make it usable by other developers for testing.
+   These changes are what the maintainer is not 100% committed to
+   (trivial typofixes etc. are often squashed directly into the
+   patches that need fixing, without being applied as a separate
+   "SQUASH???" commit), so that they can be removed easily as needed.
 
-   The full test suite is always run for 'maint' and 'master'
-   after patch application; for topic branches the tests are run
-   as time permits.
 
  - Merge maint to master as needed:
 
@@ -371,6 +389,14 @@ Some observations to be made.
    be included in the next feature release.  Being in the
    'master' branch typically is.
 
+ * Due to the nature of "SQUASH???" fix-ups, if the original author
+   agrees with the suggested changes, it is OK to squash them to
+   appropriate patches in the next round (when the suggested change
+   is small enough, the author should not even bother with
+   "Helped-by").  It is also OK to drop them from the next round
+   when the original author does not agree with the suggestion, but
+   the author is expected to say why somewhere in the discussion.
+
 
 Appendix
 --------
index 5db483f972ccd3d1c981cd2820a47fc8be6b4e94..21f929e7ee5453fc1e8b282a71f0d865db7a7b0b 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v2.26.0
+DEF_VER=v2.26.GIT
 
 LF='
 '
index 9804a0758b2458f0ba3d7130c83d7d22d9570879..ef1ff2228f04a989e9845a19c532a2801ce50614 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -609,7 +609,6 @@ SCRIPT_SH += git-merge-one-file.sh
 SCRIPT_SH += git-merge-resolve.sh
 SCRIPT_SH += git-mergetool.sh
 SCRIPT_SH += git-quiltimport.sh
-SCRIPT_SH += git-legacy-stash.sh
 SCRIPT_SH += git-request-pull.sh
 SCRIPT_SH += git-submodule.sh
 SCRIPT_SH += git-web--browse.sh
@@ -695,6 +694,7 @@ X =
 
 PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS))
 
+TEST_BUILTINS_OBJS += test-advise.o
 TEST_BUILTINS_OBJS += test-chmtime.o
 TEST_BUILTINS_OBJS += test-config.o
 TEST_BUILTINS_OBJS += test-ctype.o
index 370269d8dfc14c9dda695f8bdbc84febe64771a3..f3d8527c2c0ceea3adfdbf077cb158e1f1d06a78 120000 (symlink)
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes/2.26.0.txt
\ No newline at end of file
+Documentation/RelNotes/2.27.0.txt
\ No newline at end of file
index 98579853299427ff906434fe56288a73b9d19b21..6f15a418bb64e202811a05e14819f6ef6a578b8d 100644 (file)
--- a/abspath.c
+++ b/abspath.c
@@ -202,22 +202,6 @@ error_out:
        return retval;
 }
 
-/*
- * Resolve `path` into an absolute, cleaned-up path. The return value
- * comes from a shared buffer.
- */
-const char *real_path(const char *path)
-{
-       static struct strbuf realpath = STRBUF_INIT;
-       return strbuf_realpath(&realpath, path, 1);
-}
-
-const char *real_path_if_valid(const char *path)
-{
-       static struct strbuf realpath = STRBUF_INIT;
-       return strbuf_realpath(&realpath, path, 0);
-}
-
 char *real_pathdup(const char *path, int die_on_error)
 {
        struct strbuf realpath = STRBUF_INIT;
@@ -233,7 +217,7 @@ char *real_pathdup(const char *path, int die_on_error)
 
 /*
  * Use this to get an absolute path from a relative one. If you want
- * to resolve links, you should use real_path.
+ * to resolve links, you should use strbuf_realpath.
  */
 const char *absolute_path(const char *path)
 {
index 97f3f981b4b484a9edfed7cedadc3c01e51ff47e..f0a3d32d20687d1caadc1534aa15cf50543c27c0 100644 (file)
--- a/advice.c
+++ b/advice.c
@@ -29,7 +29,6 @@ int advice_ignored_hook = 1;
 int advice_waiting_for_editor = 1;
 int advice_graft_file_deprecated = 1;
 int advice_checkout_ambiguous_remote_branch_name = 1;
-int advice_nested_tag = 1;
 int advice_submodule_alternate_error_strategy_die = 1;
 int advice_add_ignored_file = 1;
 int advice_add_empty_pathspec = 1;
@@ -82,7 +81,7 @@ static struct {
        { "sequencerInUse", &advice_sequencer_in_use },
        { "implicitIdentity", &advice_implicit_identity },
        { "detachedHead", &advice_detached_head },
-       { "setupStreamFailure", &advice_set_upstream_failure },
+       { "setUpstreamFailure", &advice_set_upstream_failure },
        { "objectNameWarning", &advice_object_name_warning },
        { "amWorkDir", &advice_amworkdir },
        { "rmHints", &advice_rm_hints },
@@ -91,7 +90,6 @@ static struct {
        { "waitingForEditor", &advice_waiting_for_editor },
        { "graftFileDeprecated", &advice_graft_file_deprecated },
        { "checkoutAmbiguousRemoteBranchName", &advice_checkout_ambiguous_remote_branch_name },
-       { "nestedTag", &advice_nested_tag },
        { "submoduleAlternateErrorStrategyDie", &advice_submodule_alternate_error_strategy_die },
        { "addIgnoredFile", &advice_add_ignored_file },
        { "addEmptyPathspec", &advice_add_empty_pathspec },
@@ -100,15 +98,58 @@ static struct {
        { "pushNonFastForward", &advice_push_update_rejected }
 };
 
-void advise(const char *advice, ...)
+static struct {
+       const char *key;
+       int enabled;
+} advice_setting[] = {
+       [ADVICE_ADD_EMBEDDED_REPO]                      = { "addEmbeddedRepo", 1 },
+       [ADVICE_AM_WORK_DIR]                            = { "amWorkDir", 1 },
+       [ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME]  = { "checkoutAmbiguousRemoteBranchName", 1 },
+       [ADVICE_COMMIT_BEFORE_MERGE]                    = { "commitBeforeMerge", 1 },
+       [ADVICE_DETACHED_HEAD]                          = { "detachedHead", 1 },
+       [ADVICE_FETCH_SHOW_FORCED_UPDATES]              = { "fetchShowForcedUpdates", 1 },
+       [ADVICE_GRAFT_FILE_DEPRECATED]                  = { "graftFileDeprecated", 1 },
+       [ADVICE_IGNORED_HOOK]                           = { "ignoredHook", 1 },
+       [ADVICE_IMPLICIT_IDENTITY]                      = { "implicitIdentity", 1 },
+       [ADVICE_NESTED_TAG]                             = { "nestedTag", 1 },
+       [ADVICE_OBJECT_NAME_WARNING]                    = { "objectNameWarning", 1 },
+       [ADVICE_PUSH_ALREADY_EXISTS]                    = { "pushAlreadyExists", 1 },
+       [ADVICE_PUSH_FETCH_FIRST]                       = { "pushFetchFirst", 1 },
+       [ADVICE_PUSH_NEEDS_FORCE]                       = { "pushNeedsForce", 1 },
+
+       /* make this an alias for backward compatibility */
+       [ADVICE_PUSH_UPDATE_REJECTED_ALIAS]             = { "pushNonFastForward", 1 },
+
+       [ADVICE_PUSH_NON_FF_CURRENT]                    = { "pushNonFFCurrent", 1 },
+       [ADVICE_PUSH_NON_FF_MATCHING]                   = { "pushNonFFMatching", 1 },
+       [ADVICE_PUSH_UNQUALIFIED_REF_NAME]              = { "pushUnqualifiedRefName", 1 },
+       [ADVICE_PUSH_UPDATE_REJECTED]                   = { "pushUpdateRejected", 1 },
+       [ADVICE_RESET_QUIET_WARNING]                    = { "resetQuiet", 1 },
+       [ADVICE_RESOLVE_CONFLICT]                       = { "resolveConflict", 1 },
+       [ADVICE_RM_HINTS]                               = { "rmHints", 1 },
+       [ADVICE_SEQUENCER_IN_USE]                       = { "sequencerInUse", 1 },
+       [ADVICE_SET_UPSTREAM_FAILURE]                   = { "setUpstreamFailure", 1 },
+       [ADVICE_STATUS_AHEAD_BEHIND_WARNING]            = { "statusAheadBehindWarning", 1 },
+       [ADVICE_STATUS_HINTS]                           = { "statusHints", 1 },
+       [ADVICE_STATUS_U_OPTION]                        = { "statusUoption", 1 },
+       [ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE] = { "submoduleAlternateErrorStrategyDie", 1 },
+       [ADVICE_WAITING_FOR_EDITOR]                     = { "waitingForEditor", 1 },
+};
+
+static const char turn_off_instructions[] =
+N_("\n"
+   "Disable this message with \"git config advice.%s false\"");
+
+static void vadvise(const char *advice, int display_instructions,
+                   const char *key, va_list params)
 {
        struct strbuf buf = STRBUF_INIT;
-       va_list params;
        const char *cp, *np;
 
-       va_start(params, advice);
        strbuf_vaddf(&buf, advice, params);
-       va_end(params);
+
+       if (display_instructions)
+               strbuf_addf(&buf, turn_off_instructions, key);
 
        for (cp = buf.buf; *cp; cp = np) {
                np = strchrnul(cp, '\n');
@@ -122,6 +163,37 @@ void advise(const char *advice, ...)
        strbuf_release(&buf);
 }
 
+void advise(const char *advice, ...)
+{
+       va_list params;
+       va_start(params, advice);
+       vadvise(advice, 0, "", params);
+       va_end(params);
+}
+
+int advice_enabled(enum advice_type type)
+{
+       switch(type) {
+       case ADVICE_PUSH_UPDATE_REJECTED:
+               return advice_setting[ADVICE_PUSH_UPDATE_REJECTED].enabled &&
+                      advice_setting[ADVICE_PUSH_UPDATE_REJECTED_ALIAS].enabled;
+       default:
+               return advice_setting[type].enabled;
+       }
+}
+
+void advise_if_enabled(enum advice_type type, const char *advice, ...)
+{
+       va_list params;
+
+       if (!advice_enabled(type))
+               return;
+
+       va_start(params, advice);
+       vadvise(advice, 1, advice_setting[type].key, params);
+       va_end(params);
+}
+
 int git_default_advice_config(const char *var, const char *value)
 {
        const char *k, *slot_name;
@@ -148,6 +220,13 @@ int git_default_advice_config(const char *var, const char *value)
                if (strcasecmp(k, advice_config[i].name))
                        continue;
                *advice_config[i].preference = git_config_bool(var, value);
+               break;
+       }
+
+       for (i = 0; i < ARRAY_SIZE(advice_setting); i++) {
+               if (strcasecmp(k, advice_setting[i].key))
+                       continue;
+               advice_setting[i].enabled = git_config_bool(var, value);
                return 0;
        }
 
@@ -158,8 +237,8 @@ void list_config_advices(struct string_list *list, const char *prefix)
 {
        int i;
 
-       for (i = 0; i < ARRAY_SIZE(advice_config); i++)
-               list_config_item(list, prefix, advice_config[i].name);
+       for (i = 0; i < ARRAY_SIZE(advice_setting); i++)
+               list_config_item(list, prefix, advice_setting[i].key);
 }
 
 int error_resolve_conflict(const char *me)
index 0e6e58d9f8f79df4b4f6ae242cf1fb0a7d179d1b..16f2c11642a7e63c2b61e8cdba221f024d1069e5 100644 (file)
--- a/advice.h
+++ b/advice.h
@@ -29,14 +29,64 @@ extern int advice_ignored_hook;
 extern int advice_waiting_for_editor;
 extern int advice_graft_file_deprecated;
 extern int advice_checkout_ambiguous_remote_branch_name;
-extern int advice_nested_tag;
 extern int advice_submodule_alternate_error_strategy_die;
 extern int advice_add_ignored_file;
 extern int advice_add_empty_pathspec;
 
+/*
+ * To add a new advice, you need to:
+ * Define a new advice_type.
+ * Add a new entry to advice_setting array.
+ * Add the new config variable to Documentation/config/advice.txt.
+ * Call advise_if_enabled to print your advice.
+ */
+ enum advice_type {
+       ADVICE_ADD_EMBEDDED_REPO,
+       ADVICE_AM_WORK_DIR,
+       ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME,
+       ADVICE_COMMIT_BEFORE_MERGE,
+       ADVICE_DETACHED_HEAD,
+       ADVICE_FETCH_SHOW_FORCED_UPDATES,
+       ADVICE_GRAFT_FILE_DEPRECATED,
+       ADVICE_IGNORED_HOOK,
+       ADVICE_IMPLICIT_IDENTITY,
+       ADVICE_NESTED_TAG,
+       ADVICE_OBJECT_NAME_WARNING,
+       ADVICE_PUSH_ALREADY_EXISTS,
+       ADVICE_PUSH_FETCH_FIRST,
+       ADVICE_PUSH_NEEDS_FORCE,
+       ADVICE_PUSH_NON_FF_CURRENT,
+       ADVICE_PUSH_NON_FF_MATCHING,
+       ADVICE_PUSH_UNQUALIFIED_REF_NAME,
+       ADVICE_PUSH_UPDATE_REJECTED_ALIAS,
+       ADVICE_PUSH_UPDATE_REJECTED,
+       ADVICE_RESET_QUIET_WARNING,
+       ADVICE_RESOLVE_CONFLICT,
+       ADVICE_RM_HINTS,
+       ADVICE_SEQUENCER_IN_USE,
+       ADVICE_SET_UPSTREAM_FAILURE,
+       ADVICE_STATUS_AHEAD_BEHIND_WARNING,
+       ADVICE_STATUS_HINTS,
+       ADVICE_STATUS_U_OPTION,
+       ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE,
+       ADVICE_WAITING_FOR_EDITOR,
+};
+
 int git_default_advice_config(const char *var, const char *value);
 __attribute__((format (printf, 1, 2)))
 void advise(const char *advice, ...);
+
+/**
+ * Checks if advice type is enabled (can be printed to the user).
+ * Should be called before advise().
+ */
+int advice_enabled(enum advice_type type);
+
+/**
+ * Checks the visibility of the advice before printing.
+ */
+void advise_if_enabled(enum advice_type type, const char *advice, ...);
+
 int error_resolve_conflict(const char *me);
 void NORETURN die_resolve_conflict(const char *me);
 void NORETURN die_conclude_merge(void);
diff --git a/apply.c b/apply.c
index bdc008fae2ad77197a859d64196bf91161cf8d67..144c19aaca80d943e237ffd84f215a49415e0d5a 100644 (file)
--- a/apply.c
+++ b/apply.c
@@ -4349,7 +4349,7 @@ static int try_create_file(struct apply_state *state, const char *path,
        if (fd < 0)
                return 1;
 
-       if (convert_to_working_tree(state->repo->index, path, buf, size, &nbuf)) {
+       if (convert_to_working_tree(state->repo->index, path, buf, size, &nbuf, NULL)) {
                size = nbuf.len;
                buf  = nbuf.buf;
        }
index a8da0fcc4f0cc47e585b3b378109fbd84496ed15..fb39706120cdd6b668fbc10cefad608692587d09 100644 (file)
--- a/archive.c
+++ b/archive.c
@@ -77,6 +77,11 @@ void *object_file_to_archive(const struct archiver_args *args,
 {
        void *buffer;
        const struct commit *commit = args->convert ? args->commit : NULL;
+       struct checkout_metadata meta;
+
+       init_checkout_metadata(&meta, args->refname,
+                              args->commit_oid ? args->commit_oid :
+                              (args->tree ? &args->tree->object.oid : NULL), oid);
 
        path += args->baselen;
        buffer = read_object_file(oid, type, sizep);
@@ -85,7 +90,7 @@ void *object_file_to_archive(const struct archiver_args *args,
                size_t size = 0;
 
                strbuf_attach(&buf, buffer, *sizep, *sizep + 1);
-               convert_to_working_tree(args->repo->index, path, buf.buf, buf.len, &buf);
+               convert_to_working_tree(args->repo->index, path, buf.buf, buf.len, &buf, &meta);
                if (commit)
                        format_subst(commit, buf.buf, buf.len, &buf);
                buffer = strbuf_detach(&buf, &size);
@@ -385,16 +390,17 @@ static void parse_treeish_arg(const char **argv,
        struct tree *tree;
        const struct commit *commit;
        struct object_id oid;
+       char *ref = NULL;
 
        /* Remotes are only allowed to fetch actual refs */
        if (remote && !remote_allow_unreachable) {
-               char *ref = NULL;
                const char *colon = strchrnul(name, ':');
                int refnamelen = colon - name;
 
                if (!dwim_ref(name, refnamelen, &oid, &ref))
                        die(_("no such ref: %.*s"), refnamelen, name);
-               free(ref);
+       } else {
+               dwim_ref(name, strlen(name), &oid, &ref);
        }
 
        if (get_oid(name, &oid))
@@ -427,6 +433,7 @@ static void parse_treeish_arg(const char **argv,
 
                tree = parse_tree_indirect(&tree_oid);
        }
+       ar_args->refname = ref;
        ar_args->tree = tree;
        ar_args->commit_oid = commit_oid;
        ar_args->commit = commit;
index e60e3dd31c79f1c04b7afcae8eb5623238e1234f..3bd96bf6bba7ecaacb6b50e1f02080f95d11f17c 100644 (file)
--- a/archive.h
+++ b/archive.h
@@ -8,6 +8,7 @@ struct repository;
 
 struct archiver_args {
        struct repository *repo;
+       const char *refname;
        const char *base;
        size_t baselen;
        struct tree *tree;
index 272f9fc6d7cb540c89c3dca13969398afd3c1ee1..6ecc8ee6dc0ba0f904b3c48b65bdaae064e0ebcc 100644 (file)
@@ -42,7 +42,10 @@ static int filter_object(const char *path, unsigned mode,
                             oid_to_hex(oid), path);
        if ((type == OBJ_BLOB) && S_ISREG(mode)) {
                struct strbuf strbuf = STRBUF_INIT;
-               if (convert_to_working_tree(&the_index, path, *buf, *size, &strbuf)) {
+               struct checkout_metadata meta;
+
+               init_checkout_metadata(&meta, NULL, NULL, oid);
+               if (convert_to_working_tree(&the_index, path, *buf, *size, &strbuf, &meta)) {
                        free(*buf);
                        *size = strbuf.len;
                        *buf = strbuf_detach(&strbuf, NULL);
index d6773818b80315773078954c9879e7478224e9eb..8bc94d392b83624d0cd538fbbd702dc4ad824f7c 100644 (file)
@@ -88,6 +88,19 @@ struct checkout_opts {
        struct tree *source_tree;
 };
 
+struct branch_info {
+       const char *name; /* The short name used */
+       const char *path; /* The full name of a real branch */
+       struct commit *commit; /* The named commit */
+       char *refname; /* The full name of the ref being checked out. */
+       struct object_id oid; /* The object ID of the commit being checked out. */
+       /*
+        * if not null the branch is detached because it's already
+        * checked out in this checkout
+        */
+       char *checkout;
+};
+
 static int post_checkout_hook(struct commit *old_commit, struct commit *new_commit,
                              int changed)
 {
@@ -337,7 +350,8 @@ static void mark_ce_for_checkout_no_overlay(struct cache_entry *ce,
        }
 }
 
-static int checkout_worktree(const struct checkout_opts *opts)
+static int checkout_worktree(const struct checkout_opts *opts,
+                            const struct branch_info *info)
 {
        struct checkout state = CHECKOUT_INIT;
        int nr_checkouts = 0, nr_unmerged = 0;
@@ -348,6 +362,10 @@ static int checkout_worktree(const struct checkout_opts *opts)
        state.refresh_cache = 1;
        state.istate = &the_index;
 
+       init_checkout_metadata(&state.meta, info->refname,
+                              info->commit ? &info->commit->object.oid : &info->oid,
+                              NULL);
+
        enable_delayed_checkout(&state);
        for (pos = 0; pos < active_nr; pos++) {
                struct cache_entry *ce = active_cache[pos];
@@ -396,7 +414,7 @@ static int checkout_worktree(const struct checkout_opts *opts)
 }
 
 static int checkout_paths(const struct checkout_opts *opts,
-                         const char *revision)
+                         const struct branch_info *new_branch_info)
 {
        int pos;
        static char *ps_matched;
@@ -462,7 +480,7 @@ static int checkout_paths(const struct checkout_opts *opts,
                else
                        BUG("either flag must have been set, worktree=%d, index=%d",
                            opts->checkout_worktree, opts->checkout_index);
-               return run_add_interactive(revision, patch_mode, &opts->pathspec);
+               return run_add_interactive(new_branch_info->name, patch_mode, &opts->pathspec);
        }
 
        repo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR);
@@ -523,7 +541,7 @@ static int checkout_paths(const struct checkout_opts *opts,
 
        /* Now we are committed to check them out */
        if (opts->checkout_worktree)
-               errs |= checkout_worktree(opts);
+               errs |= checkout_worktree(opts, new_branch_info);
        else
                remove_marked_cache_entries(&the_index, 1);
 
@@ -586,7 +604,8 @@ static void describe_detached_head(const char *msg, struct commit *commit)
 }
 
 static int reset_tree(struct tree *tree, const struct checkout_opts *o,
-                     int worktree, int *writeout_error)
+                     int worktree, int *writeout_error,
+                     struct branch_info *info)
 {
        struct unpack_trees_options opts;
        struct tree_desc tree_desc;
@@ -601,6 +620,11 @@ static int reset_tree(struct tree *tree, const struct checkout_opts *o,
        opts.verbose_update = o->show_progress;
        opts.src_index = &the_index;
        opts.dst_index = &the_index;
+       init_checkout_metadata(&opts.meta, info->refname,
+                              info->commit ? &info->commit->object.oid :
+                              is_null_oid(&info->oid) ? &tree->object.oid :
+                              &info->oid,
+                              NULL);
        parse_tree(tree);
        init_tree_desc(&tree_desc, tree->buffer, tree->size);
        switch (unpack_trees(1, &tree_desc, &opts)) {
@@ -620,21 +644,17 @@ static int reset_tree(struct tree *tree, const struct checkout_opts *o,
        }
 }
 
-struct branch_info {
-       const char *name; /* The short name used */
-       const char *path; /* The full name of a real branch */
-       struct commit *commit; /* The named commit */
-       /*
-        * if not null the branch is detached because it's already
-        * checked out in this checkout
-        */
-       char *checkout;
-};
-
 static void setup_branch_path(struct branch_info *branch)
 {
        struct strbuf buf = STRBUF_INIT;
 
+       /*
+        * If this is a ref, resolve it; otherwise, look up the OID for our
+        * expression.  Failure here is okay.
+        */
+       if (!dwim_ref(branch->name, strlen(branch->name), &branch->oid, &branch->refname))
+               repo_get_oid_committish(the_repository, branch->name, &branch->oid);
+
        strbuf_branchname(&buf, branch->name, INTERPRET_BRANCH_LOCAL);
        if (strcmp(buf.buf, branch->name))
                branch->name = xstrdup(buf.buf);
@@ -663,7 +683,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
        } else
                new_tree = get_commit_tree(new_branch_info->commit);
        if (opts->discard_changes) {
-               ret = reset_tree(new_tree, opts, 1, writeout_error);
+               ret = reset_tree(new_tree, opts, 1, writeout_error, new_branch_info);
                if (ret)
                        return ret;
        } else {
@@ -692,6 +712,10 @@ static int merge_working_tree(const struct checkout_opts *opts,
                topts.quiet = opts->merge && old_branch_info->commit;
                topts.verbose_update = opts->show_progress;
                topts.fn = twoway_merge;
+               init_checkout_metadata(&topts.meta, new_branch_info->refname,
+                                      new_branch_info->commit ?
+                                      &new_branch_info->commit->object.oid :
+                                      &new_branch_info->oid, NULL);
                if (opts->overwrite_ignore) {
                        topts.dir = xcalloc(1, sizeof(*topts.dir));
                        topts.dir->flags |= DIR_SHOW_IGNORED;
@@ -762,7 +786,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
 
                        ret = reset_tree(new_tree,
                                         opts, 1,
-                                        writeout_error);
+                                        writeout_error, new_branch_info);
                        if (ret)
                                return ret;
                        o.ancestor = old_branch_info->name;
@@ -782,7 +806,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
                                exit(128);
                        ret = reset_tree(new_tree,
                                         opts, 0,
-                                        writeout_error);
+                                        writeout_error, new_branch_info);
                        strbuf_release(&o.obuf);
                        strbuf_release(&old_commit_shortname);
                        if (ret)
@@ -1710,7 +1734,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 
        UNLEAK(opts);
        if (opts->patch_mode || opts->pathspec.nr)
-               return checkout_paths(opts, new_branch_info.name);
+               return checkout_paths(opts, &new_branch_info);
        else
                return checkout_branch(opts, &new_branch_info);
 }
index 1ad26f4d8c81b3e26bca8c3701162ab0e8120e87..d8b1f413aad28e276b5dbce7ae18f720c2ad5846 100644 (file)
@@ -420,6 +420,7 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
        struct dir_iterator *iter;
        int iter_status;
        unsigned int flags;
+       struct strbuf realpath = STRBUF_INIT;
 
        mkdir_if_missing(dest->buf, 0777);
 
@@ -454,7 +455,8 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
                if (unlink(dest->buf) && errno != ENOENT)
                        die_errno(_("failed to unlink '%s'"), dest->buf);
                if (!option_no_hardlinks) {
-                       if (!link(real_path(src->buf), dest->buf))
+                       strbuf_realpath(&realpath, src->buf, 1);
+                       if (!link(realpath.buf, dest->buf))
                                continue;
                        if (option_local > 0)
                                die_errno(_("failed to create link '%s'"), dest->buf);
@@ -468,6 +470,8 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
                strbuf_setlen(src, src_len);
                die(_("failed to iterate over '%s'"), src->buf);
        }
+
+       strbuf_release(&realpath);
 }
 
 static void clone_local(const char *src_repo, const char *dest_repo)
@@ -780,11 +784,11 @@ static int checkout(int submodule_progress)
        if (!strcmp(head, "HEAD")) {
                if (advice_detached_head)
                        detach_advice(oid_to_hex(&oid));
+               FREE_AND_NULL(head);
        } else {
                if (!starts_with(head, "refs/heads/"))
                        die(_("HEAD not found below refs/heads!"));
        }
-       free(head);
 
        /* We need to be in the new work tree for the checkout */
        setup_work_tree();
@@ -799,6 +803,7 @@ static int checkout(int submodule_progress)
        opts.verbose_update = (option_verbosity >= 0);
        opts.src_index = &the_index;
        opts.dst_index = &the_index;
+       init_checkout_metadata(&opts.meta, head, &oid, NULL);
 
        tree = parse_tree_indirect(&oid);
        parse_tree(tree);
@@ -806,6 +811,8 @@ static int checkout(int submodule_progress)
        if (unpack_trees(1, &t, &opts) < 0)
                die(_("unable to checkout working tree"));
 
+       free(head);
+
        if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
                die(_("unable to write new index file"));
 
@@ -1102,7 +1109,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                }
        }
 
-       init_db(git_dir, real_git_dir, option_template, INIT_DB_QUIET);
+       init_db(git_dir, real_git_dir, option_template, GIT_HASH_UNKNOWN, INIT_DB_QUIET);
 
        if (real_git_dir)
                git_dir = real_git_dir;
index 4a70b33fb5f1359397742fcf8601c4c1d49109fe..d1ab6625f632031787584e3fa792015a137ba8bd 100644 (file)
@@ -39,14 +39,17 @@ static struct object_directory *find_odb(struct repository *r,
 {
        struct object_directory *odb;
        char *obj_dir_real = real_pathdup(obj_dir, 1);
+       struct strbuf odb_path_real = STRBUF_INIT;
 
        prepare_alt_odb(r);
        for (odb = r->objects->odb; odb; odb = odb->next) {
-               if (!strcmp(obj_dir_real, real_path(odb->path)))
+               strbuf_realpath(&odb_path_real, odb->path, 1);
+               if (!strcmp(obj_dir_real, odb_path_real.buf))
                        break;
        }
 
        free(obj_dir_real);
+       strbuf_release(&odb_path_real);
 
        if (!odb)
                die(_("could not find object directory matching %s"), obj_dir);
index 7ba33a3bec48de4b8a7b6433df1205bde9e3ebfa..d3e7781e658a67d79df781df44a7d716a5094b30 100644 (file)
@@ -59,6 +59,9 @@ N_("The previous cherry-pick is now empty, possibly due to conflict resolution.\
 "    git commit --allow-empty\n"
 "\n");
 
+static const char empty_rebase_pick_advice[] =
+N_("Otherwise, please use 'git rebase --skip'\n");
+
 static const char empty_cherry_pick_advice_single[] =
 N_("Otherwise, please use 'git cherry-pick --skip'\n");
 
@@ -122,7 +125,6 @@ static enum commit_msg_cleanup_mode cleanup_mode;
 static const char *cleanup_arg;
 
 static enum commit_whence whence;
-static int sequencer_in_use;
 static int use_editor = 1, include_status = 1;
 static int have_option_m;
 static struct strbuf message = STRBUF_INIT;
@@ -179,12 +181,7 @@ static void determine_whence(struct wt_status *s)
 {
        if (file_exists(git_path_merge_head(the_repository)))
                whence = FROM_MERGE;
-       else if (file_exists(git_path_cherry_pick_head(the_repository))) {
-               whence = FROM_CHERRY_PICK;
-               if (file_exists(git_path_seq_dir()))
-                       sequencer_in_use = 1;
-       }
-       else
+       else if (!sequencer_determine_whence(the_repository, &whence))
                whence = FROM_COMMIT;
        if (s)
                s->whence = whence;
@@ -477,8 +474,10 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix
        if (whence != FROM_COMMIT) {
                if (whence == FROM_MERGE)
                        die(_("cannot do a partial commit during a merge."));
-               else if (whence == FROM_CHERRY_PICK)
+               else if (is_from_cherry_pick(whence))
                        die(_("cannot do a partial commit during a cherry-pick."));
+               else if (is_from_rebase(whence))
+                       die(_("cannot do a partial commit during a rebase."));
        }
 
        if (list_paths(&partial, !current_head ? NULL : "HEAD", &pathspec))
@@ -795,7 +794,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
         */
        else if (whence == FROM_MERGE)
                hook_arg1 = "merge";
-       else if (whence == FROM_CHERRY_PICK) {
+       else if (is_from_cherry_pick(whence) || whence == FROM_REBASE_PICK) {
                hook_arg1 = "commit";
                hook_arg2 = "CHERRY_PICK_HEAD";
        }
@@ -973,12 +972,15 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
                run_status(stdout, index_file, prefix, 0, s);
                if (amend)
                        fputs(_(empty_amend_advice), stderr);
-               else if (whence == FROM_CHERRY_PICK) {
+               else if (is_from_cherry_pick(whence) ||
+                        whence == FROM_REBASE_PICK) {
                        fputs(_(empty_cherry_pick_advice), stderr);
-                       if (!sequencer_in_use)
+                       if (whence == FROM_CHERRY_PICK_SINGLE)
                                fputs(_(empty_cherry_pick_advice_single), stderr);
-                       else
+                       else if (whence == FROM_CHERRY_PICK_MULTI)
                                fputs(_(empty_cherry_pick_advice_multi), stderr);
+                       else
+                               fputs(_(empty_rebase_pick_advice), stderr);
                }
                return 0;
        }
@@ -1181,8 +1183,10 @@ static int parse_and_validate_options(int argc, const char *argv[],
        if (amend && whence != FROM_COMMIT) {
                if (whence == FROM_MERGE)
                        die(_("You are in the middle of a merge -- cannot amend."));
-               else if (whence == FROM_CHERRY_PICK)
+               else if (is_from_cherry_pick(whence))
                        die(_("You are in the middle of a cherry-pick -- cannot amend."));
+               else if (whence == FROM_REBASE_PICK)
+                       die(_("You are in the middle of a rebase -- cannot amend."));
        }
        if (fixup_message && squash_message)
                die(_("Options --squash and --fixup cannot be used together"));
@@ -1204,7 +1208,8 @@ static int parse_and_validate_options(int argc, const char *argv[],
                use_message = edit_message;
        if (amend && !use_message && !fixup_message)
                use_message = "HEAD";
-       if (!use_message && whence != FROM_CHERRY_PICK && renew_authorship)
+       if (!use_message && !is_from_cherry_pick(whence) &&
+           !is_from_rebase(whence) && renew_authorship)
                die(_("--reset-author can be used only with -C, -c or --amend."));
        if (use_message) {
                use_message_buffer = read_commit_message(use_message);
@@ -1213,7 +1218,8 @@ static int parse_and_validate_options(int argc, const char *argv[],
                        author_message_buffer = use_message_buffer;
                }
        }
-       if (whence == FROM_CHERRY_PICK && !renew_authorship) {
+       if ((is_from_cherry_pick(whence) || whence == FROM_REBASE_PICK) &&
+           !renew_authorship) {
                author_message = "CHERRY_PICK_HEAD";
                author_message_buffer = read_commit_message(author_message);
        }
@@ -1631,8 +1637,10 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
                        reduce_heads_replace(&parents);
        } else {
                if (!reflog_msg)
-                       reflog_msg = (whence == FROM_CHERRY_PICK)
+                       reflog_msg = is_from_cherry_pick(whence)
                                        ? "commit (cherry-pick)"
+                                       : is_from_rebase(whence)
+                                       ? "commit (rebase)"
                                        : "commit";
                commit_list_insert(current_head, &parents);
        }
@@ -1659,7 +1667,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
        }
 
        if (amend) {
-               const char *exclude_gpgsig[2] = { "gpgsig", NULL };
+               const char *exclude_gpgsig[3] = { "gpgsig", "gpgsig-sha256", NULL };
                extra = read_commit_extra_headers(current_head, exclude_gpgsig);
        } else {
                struct commit_extra_header **tail = &extra;
index 420f4c6401b6204bb422325ed0c08d713b2dfcda..21d2cb9e57f4bf5c33f47989128710411c431d5d 100644 (file)
@@ -54,6 +54,7 @@ struct commit_name {
        struct tag *tag;
        unsigned prio:2; /* annotated tag = 2, tag = 1, head = 0 */
        unsigned name_checked:1;
+       unsigned misnamed:1;
        struct object_id oid;
        char *path;
 };
@@ -132,6 +133,7 @@ static void add_to_known_names(const char *path,
                e->tag = tag;
                e->prio = prio;
                e->name_checked = 0;
+               e->misnamed = 0;
                oidcpy(&e->oid, oid);
                free(e->path);
                e->path = xstrdup(path);
@@ -275,10 +277,11 @@ static void append_name(struct commit_name *n, struct strbuf *dst)
                        die(_("annotated tag %s not available"), n->path);
        }
        if (n->tag && !n->name_checked) {
-               if (!n->tag->tag)
-                       die(_("annotated tag %s has no embedded name"), n->path);
-               if (strcmp(n->tag->tag, all ? n->path + 5 : n->path))
-                       warning(_("tag '%s' is really '%s' here"), n->tag->tag, n->path);
+               if (strcmp(n->tag->tag, all ? n->path + 5 : n->path)) {
+                       warning(_("tag '%s' is externally known as '%s'"),
+                               n->path, n->tag->tag);
+                       n->misnamed = 1;
+               }
                n->name_checked = 1;
        }
 
@@ -314,7 +317,7 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst)
                 * Exact match to an existing ref.
                 */
                append_name(n, dst);
-               if (longformat)
+               if (n->misnamed || longformat)
                        append_suffix(0, n->tag ? get_tagged_oid(n->tag) : oid, dst);
                if (suffix)
                        strbuf_addstr(dst, suffix);
@@ -463,7 +466,7 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst)
        }
 
        append_name(all_matches[0].name, dst);
-       if (abbrev)
+       if (all_matches[0].name->misnamed || abbrev)
                append_suffix(all_matches[0].depth, &cmit->object.oid, dst);
        if (suffix)
                strbuf_addstr(dst, suffix);
index 736f666f644c7cf78b816888ba226693464fc331..172dfbd852a9d50aeb93b42183a77e88979c6597 100644 (file)
@@ -494,6 +494,7 @@ static void fmt_merge_msg_sigs(struct strbuf *out)
                enum object_type type;
                unsigned long size, len;
                char *buf = read_object_file(oid, &type, &size);
+               struct signature_check sigc = { 0 };
                struct strbuf sig = STRBUF_INIT;
 
                if (!buf || type != OBJ_TAG)
@@ -502,10 +503,12 @@ static void fmt_merge_msg_sigs(struct strbuf *out)
 
                if (size == len)
                        ; /* merely annotated */
-               else if (verify_signed_buffer(buf, len, buf + len, size - len, &sig, NULL)) {
-                       if (!sig.len)
-                               strbuf_addstr(&sig, "gpg verification failed.\n");
-               }
+               else if (check_signature(buf, len, buf + len, size - len, &sigc) &&
+                       !sigc.gpg_output)
+                       strbuf_addstr(&sig, "gpg verification failed.\n");
+               else
+                       strbuf_addstr(&sig, sigc.gpg_output);
+               signature_check_clear(&sigc);
 
                if (!tag_number++) {
                        fmt_tag_signature(&tagbuf, &sig, buf, len);
index 944ec77fe1032775ff595f0b1eb99a1fb22d059f..0b7222e7188858a7c820e870dce4c2dd0370f443 100644 (file)
@@ -20,6 +20,8 @@
 #define TEST_FILEMODE 1
 #endif
 
+#define GIT_DEFAULT_HASH_ENVIRONMENT "GIT_DEFAULT_HASH"
+
 static int init_is_bare_repository = 0;
 static int init_shared_repository = -1;
 static const char *init_db_template_dir;
@@ -176,13 +178,36 @@ static int needs_work_tree_config(const char *git_dir, const char *work_tree)
        return 1;
 }
 
+void initialize_repository_version(int hash_algo)
+{
+       char repo_version_string[10];
+       int repo_version = GIT_REPO_VERSION;
+
+#ifndef ENABLE_SHA256
+       if (hash_algo != GIT_HASH_SHA1)
+               die(_("The hash algorithm %s is not supported in this build."), hash_algos[hash_algo].name);
+#endif
+
+       if (hash_algo != GIT_HASH_SHA1)
+               repo_version = GIT_REPO_VERSION_READ;
+
+       /* This forces creation of new config file */
+       xsnprintf(repo_version_string, sizeof(repo_version_string),
+                 "%d", repo_version);
+       git_config_set("core.repositoryformatversion", repo_version_string);
+
+       if (hash_algo != GIT_HASH_SHA1)
+               git_config_set("extensions.objectformat",
+                              hash_algos[hash_algo].name);
+}
+
 static int create_default_files(const char *template_path,
-                               const char *original_git_dir)
+                               const char *original_git_dir,
+                               const struct repository_format *fmt)
 {
        struct stat st1;
        struct strbuf buf = STRBUF_INIT;
        char *path;
-       char repo_version_string[10];
        char junk[2];
        int reinit;
        int filemode;
@@ -244,10 +269,7 @@ static int create_default_files(const char *template_path,
                        exit(1);
        }
 
-       /* This forces creation of new config file */
-       xsnprintf(repo_version_string, sizeof(repo_version_string),
-                 "%d", GIT_REPO_VERSION);
-       git_config_set("core.repositoryformatversion", repo_version_string);
+       initialize_repository_version(fmt->hash_algo);
 
        /* Check filemode trustability */
        path = git_path_buf(&buf, "config");
@@ -340,12 +362,33 @@ static void separate_git_dir(const char *git_dir, const char *git_link)
        write_file(git_link, "gitdir: %s", git_dir);
 }
 
+static void validate_hash_algorithm(struct repository_format *repo_fmt, int hash)
+{
+       const char *env = getenv(GIT_DEFAULT_HASH_ENVIRONMENT);
+       /*
+        * If we already have an initialized repo, don't allow the user to
+        * specify a different algorithm, as that could cause corruption.
+        * Otherwise, if the user has specified one on the command line, use it.
+        */
+       if (repo_fmt->version >= 0 && hash != GIT_HASH_UNKNOWN && hash != repo_fmt->hash_algo)
+               die(_("attempt to reinitialize repository with different hash"));
+       else if (hash != GIT_HASH_UNKNOWN)
+               repo_fmt->hash_algo = hash;
+       else if (env) {
+               int env_algo = hash_algo_by_name(env);
+               if (env_algo == GIT_HASH_UNKNOWN)
+                       die(_("unknown hash algorithm '%s'"), env);
+               repo_fmt->hash_algo = env_algo;
+       }
+}
+
 int init_db(const char *git_dir, const char *real_git_dir,
-           const char *template_dir, unsigned int flags)
+           const char *template_dir, int hash, unsigned int flags)
 {
        int reinit;
        int exist_ok = flags & INIT_DB_EXIST_OK;
        char *original_git_dir = real_pathdup(git_dir, 1);
+       struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;
 
        if (real_git_dir) {
                struct stat st;
@@ -356,12 +399,12 @@ int init_db(const char *git_dir, const char *real_git_dir,
                if (!exist_ok && !stat(real_git_dir, &st))
                        die(_("%s already exists"), real_git_dir);
 
-               set_git_dir(real_path(real_git_dir));
+               set_git_dir(real_git_dir, 1);
                git_dir = get_git_dir();
                separate_git_dir(git_dir, original_git_dir);
        }
        else {
-               set_git_dir(real_path(git_dir));
+               set_git_dir(git_dir, 1);
                git_dir = get_git_dir();
        }
        startup_info->have_repository = 1;
@@ -378,9 +421,11 @@ int init_db(const char *git_dir, const char *real_git_dir,
         * config file, so this will not fail.  What we are catching
         * is an attempt to reinitialize new repository with an old tool.
         */
-       check_repository_format();
+       check_repository_format(&repo_fmt);
 
-       reinit = create_default_files(template_dir, original_git_dir);
+       validate_hash_algorithm(&repo_fmt, hash);
+
+       reinit = create_default_files(template_dir, original_git_dir, &repo_fmt);
 
        create_object_directory();
 
@@ -482,6 +527,8 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
        const char *work_tree;
        const char *template_dir = NULL;
        unsigned int flags = 0;
+       const char *object_format = NULL;
+       int hash_algo = GIT_HASH_UNKNOWN;
        const struct option init_db_options[] = {
                OPT_STRING(0, "template", &template_dir, N_("template-directory"),
                                N_("directory from which templates will be used")),
@@ -494,6 +541,8 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
                OPT_BIT('q', "quiet", &flags, N_("be quiet"), INIT_DB_QUIET),
                OPT_STRING(0, "separate-git-dir", &real_git_dir, N_("gitdir"),
                           N_("separate git dir from working tree")),
+               OPT_STRING(0, "object-format", &object_format, N_("hash"),
+                          N_("specify the hash algorithm to use")),
                OPT_END()
        };
 
@@ -546,6 +595,12 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
                free(cwd);
        }
 
+       if (object_format) {
+               hash_algo = hash_algo_by_name(object_format);
+               if (hash_algo == GIT_HASH_UNKNOWN)
+                       die(_("unknown hash algorithm '%s'"), object_format);
+       }
+
        if (init_shared_repository != -1)
                set_shared_repository(init_shared_repository);
 
@@ -597,5 +652,5 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
        UNLEAK(work_tree);
 
        flags |= INIT_DB_EXIST_OK;
-       return init_db(git_dir, real_git_dir, template_dir, flags);
+       return init_db(git_dir, real_git_dir, template_dir, hash_algo, flags);
 }
index e3f8da13b69b8dbe6898bf71ee2b5df206179bd1..6719ac198dc2092ae49829d5946ca00664e34b0e 100644 (file)
@@ -114,26 +114,16 @@ static int handle_is_ancestor(int argc, const char **argv)
 static int handle_fork_point(int argc, const char **argv)
 {
        struct object_id oid;
-       char *refname;
        struct commit *derived, *fork_point;
        const char *commitname;
 
-       switch (dwim_ref(argv[0], strlen(argv[0]), &oid, &refname)) {
-       case 0:
-               die("No such ref: '%s'", argv[0]);
-       case 1:
-               break; /* good */
-       default:
-               die("Ambiguous refname: '%s'", argv[0]);
-       }
-
        commitname = (argc == 2) ? argv[1] : "HEAD";
        if (get_oid(commitname, &oid))
                die("Not a valid object name: '%s'", commitname);
 
        derived = lookup_commit_reference(the_repository, &oid);
 
-       fork_point = get_fork_point(refname, derived);
+       fork_point = get_fork_point(argv[0], derived);
 
        if (!fork_point)
                return 1;
index 02aa6ee4808a96f264a861bc49789341179056be..dc7c58ce3ff77b5a1b8bc3eb372340aee48d457c 100644 (file)
@@ -880,7 +880,7 @@ static void write_reused_pack_one(size_t pos, struct hashfile *out,
                        len = encode_in_pack_object_header(header, sizeof(header),
                                                           OBJ_REF_DELTA, size);
                        hashwrite(out, header, len);
-                       hashwrite(out, base_oid.hash, 20);
+                       hashwrite(out, base_oid.hash, the_hash_algo->rawsz);
                        copy_pack_data(out, reuse_packfile, w_curs, cur, next - cur);
                        return;
                }
@@ -3469,9 +3469,9 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
 
        read_replace_refs = 0;
 
-       sparse = git_env_bool("GIT_TEST_PACK_SPARSE", 0);
+       sparse = git_env_bool("GIT_TEST_PACK_SPARSE", -1);
        prepare_repo_settings(the_repository);
-       if (!sparse && the_repository->settings.pack_use_sparse != -1)
+       if (sparse < 0)
                sparse = the_repository->settings.pack_use_sparse;
 
        reset_pack_idx_option(&pack_idx_opts);
index 3e624d1e008588ed063a7a960026287d34b84965..e42665b6811d3cd48d36ab7d559cd50cd16d4373 100644 (file)
@@ -327,6 +327,22 @@ static enum rebase_type config_get_rebase(void)
        if (!git_config_get_value("pull.rebase", &value))
                return parse_config_rebase("pull.rebase", value, 1);
 
+       if (opt_verbosity >= 0 &&
+           (!opt_ff || strcmp(opt_ff, "--ff-only"))) {
+               warning(_("Pulling without specifying how to reconcile divergent branches is\n"
+                       "discouraged. You can squelch this message by running one of the following\n"
+                       "commands sometime before your next pull:\n"
+                       "\n"
+                       "  git config pull.rebase false  # merge (the default strategy)\n"
+                       "  git config pull.rebase true   # rebase\n"
+                       "  git config pull.ff only       # fast-forward only\n"
+                       "\n"
+                       "You can replace \"git config\" with \"git config --global\" to set a default\n"
+                       "preference for all repositories. You can also pass --rebase, --no-rebase,\n"
+                       "or --ff-only on the command line to override the configured default per\n"
+                       "invocation.\n"));
+       }
+
        return REBASE_FALSE;
 }
 
index bff53d5d167e8501d7f501046ddb35daf0f2cd17..27a07d4e78e63855a80b259f640f87c276c8e2c3 100644 (file)
@@ -868,6 +868,7 @@ static int reset_head(struct object_id *oid, const char *action,
        unpack_tree_opts.fn = reset_hard ? oneway_merge : twoway_merge;
        unpack_tree_opts.update = 1;
        unpack_tree_opts.merge = 1;
+       init_checkout_metadata(&unpack_tree_opts.meta, switch_to_branch, oid, NULL);
        if (!detach_head)
                unpack_tree_opts.reset = 1;
 
index 18228c312ea009e79b1ae179f4a40ad3b4a35574..4c634111bd80f62b948ccb8935e0d1f6d21ab438 100644 (file)
@@ -46,7 +46,7 @@ static inline int is_merge(void)
        return !access(git_path_merge_head(the_repository), F_OK);
 }
 
-static int reset_index(const struct object_id *oid, int reset_type, int quiet)
+static int reset_index(const char *ref, const struct object_id *oid, int reset_type, int quiet)
 {
        int i, nr = 0;
        struct tree_desc desc[2];
@@ -60,6 +60,7 @@ static int reset_index(const struct object_id *oid, int reset_type, int quiet)
        opts.dst_index = &the_index;
        opts.fn = oneway_merge;
        opts.merge = 1;
+       init_checkout_metadata(&opts.meta, ref, oid, NULL);
        if (!quiet)
                opts.verbose_update = 1;
        switch (reset_type) {
@@ -418,11 +419,20 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
                                }
                        }
                } else {
-                       int err = reset_index(&oid, reset_type, quiet);
+                       struct object_id dummy;
+                       char *ref = NULL;
+                       int err;
+
+                       dwim_ref(rev, strlen(rev), &dummy, &ref);
+                       if (ref && !starts_with(ref, "refs/"))
+                               ref = NULL;
+
+                       err = reset_index(ref, &oid, reset_type, quiet);
                        if (reset_type == KEEP && !err)
-                               err = reset_index(&oid, MIXED, quiet);
+                               err = reset_index(ref, &oid, MIXED, quiet);
                        if (err)
                                die(_("Could not reset index file to revision '%s'."), rev);
+                       free(ref);
                }
 
                if (write_locked_index(&the_index, &lock, COMMIT_LOCK))
index 7a00da820355b61c548449c55381577a2232a0e8..06056434ed1f552ba70e1944718cc2fe79e434bc 100644 (file)
@@ -808,9 +808,10 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
                                continue;
                        }
                        if (!strcmp(arg, "--show-superproject-working-tree")) {
-                               const char *superproject = get_superproject_working_tree();
-                               if (superproject)
-                                       puts(superproject);
+                               struct strbuf superproject = STRBUF_INIT;
+                               if (get_superproject_working_tree(&superproject))
+                                       puts(superproject.buf);
+                               strbuf_release(&superproject);
                                continue;
                        }
                        if (!strcmp(arg, "--show-prefix")) {
@@ -857,7 +858,10 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
                                        if (!gitdir && !prefix)
                                                gitdir = ".git";
                                        if (gitdir) {
-                                               puts(real_path(gitdir));
+                                               struct strbuf realpath = STRBUF_INIT;
+                                               strbuf_realpath(&realpath, gitdir, 1);
+                                               puts(realpath.buf);
+                                               strbuf_release(&realpath);
                                                continue;
                                        }
                                }
index 78af6ce56431a5b1a8eac311d8a33d2c702ee8ba..6d586ef06dfd6e243053c3c7cda86b81fd5a3ddb 100644 (file)
@@ -702,6 +702,7 @@ static int list_stash(int argc, const char **argv, const char *prefix)
 
 static int show_stat = 1;
 static int show_patch;
+static int use_legacy_stash;
 
 static int git_stash_config(const char *var, const char *value, void *cb)
 {
@@ -713,7 +714,11 @@ static int git_stash_config(const char *var, const char *value, void *cb)
                show_patch = git_config_bool(var, value);
                return 0;
        }
-       return git_default_config(var, value, cb);
+       if (!strcmp(var, "stash.usebuiltin")) {
+               use_legacy_stash = !git_config_bool(var, value);
+               return 0;
+       }
+       return git_diff_basic_config(var, value, cb);
 }
 
 static int show_stash(int argc, const char **argv, const char *prefix)
@@ -750,7 +755,6 @@ static int show_stash(int argc, const char **argv, const char *prefix)
         * any options.
         */
        if (revision_args.argc == 1) {
-               git_config(git_stash_config, NULL);
                if (show_stat)
                        rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT;
 
@@ -1559,29 +1563,6 @@ static int save_stash(int argc, const char **argv, const char *prefix)
        return ret;
 }
 
-static int use_builtin_stash(void)
-{
-       struct child_process cp = CHILD_PROCESS_INIT;
-       struct strbuf out = STRBUF_INIT;
-       int ret, env = git_env_bool("GIT_TEST_STASH_USE_BUILTIN", -1);
-
-       if (env != -1)
-               return env;
-
-       argv_array_pushl(&cp.args,
-                        "config", "--bool", "stash.usebuiltin", NULL);
-       cp.git_cmd = 1;
-       if (capture_command(&cp, &out, 6)) {
-               strbuf_release(&out);
-               return 1;
-       }
-
-       strbuf_trim(&out);
-       ret = !strcmp("true", out.buf);
-       strbuf_release(&out);
-       return ret;
-}
-
 int cmd_stash(int argc, const char **argv, const char *prefix)
 {
        pid_t pid = getpid();
@@ -1592,21 +1573,12 @@ int cmd_stash(int argc, const char **argv, const char *prefix)
                OPT_END()
        };
 
-       if (!use_builtin_stash()) {
-               const char *path = mkpath("%s/git-legacy-stash",
-                                         git_exec_path());
-
-               if (sane_execvp(path, (char **)argv) < 0)
-                       die_errno(_("could not exec %s"), path);
-               else
-                       BUG("sane_execvp() returned???");
-       }
-
-       prefix = setup_git_directory();
-       trace_repo_setup(prefix);
-       setup_work_tree();
+       git_config(git_stash_config, NULL);
 
-       git_config(git_diff_basic_config, NULL);
+       if (use_legacy_stash ||
+           !git_env_bool("GIT_TEST_STASH_USE_BUILTIN", -1))
+               warning(_("the stash.useBuiltin support has been removed!\n"
+                         "See its entry in 'git help config' for details."));
 
        argc = parse_options(argc, argv, prefix, options, git_stash_usage,
                             PARSE_OPT_KEEP_UNKNOWN | PARSE_OPT_KEEP_DASHDASH);
index 86a608eec1144649c87444dac9c3628b1d332977..1a4b391c882ad1bcdee8340d1594fe8a4bbc6af0 100644 (file)
@@ -444,19 +444,19 @@ static void for_each_listed_submodule(const struct module_list *list,
                fn(list->entries[i], cb_data);
 }
 
-struct cb_foreach {
+struct foreach_cb {
        int argc;
        const char **argv;
        const char *prefix;
        int quiet;
        int recursive;
 };
-#define CB_FOREACH_INIT { 0 }
+#define FOREACH_CB_INIT { 0 }
 
 static void runcommand_in_submodule_cb(const struct cache_entry *list_item,
                                       void *cb_data)
 {
-       struct cb_foreach *info = cb_data;
+       struct foreach_cb *info = cb_data;
        const char *path = list_item->name;
        const struct object_id *ce_oid = &list_item->oid;
 
@@ -557,7 +557,7 @@ cleanup:
 
 static int module_foreach(int argc, const char **argv, const char *prefix)
 {
-       struct cb_foreach info = CB_FOREACH_INIT;
+       struct foreach_cb info = FOREACH_CB_INIT;
        struct pathspec pathspec;
        struct module_list list = MODULE_LIST_INIT;
 
index e0a4c25382846f801c43cd1093ffb0ffa63f975f..cc30d346f5d5baf554965e60350dfb05a4f20b74 100644 (file)
@@ -231,8 +231,9 @@ static void create_tag(const struct object_id *object, const char *object_ref,
        if (type <= OBJ_NONE)
                die(_("bad object type."));
 
-       if (type == OBJ_TAG && advice_nested_tag)
-               advise(_(message_advice_nested_tag), tag, object_ref);
+       if (type == OBJ_TAG)
+               advise_if_enabled(ADVICE_NESTED_TAG, _(message_advice_nested_tag),
+                                 tag, object_ref);
 
        strbuf_addf(&header,
                    "object %s\n"
index 24f22800f38c759d123d7e307de488f0c8ee852f..d99db356684fab9c1f2c53790a95bb2e2723c541 100644 (file)
@@ -258,7 +258,7 @@ static int add_worktree(const char *path, const char *refname,
                        const struct add_opts *opts)
 {
        struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT;
-       struct strbuf sb = STRBUF_INIT;
+       struct strbuf sb = STRBUF_INIT, realpath = STRBUF_INIT;
        const char *name;
        struct child_process cp = CHILD_PROCESS_INIT;
        struct argv_array child_env = ARGV_ARRAY_INIT;
@@ -330,9 +330,11 @@ static int add_worktree(const char *path, const char *refname,
 
        strbuf_reset(&sb);
        strbuf_addf(&sb, "%s/gitdir", sb_repo.buf);
-       write_file(sb.buf, "%s", real_path(sb_git.buf));
+       strbuf_realpath(&realpath, sb_git.buf, 1);
+       write_file(sb.buf, "%s", realpath.buf);
+       strbuf_realpath(&realpath, get_git_common_dir(), 1);
        write_file(sb_git.buf, "gitdir: %s/worktrees/%s",
-                  real_path(get_git_common_dir()), name);
+                  realpath.buf, name);
        /*
         * This is to keep resolve_ref() happy. We need a valid HEAD
         * or is_git_directory() will reject the directory. Any value which
@@ -418,6 +420,7 @@ done:
        strbuf_release(&sb_repo);
        strbuf_release(&sb_git);
        strbuf_release(&sb_name);
+       strbuf_release(&realpath);
        return ret;
 }
 
diff --git a/cache.h b/cache.h
index 37c899b53f7c3d36f1145d33f19d5c8302b911f5..c77b95870a51140d71fdd44b1ba2288c60ed9f46 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -543,7 +543,7 @@ const char *get_git_common_dir(void);
 char *get_object_directory(void);
 char *get_index_file(void);
 char *get_graft_file(struct repository *r);
-void set_git_dir(const char *path);
+void set_git_dir(const char *path, int make_realpath);
 int get_common_dir_noenv(struct strbuf *sb, const char *gitdir);
 int get_common_dir(struct strbuf *sb, const char *gitdir);
 const char *get_git_namespace(void);
@@ -627,7 +627,9 @@ int path_inside_repo(const char *prefix, const char *path);
 #define INIT_DB_EXIST_OK 0x0002
 
 int init_db(const char *git_dir, const char *real_git_dir,
-           const char *template_dir, unsigned int flags);
+           const char *template_dir, int hash_algo,
+           unsigned int flags);
+void initialize_repository_version(int hash_algo);
 
 void sanitize_stdfds(void);
 int daemonize(void);
@@ -1086,8 +1088,10 @@ int verify_repository_format(const struct repository_format *format,
  * and die if it is a version we don't understand. Generally one would
  * set_git_dir() before calling this, and use it only for "are we in a valid
  * repo?".
+ *
+ * If successful and fmt is not NULL, fill fmt with data.
  */
-void check_repository_format(void);
+void check_repository_format(struct repository_format *fmt);
 
 #define MTIME_CHANGED  0x0001
 #define CTIME_CHANGED  0x0002
@@ -1314,8 +1318,6 @@ static inline int is_absolute_path(const char *path)
 int is_directory(const char *);
 char *strbuf_realpath(struct strbuf *resolved, const char *path,
                      int die_on_error);
-const char *real_path(const char *path);
-const char *real_path_if_valid(const char *path);
 char *real_pathdup(const char *path, int die_on_error);
 const char *absolute_path(const char *path);
 char *absolute_pathdup(const char *path);
@@ -1481,6 +1483,9 @@ int set_disambiguate_hint_config(const char *var, const char *value);
 int get_sha1_hex(const char *hex, unsigned char *sha1);
 int get_oid_hex(const char *hex, struct object_id *sha1);
 
+/* Like get_oid_hex, but for an arbitrary hash algorithm. */
+int get_oid_hex_algop(const char *hex, struct object_id *oid, const struct git_hash_algo *algop);
+
 /*
  * Read `len` pairs of hexadecimal digits from `hex` and write the
  * values to `binary` as `len` bytes. Return 0 on success, or -1 if
@@ -1516,6 +1521,20 @@ char *oid_to_hex(const struct object_id *oid);                                           /* same static buffer */
  */
 int parse_oid_hex(const char *hex, struct object_id *oid, const char **end);
 
+/* Like parse_oid_hex, but for an arbitrary hash algorithm. */
+int parse_oid_hex_algop(const char *hex, struct object_id *oid, const char **end,
+                       const struct git_hash_algo *algo);
+
+
+/*
+ * These functions work like get_oid_hex and parse_oid_hex, but they will parse
+ * a hex value for any algorithm. The algorithm is detected based on the length
+ * and the algorithm in use is returned. If this is not a hex object ID in any
+ * algorithm, returns GIT_HASH_UNKNOWN.
+ */
+int get_oid_hex_any(const char *hex, struct object_id *oid);
+int parse_oid_hex_any(const char *hex, struct object_id *oid, const char **end);
+
 /*
  * This reads short-hand syntax that not only evaluates to a commit
  * object name, but also can act as if the end user spelled the name
@@ -1679,6 +1698,7 @@ struct checkout {
        const char *base_dir;
        int base_dir_len;
        struct delayed_checkout *delayed_checkout;
+       struct checkout_metadata meta;
        unsigned force:1,
                 quiet:1,
                 not_new:1,
index a90d0dc0fd2ae30ba59aa7f2262f884f43dc5ee5..c3a8cd2104641e8134f8af04dd4ac6bc4a841209 100755 (executable)
--- a/ci/lib.sh
+++ b/ci/lib.sh
@@ -162,6 +162,9 @@ linux-clang|linux-gcc)
        if [ "$jobname" = linux-gcc ]
        then
                export CC=gcc-8
+               MAKEFLAGS="$MAKEFLAGS PYTHON_PATH=$(which python3)"
+       else
+               MAKEFLAGS="$MAKEFLAGS PYTHON_PATH=$(which python2)"
        fi
 
        export GIT_TEST_HTTPD=true
@@ -182,6 +185,9 @@ osx-clang|osx-gcc)
        if [ "$jobname" = osx-gcc ]
        then
                export CC=gcc-9
+               MAKEFLAGS="$MAKEFLAGS PYTHON_PATH=$(which python3)"
+       else
+               MAKEFLAGS="$MAKEFLAGS PYTHON_PATH=$(which python2)"
        fi
 
        # t9810 occasionally fails on Travis CI OS X
index 69bf0c807c64b79216118f3485cfd5dac3b6a273..05b3f2804e74d5bb6bbcc0c735d7471d32566d88 100644 (file)
  * - int *indegree_peek(struct indegree *, struct commit *);
  *
  *   This function is similar to indegree_at(), but it will return NULL
- *   until a call to indegree_at() was made for the commit.
+ *   if the location to store the data associated with the given commit
+ *   has not been allocated yet.
+ *   Note that the location to store the data might have already been
+ *   allocated even if no indegree_at() call has been made for that commit
+ *   yet; in this case this function returns a pointer to a
+ *   zero-initialized location.
  *
  * - void init_indegree(struct indegree *);
  *   void init_indegree_with_stride(struct indegree *, int);
index a6cfa41a4e315225c08b8e51c5dd982d9f6273d2..c7099daeac3b86a91dea48314da446b9494129e7 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -927,12 +927,22 @@ struct commit *get_fork_point(const char *refname, struct commit *commit)
        struct commit_list *bases;
        int i;
        struct commit *ret = NULL;
+       char *full_refname;
+
+       switch (dwim_ref(refname, strlen(refname), &oid, &full_refname)) {
+       case 0:
+               die("No such ref: '%s'", refname);
+       case 1:
+               break; /* good */
+       default:
+               die("Ambiguous refname: '%s'", refname);
+       }
 
        memset(&revs, 0, sizeof(revs));
        revs.initial = 1;
-       for_each_reflog_ent(refname, collect_one_reflog_ent, &revs);
+       for_each_reflog_ent(full_refname, collect_one_reflog_ent, &revs);
 
-       if (!revs.nr && !get_oid(refname, &oid))
+       if (!revs.nr)
                add_one_commit(&oid, &revs);
 
        for (i = 0; i < revs.nr; i++)
@@ -958,17 +968,26 @@ struct commit *get_fork_point(const char *refname, struct commit *commit)
 
 cleanup_return:
        free_commit_list(bases);
+       free(full_refname);
        return ret;
 }
 
-static const char gpg_sig_header[] = "gpgsig";
-static const int gpg_sig_header_len = sizeof(gpg_sig_header) - 1;
+/*
+ * Indexed by hash algorithm identifier.
+ */
+static const char *gpg_sig_headers[] = {
+       NULL,
+       "gpgsig",
+       "gpgsig-sha256",
+};
 
 static int do_sign_commit(struct strbuf *buf, const char *keyid)
 {
        struct strbuf sig = STRBUF_INIT;
        int inspos, copypos;
        const char *eoh;
+       const char *gpg_sig_header = gpg_sig_headers[hash_algo_by_ptr(the_hash_algo)];
+       int gpg_sig_header_len = strlen(gpg_sig_header);
 
        /* find the end of the header */
        eoh = strstr(buf->buf, "\n\n");
@@ -1010,6 +1029,8 @@ int parse_signed_commit(const struct commit *commit,
        const char *buffer = get_commit_buffer(commit, &size);
        int in_signature, saw_signature = -1;
        const char *line, *tail;
+       const char *gpg_sig_header = gpg_sig_headers[hash_algo_by_ptr(the_hash_algo)];
+       int gpg_sig_header_len = strlen(gpg_sig_header);
 
        line = buffer;
        tail = buffer + size;
@@ -1056,11 +1077,17 @@ int remove_signature(struct strbuf *buf)
 
                if (in_signature && line[0] == ' ')
                        sig_end = next;
-               else if (starts_with(line, gpg_sig_header) &&
-                        line[gpg_sig_header_len] == ' ') {
-                       sig_start = line;
-                       sig_end = next;
-                       in_signature = 1;
+               else if (starts_with(line, "gpgsig")) {
+                       int i;
+                       for (i = 1; i < GIT_HASH_NALGOS; i++) {
+                               const char *p;
+                               if (skip_prefix(line, gpg_sig_headers[i], &p) &&
+                                   *p == ' ') {
+                                       sig_start = line;
+                                       sig_end = next;
+                                       in_signature = 1;
+                               }
+                       }
                } else {
                        if (*line == '\n')
                                /* dump the whole remainder of the buffer */
index 89b218d11a5313f327492795964793bec8670160..cd4a82a9eb4a2b0a4a1d45f0186cd7f858a0ff57 100644 (file)
@@ -16,6 +16,8 @@ DEVELOPER_CFLAGS += -Wstrict-prototypes
 DEVELOPER_CFLAGS += -Wunused
 DEVELOPER_CFLAGS += -Wvla
 
+DEVELOPER_CFLAGS += -DENABLE_SHA256
+
 ifndef COMPILER_FEATURES
 COMPILER_FEATURES := $(shell ./detect-compiler $(CC))
 endif
index 7e9bd1bc622eefa3dad76c8e786767a0ffa3077f..ac52b07b474d6cd7700bd0f394ecbe1345fb018e 100644 (file)
@@ -61,7 +61,11 @@ int check_connected(oid_iterate_fn fn, void *cb_data,
                 * object is a promisor object. Instead, just make sure we
                 * received, in a promisor packfile, the objects pointed to by
                 * each wanted ref.
+                *
+                * Before checking for promisor packs, be sure we have the
+                * latest pack-files loaded into memory.
                 */
+               reprepare_packed_git(the_repository);
                do {
                        struct packed_git *p;
 
index e800d9f5c9cf25e1aae3a0b87ad35f46b2a973c3..d50ce26d5d9f3d0ceb3351339893c9472f6e5195 100755 (executable)
@@ -139,6 +139,8 @@ foreach my $tar_file (@ARGV)
                        print FI "\n";
                }
 
+               next if ($typeflag eq 'g'); # ignore global header
+
                my $path;
                if ($prefix) {
                        $path = "$prefix/$name";
index 5ead3ce678bb746fa82cef9e3b8e8ac407aa0741..5aa87d45e3e5a03b7530fcc36d8a71581250f446 100644 (file)
--- a/convert.c
+++ b/convert.c
@@ -797,6 +797,7 @@ static void handle_filter_error(const struct strbuf *filter_status,
 static int apply_multi_file_filter(const char *path, const char *src, size_t len,
                                   int fd, struct strbuf *dst, const char *cmd,
                                   const unsigned int wanted_capability,
+                                  const struct checkout_metadata *meta,
                                   struct delayed_checkout *dco)
 {
        int err;
@@ -855,6 +856,24 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t len
        if (err)
                goto done;
 
+       if (meta && meta->refname) {
+               err = packet_write_fmt_gently(process->in, "ref=%s\n", meta->refname);
+               if (err)
+                       goto done;
+       }
+
+       if (meta && !is_null_oid(&meta->treeish)) {
+               err = packet_write_fmt_gently(process->in, "treeish=%s\n", oid_to_hex(&meta->treeish));
+               if (err)
+                       goto done;
+       }
+
+       if (meta && !is_null_oid(&meta->blob)) {
+               err = packet_write_fmt_gently(process->in, "blob=%s\n", oid_to_hex(&meta->blob));
+               if (err)
+                       goto done;
+       }
+
        if ((entry->supported_capabilities & CAP_DELAY) &&
            dco && dco->state == CE_CAN_DELAY) {
                can_delay = 1;
@@ -971,6 +990,7 @@ static struct convert_driver {
 static int apply_filter(const char *path, const char *src, size_t len,
                        int fd, struct strbuf *dst, struct convert_driver *drv,
                        const unsigned int wanted_capability,
+                       const struct checkout_metadata *meta,
                        struct delayed_checkout *dco)
 {
        const char *cmd = NULL;
@@ -990,7 +1010,7 @@ static int apply_filter(const char *path, const char *src, size_t len,
                return apply_single_file_filter(path, src, len, fd, dst, cmd);
        else if (drv->process && *drv->process)
                return apply_multi_file_filter(path, src, len, fd, dst,
-                       drv->process, wanted_capability, dco);
+                       drv->process, wanted_capability, meta, dco);
 
        return 0;
 }
@@ -1368,7 +1388,7 @@ int would_convert_to_git_filter_fd(const struct index_state *istate, const char
        if (!ca.drv->required)
                return 0;
 
-       return apply_filter(path, NULL, 0, -1, NULL, ca.drv, CAP_CLEAN, NULL);
+       return apply_filter(path, NULL, 0, -1, NULL, ca.drv, CAP_CLEAN, NULL, NULL);
 }
 
 const char *get_convert_attr_ascii(const struct index_state *istate, const char *path)
@@ -1406,7 +1426,7 @@ int convert_to_git(const struct index_state *istate,
 
        convert_attrs(istate, &ca, path);
 
-       ret |= apply_filter(path, src, len, -1, dst, ca.drv, CAP_CLEAN, NULL);
+       ret |= apply_filter(path, src, len, -1, dst, ca.drv, CAP_CLEAN, NULL, NULL);
        if (!ret && ca.drv && ca.drv->required)
                die(_("%s: clean filter '%s' failed"), path, ca.drv->name);
 
@@ -1441,7 +1461,7 @@ void convert_to_git_filter_fd(const struct index_state *istate,
        assert(ca.drv);
        assert(ca.drv->clean || ca.drv->process);
 
-       if (!apply_filter(path, NULL, 0, fd, dst, ca.drv, CAP_CLEAN, NULL))
+       if (!apply_filter(path, NULL, 0, fd, dst, ca.drv, CAP_CLEAN, NULL, NULL))
                die(_("%s: clean filter '%s' failed"), path, ca.drv->name);
 
        encode_to_git(path, dst->buf, dst->len, dst, ca.working_tree_encoding, conv_flags);
@@ -1452,7 +1472,9 @@ void convert_to_git_filter_fd(const struct index_state *istate,
 static int convert_to_working_tree_internal(const struct index_state *istate,
                                            const char *path, const char *src,
                                            size_t len, struct strbuf *dst,
-                                           int normalizing, struct delayed_checkout *dco)
+                                           int normalizing,
+                                           const struct checkout_metadata *meta,
+                                           struct delayed_checkout *dco)
 {
        int ret = 0, ret_filter = 0;
        struct conv_attrs ca;
@@ -1484,7 +1506,7 @@ static int convert_to_working_tree_internal(const struct index_state *istate,
        }
 
        ret_filter = apply_filter(
-               path, src, len, -1, dst, ca.drv, CAP_SMUDGE, dco);
+               path, src, len, -1, dst, ca.drv, CAP_SMUDGE, meta, dco);
        if (!ret_filter && ca.drv && ca.drv->required)
                die(_("%s: smudge filter %s failed"), path, ca.drv->name);
 
@@ -1494,22 +1516,24 @@ static int convert_to_working_tree_internal(const struct index_state *istate,
 int async_convert_to_working_tree(const struct index_state *istate,
                                  const char *path, const char *src,
                                  size_t len, struct strbuf *dst,
+                                 const struct checkout_metadata *meta,
                                  void *dco)
 {
-       return convert_to_working_tree_internal(istate, path, src, len, dst, 0, dco);
+       return convert_to_working_tree_internal(istate, path, src, len, dst, 0, meta, dco);
 }
 
 int convert_to_working_tree(const struct index_state *istate,
                            const char *path, const char *src,
-                           size_t len, struct strbuf *dst)
+                           size_t len, struct strbuf *dst,
+                           const struct checkout_metadata *meta)
 {
-       return convert_to_working_tree_internal(istate, path, src, len, dst, 0, NULL);
+       return convert_to_working_tree_internal(istate, path, src, len, dst, 0, meta, NULL);
 }
 
 int renormalize_buffer(const struct index_state *istate, const char *path,
                       const char *src, size_t len, struct strbuf *dst)
 {
-       int ret = convert_to_working_tree_internal(istate, path, src, len, dst, 1, NULL);
+       int ret = convert_to_working_tree_internal(istate, path, src, len, dst, 1, NULL, NULL);
        if (ret) {
                src = dst->buf;
                len = dst->len;
@@ -1982,3 +2006,25 @@ int stream_filter(struct stream_filter *filter,
 {
        return filter->vtbl->filter(filter, input, isize_p, output, osize_p);
 }
+
+void init_checkout_metadata(struct checkout_metadata *meta, const char *refname,
+                           const struct object_id *treeish,
+                           const struct object_id *blob)
+{
+       memset(meta, 0, sizeof(*meta));
+       if (refname)
+               meta->refname = refname;
+       if (treeish)
+               oidcpy(&meta->treeish, treeish);
+       if (blob)
+               oidcpy(&meta->blob, blob);
+}
+
+void clone_checkout_metadata(struct checkout_metadata *dst,
+                            const struct checkout_metadata *src,
+                            const struct object_id *blob)
+{
+       memcpy(dst, src, sizeof(*dst));
+       if (blob)
+               oidcpy(&dst->blob, blob);
+}
index 3710969d43d6a16c6eab2e8302f962aa466ccd9f..e29d1026a6866b63b047b6838727bcb71ea6eccb 100644 (file)
--- a/convert.h
+++ b/convert.h
@@ -4,10 +4,10 @@
 #ifndef CONVERT_H
 #define CONVERT_H
 
+#include "hash.h"
 #include "string-list.h"
 
 struct index_state;
-struct object_id;
 struct strbuf;
 
 #define CONV_EOL_RNDTRP_DIE   (1<<0) /* Die if CRLF to LF to CRLF is different */
@@ -57,6 +57,12 @@ struct delayed_checkout {
        struct string_list paths;
 };
 
+struct checkout_metadata {
+       const char *refname;
+       struct object_id treeish;
+       struct object_id blob;
+};
+
 extern enum eol core_eol;
 extern char *check_roundtrip_encoding;
 const char *get_cached_convert_stats_ascii(const struct index_state *istate,
@@ -71,10 +77,12 @@ int convert_to_git(const struct index_state *istate,
                   struct strbuf *dst, int conv_flags);
 int convert_to_working_tree(const struct index_state *istate,
                            const char *path, const char *src,
-                           size_t len, struct strbuf *dst);
+                           size_t len, struct strbuf *dst,
+                           const struct checkout_metadata *meta);
 int async_convert_to_working_tree(const struct index_state *istate,
                                  const char *path, const char *src,
                                  size_t len, struct strbuf *dst,
+                                 const struct checkout_metadata *meta,
                                  void *dco);
 int async_query_available_blobs(const char *cmd,
                                struct string_list *available_paths);
@@ -94,6 +102,23 @@ void convert_to_git_filter_fd(const struct index_state *istate,
 int would_convert_to_git_filter_fd(const struct index_state *istate,
                                   const char *path);
 
+/*
+ * Initialize the checkout metadata with the given values.  Any argument may be
+ * NULL if it is not applicable.  The treeish should be a commit if that is
+ * available, and a tree otherwise.
+ *
+ * The refname is not copied and must be valid for the lifetime of the struct.
+ * THe object IDs are copied.
+ */
+void init_checkout_metadata(struct checkout_metadata *meta, const char *refname,
+                           const struct object_id *treeish,
+                           const struct object_id *blob);
+
+/* Copy the metadata from src to dst, updating the blob. */
+void clone_checkout_metadata(struct checkout_metadata *dst,
+                            const struct checkout_metadata *src,
+                            const struct object_id *blob);
+
 /*
  * Reset the internal list of attributes used by convert_to_git and
  * convert_to_working_tree.
index 77dfde44e3d136a29f68091878c9f9392af746c0..108d9e183a5ddac0ccafca25d184cb94710ee9f5 100644 (file)
@@ -88,6 +88,11 @@ static void credential_apply_config(struct credential *c)
        struct urlmatch_config config = { STRING_LIST_INIT_DUP };
        struct strbuf url = STRBUF_INIT;
 
+       if (!c->host)
+               die(_("refusing to work with credential missing host field"));
+       if (!c->protocol)
+               die(_("refusing to work with credential missing protocol field"));
+
        if (c->configured)
                return;
 
@@ -222,20 +227,25 @@ int credential_read(struct credential *c, FILE *fp)
        return 0;
 }
 
-static void credential_write_item(FILE *fp, const char *key, const char *value)
+static void credential_write_item(FILE *fp, const char *key, const char *value,
+                                 int required)
 {
+       if (!value && required)
+               BUG("credential value for %s is missing", key);
        if (!value)
                return;
+       if (strchr(value, '\n'))
+               die("credential value for %s contains newline", key);
        fprintf(fp, "%s=%s\n", key, value);
 }
 
 void credential_write(const struct credential *c, FILE *fp)
 {
-       credential_write_item(fp, "protocol", c->protocol);
-       credential_write_item(fp, "host", c->host);
-       credential_write_item(fp, "path", c->path);
-       credential_write_item(fp, "username", c->username);
-       credential_write_item(fp, "password", c->password);
+       credential_write_item(fp, "protocol", c->protocol, 1);
+       credential_write_item(fp, "host", c->host, 1);
+       credential_write_item(fp, "path", c->path, 0);
+       credential_write_item(fp, "username", c->username, 0);
+       credential_write_item(fp, "password", c->password, 0);
 }
 
 static int run_credential_helper(struct credential *c,
@@ -353,7 +363,22 @@ void credential_reject(struct credential *c)
        c->approved = 0;
 }
 
-void credential_from_url(struct credential *c, const char *url)
+static int check_url_component(const char *url, int quiet,
+                              const char *name, const char *value)
+{
+       if (!value)
+               return 0;
+       if (!strchr(value, '\n'))
+               return 0;
+
+       if (!quiet)
+               warning(_("url contains a newline in its %s component: %s"),
+                       name, url);
+       return -1;
+}
+
+int credential_from_url_gently(struct credential *c, const char *url,
+                              int quiet)
 {
        const char *at, *colon, *cp, *slash, *host, *proto_end;
 
@@ -366,8 +391,11 @@ void credential_from_url(struct credential *c, const char *url)
         *   (3) proto://<user>:<pass>@<host>/...
         */
        proto_end = strstr(url, "://");
-       if (!proto_end)
-               return;
+       if (!proto_end || proto_end == url) {
+               if (!quiet)
+                       warning(_("url has no scheme: %s"), url);
+               return -1;
+       }
        cp = proto_end + 3;
        at = strchr(cp, '@');
        colon = strchr(cp, ':');
@@ -392,10 +420,8 @@ void credential_from_url(struct credential *c, const char *url)
                host = at + 1;
        }
 
-       if (proto_end - url > 0)
-               c->protocol = xmemdupz(url, proto_end - url);
-       if (slash - host > 0)
-               c->host = url_decode_mem(host, slash - host);
+       c->protocol = xmemdupz(url, proto_end - url);
+       c->host = url_decode_mem(host, slash - host);
        /* Trim leading and trailing slashes from path */
        while (*slash == '/')
                slash++;
@@ -406,4 +432,19 @@ void credential_from_url(struct credential *c, const char *url)
                while (p > c->path && *p == '/')
                        *p-- = '\0';
        }
+
+       if (check_url_component(url, quiet, "username", c->username) < 0 ||
+           check_url_component(url, quiet, "password", c->password) < 0 ||
+           check_url_component(url, quiet, "protocol", c->protocol) < 0 ||
+           check_url_component(url, quiet, "host", c->host) < 0 ||
+           check_url_component(url, quiet, "path", c->path) < 0)
+               return -1;
+
+       return 0;
+}
+
+void credential_from_url(struct credential *c, const char *url)
+{
+       if (credential_from_url_gently(c, url, 0) < 0)
+               die(_("credential url cannot be parsed: %s"), url);
 }
index fec7815dd0d60bf398bb8d448ab55a6cd6cee64c..d99ec42b2a8c96a298e75b809bf86e47d6d38211 100644 (file)
@@ -173,8 +173,21 @@ void credential_reject(struct credential *);
 int credential_read(struct credential *, FILE *);
 void credential_write(const struct credential *, FILE *);
 
-/* Parse a URL into broken-down credential fields. */
+/*
+ * Parse a url into a credential struct, replacing any existing contents.
+ *
+ * If the url can't be parsed (e.g., a missing "proto://" component), the
+ * resulting credential will be empty but we'll still return success from the
+ * "gently" form.
+ *
+ * If we encounter a component which cannot be represented as a credential
+ * value (e.g., because it contains a newline), the "gently" form will return
+ * an error but leave the broken state in the credential object for further
+ * examination.  The non-gentle form will issue a warning to stderr and return
+ * an empty credential.
+ */
 void credential_from_url(struct credential *, const char *url);
+int credential_from_url_gently(struct credential *, const char *url, int quiet);
 
 int credential_match(const struct credential *have,
                     const struct credential *want);
index 53ce37f7ca42996dbfb4cf80e2127ea43496734d..0f35fa5ee47c5280b73c945a0043dea439917eb5 100644 (file)
@@ -157,7 +157,7 @@ void hashfile_checkpoint(struct hashfile *f, struct hashfile_checkpoint *checkpo
 {
        hashflush(f);
        checkpoint->offset = f->total;
-       checkpoint->ctx = f->ctx;
+       the_hash_algo->clone_fn(&checkpoint->ctx, &f->ctx);
 }
 
 int hashfile_truncate(struct hashfile *f, struct hashfile_checkpoint *checkpoint)
diff --git a/diff.c b/diff.c
index f2cfbf2214a29fc9ddf0e356230df7625d3d1139..1010d806f50dac47927f6f70125647e9bf8276ef 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -4062,6 +4062,9 @@ static void prep_temp_blob(struct index_state *istate,
        struct strbuf tempfile = STRBUF_INIT;
        char *path_dup = xstrdup(path);
        const char *base = basename(path_dup);
+       struct checkout_metadata meta;
+
+       init_checkout_metadata(&meta, NULL, NULL, oid);
 
        /* Generate "XXXXXX_basename.ext" */
        strbuf_addstr(&tempfile, "XXXXXX_");
@@ -4071,7 +4074,7 @@ static void prep_temp_blob(struct index_state *istate,
        if (!temp->tempfile)
                die_errno("unable to create temp-file");
        if (convert_to_working_tree(istate, path,
-                       (const char *)blob, (size_t)size, &buf)) {
+                       (const char *)blob, (size_t)size, &buf, &meta)) {
                blob = buf.buf;
                size = buf.len;
        }
index f079abbf1102686fdb8e64258f456877f976dd03..91989ee8a116aa44502a2c60dfbd02ba4528a34f 100644 (file)
--- a/editor.c
+++ b/editor.c
@@ -54,7 +54,8 @@ static int launch_specified_editor(const char *editor, const char *path,
                return error("Terminal is dumb, but EDITOR unset");
 
        if (strcmp(editor, ":")) {
-               const char *args[] = { editor, real_path(path), NULL };
+               struct strbuf realpath = STRBUF_INIT;
+               const char *args[] = { editor, NULL, NULL };
                struct child_process p = CHILD_PROCESS_INIT;
                int ret, sig;
                int print_waiting_for_editor = advice_waiting_for_editor && isatty(2);
@@ -75,16 +76,22 @@ static int launch_specified_editor(const char *editor, const char *path,
                        fflush(stderr);
                }
 
+               strbuf_realpath(&realpath, path, 1);
+               args[1] = realpath.buf;
+
                p.argv = args;
                p.env = env;
                p.use_shell = 1;
                p.trace2_child_class = "editor";
-               if (start_command(&p) < 0)
+               if (start_command(&p) < 0) {
+                       strbuf_release(&realpath);
                        return error("unable to start editor '%s'", editor);
+               }
 
                sigchain_push(SIGINT, SIG_IGN);
                sigchain_push(SIGQUIT, SIG_IGN);
                ret = finish_command(&p);
+               strbuf_release(&realpath);
                sig = ret - 128;
                sigchain_pop(SIGINT);
                sigchain_pop(SIGQUIT);
diff --git a/entry.c b/entry.c
index 53380bb614c19e82edfb45049f1703b4a3b9c8a3..00b49033668160574c35555293c0c755cbafaeaa 100644 (file)
--- a/entry.c
+++ b/entry.c
@@ -264,6 +264,9 @@ static int write_entry(struct cache_entry *ce,
        size_t newsize = 0;
        struct stat st;
        const struct submodule *sub;
+       struct checkout_metadata meta;
+
+       clone_checkout_metadata(&meta, &state->meta, &ce->oid);
 
        if (ce_mode_s_ifmt == S_IFREG) {
                struct stream_filter *filter = get_stream_filter(state->istate, ce->name,
@@ -315,13 +318,13 @@ static int write_entry(struct cache_entry *ce,
                 */
                if (dco && dco->state != CE_NO_DELAY) {
                        ret = async_convert_to_working_tree(state->istate, ce->name, new_blob,
-                                                           size, &buf, dco);
+                                                           size, &buf, &meta, dco);
                        if (ret && string_list_has_string(&dco->paths, ce->name)) {
                                free(new_blob);
                                goto delayed;
                        }
                } else
-                       ret = convert_to_working_tree(state->istate, ce->name, new_blob, size, &buf);
+                       ret = convert_to_working_tree(state->istate, ce->name, new_blob, size, &buf, &meta);
 
                if (ret) {
                        free(new_blob);
index e72a02d0d577dab4da0f559ed988899baff91fbc..10c9061c432cf9d82e0fbd14fed7c4a9a304c447 100644 (file)
@@ -254,8 +254,11 @@ static int git_work_tree_initialized;
  */
 void set_git_work_tree(const char *new_work_tree)
 {
+       struct strbuf realpath = STRBUF_INIT;
+
        if (git_work_tree_initialized) {
-               new_work_tree = real_path(new_work_tree);
+               strbuf_realpath(&realpath, new_work_tree, 1);
+               new_work_tree = realpath.buf;
                if (strcmp(new_work_tree, the_repository->worktree))
                        die("internal error: work tree has already been set\n"
                            "Current worktree: %s\nNew worktree: %s",
@@ -264,6 +267,8 @@ void set_git_work_tree(const char *new_work_tree)
        }
        git_work_tree_initialized = 1;
        repo_set_worktree(the_repository, new_work_tree);
+
+       strbuf_release(&realpath);
 }
 
 const char *get_git_work_tree(void)
@@ -345,11 +350,20 @@ static void update_relative_gitdir(const char *name,
        free(path);
 }
 
-void set_git_dir(const char *path)
+void set_git_dir(const char *path, int make_realpath)
 {
+       struct strbuf realpath = STRBUF_INIT;
+
+       if (make_realpath) {
+               strbuf_realpath(&realpath, path, 1);
+               path = realpath.buf;
+       }
+
        set_git_dir_1(path);
        if (!is_absolute_path(path))
                chdir_notify_register(NULL, update_relative_gitdir, NULL);
+
+       strbuf_release(&realpath);
 }
 
 const char *get_log_output_encoding(void)
index b8b65a801cc1f902f7131590816459d2d1e0faa0..202dda11a6b206162a7199718c21c43beb698e22 100644 (file)
@@ -18,6 +18,7 @@
 #include "object-store.h"
 #include "mem-pool.h"
 #include "commit-reach.h"
+#include "khash.h"
 
 #define PACK_ID_BITS 16
 #define MAX_PACK_ID ((1<<PACK_ID_BITS)-1)
@@ -53,6 +54,7 @@ struct object_entry_pool {
 
 struct mark_set {
        union {
+               struct object_id *oids[1024];
                struct object_entry *marked[1024];
                struct mark_set *sets[1024];
        } data;
@@ -131,6 +133,9 @@ struct recent_command {
        char *buf;
 };
 
+typedef void (*mark_set_inserter_t)(struct mark_set *s, struct object_id *oid, uintmax_t mark);
+typedef void (*each_mark_fn_t)(uintmax_t mark, void *obj, void *cbp);
+
 /* Configured limits on output */
 static unsigned long max_depth = 50;
 static off_t max_packsize;
@@ -222,6 +227,11 @@ static int allow_unsafe_features;
 /* Signal handling */
 static volatile sig_atomic_t checkpoint_requested;
 
+/* Submodule marks */
+static struct string_list sub_marks_from = STRING_LIST_INIT_DUP;
+static struct string_list sub_marks_to = STRING_LIST_INIT_DUP;
+static kh_oid_map_t *sub_oid_map;
+
 /* Where to write output of cat-blob commands */
 static int cat_blob_fd = STDOUT_FILENO;
 
@@ -230,6 +240,29 @@ static void parse_get_mark(const char *p);
 static void parse_cat_blob(const char *p);
 static void parse_ls(const char *p, struct branch *b);
 
+static void for_each_mark(struct mark_set *m, uintmax_t base, each_mark_fn_t callback, void *p)
+{
+       uintmax_t k;
+       if (m->shift) {
+               for (k = 0; k < 1024; k++) {
+                       if (m->data.sets[k])
+                               for_each_mark(m->data.sets[k], base + (k << m->shift), callback, p);
+               }
+       } else {
+               for (k = 0; k < 1024; k++) {
+                       if (m->data.marked[k])
+                               callback(base + k, m->data.marked[k], p);
+               }
+       }
+}
+
+static void dump_marks_fn(uintmax_t mark, void *object, void *cbp) {
+       struct object_entry *e = object;
+       FILE *f = cbp;
+
+       fprintf(f, ":%" PRIuMAX " %s\n", mark, oid_to_hex(&e->idx.oid));
+}
+
 static void write_branch_report(FILE *rpt, struct branch *b)
 {
        fprintf(rpt, "%s:\n", b->name);
@@ -258,8 +291,6 @@ static void write_branch_report(FILE *rpt, struct branch *b)
        fputc('\n', rpt);
 }
 
-static void dump_marks_helper(FILE *, uintmax_t, struct mark_set *);
-
 static void write_crash_report(const char *err)
 {
        char *loc = git_pathdup("fast_import_crash_%"PRIuMAX, (uintmax_t) getpid());
@@ -338,7 +369,7 @@ static void write_crash_report(const char *err)
        if (export_marks_file)
                fprintf(rpt, "  exported to %s\n", export_marks_file);
        else
-               dump_marks_helper(rpt, 0, marks);
+               for_each_mark(marks, 0, dump_marks_fn, rpt);
 
        fputc('\n', rpt);
        fputs("-------------------\n", rpt);
@@ -493,9 +524,8 @@ static char *pool_strdup(const char *s)
        return r;
 }
 
-static void insert_mark(uintmax_t idnum, struct object_entry *oe)
+static void insert_mark(struct mark_set *s, uintmax_t idnum, struct object_entry *oe)
 {
-       struct mark_set *s = marks;
        while ((idnum >> s->shift) >= 1024) {
                s = mem_pool_calloc(&fi_mem_pool, 1, sizeof(struct mark_set));
                s->shift = marks->shift + 10;
@@ -516,10 +546,9 @@ static void insert_mark(uintmax_t idnum, struct object_entry *oe)
        s->data.marked[idnum] = oe;
 }
 
-static struct object_entry *find_mark(uintmax_t idnum)
+static void *find_mark(struct mark_set *s, uintmax_t idnum)
 {
        uintmax_t orig_idnum = idnum;
-       struct mark_set *s = marks;
        struct object_entry *oe = NULL;
        if ((idnum >> s->shift) < 1024) {
                while (s && s->shift) {
@@ -919,7 +948,7 @@ static int store_object(
 
        e = insert_object(&oid);
        if (mark)
-               insert_mark(mark, e);
+               insert_mark(marks, mark, e);
        if (e->idx.offset) {
                duplicate_count_by_type[type]++;
                return 1;
@@ -1117,7 +1146,7 @@ static void stream_blob(uintmax_t len, struct object_id *oidout, uintmax_t mark)
        e = insert_object(&oid);
 
        if (mark)
-               insert_mark(mark, e);
+               insert_mark(marks, mark, e);
 
        if (e->idx.offset) {
                duplicate_count_by_type[OBJ_BLOB]++;
@@ -1655,26 +1684,6 @@ static void dump_tags(void)
        strbuf_release(&err);
 }
 
-static void dump_marks_helper(FILE *f,
-       uintmax_t base,
-       struct mark_set *m)
-{
-       uintmax_t k;
-       if (m->shift) {
-               for (k = 0; k < 1024; k++) {
-                       if (m->data.sets[k])
-                               dump_marks_helper(f, base + (k << m->shift),
-                                       m->data.sets[k]);
-               }
-       } else {
-               for (k = 0; k < 1024; k++) {
-                       if (m->data.marked[k])
-                               fprintf(f, ":%" PRIuMAX " %s\n", base + k,
-                                       oid_to_hex(&m->data.marked[k]->idx.oid));
-               }
-       }
-}
-
 static void dump_marks(void)
 {
        struct lock_file mark_lock = LOCK_INIT;
@@ -1704,7 +1713,7 @@ static void dump_marks(void)
                return;
        }
 
-       dump_marks_helper(f, 0, marks);
+       for_each_mark(marks, 0, dump_marks_fn, f);
        if (commit_lock_file(&mark_lock)) {
                failure |= error_errno("Unable to write file %s",
                                       export_marks_file);
@@ -1712,21 +1721,38 @@ static void dump_marks(void)
        }
 }
 
-static void read_marks(void)
+static void insert_object_entry(struct mark_set *s, struct object_id *oid, uintmax_t mark)
+{
+       struct object_entry *e;
+       e = find_object(oid);
+       if (!e) {
+               enum object_type type = oid_object_info(the_repository,
+                                                       oid, NULL);
+               if (type < 0)
+                       die("object not found: %s", oid_to_hex(oid));
+               e = insert_object(oid);
+               e->type = type;
+               e->pack_id = MAX_PACK_ID;
+               e->idx.offset = 1; /* just not zero! */
+       }
+       insert_mark(s, mark, e);
+}
+
+static void insert_oid_entry(struct mark_set *s, struct object_id *oid, uintmax_t mark)
+{
+       insert_mark(s, mark, xmemdupz(oid, sizeof(*oid)));
+}
+
+static void read_mark_file(struct mark_set *s, FILE *f, mark_set_inserter_t inserter)
 {
        char line[512];
-       FILE *f = fopen(import_marks_file, "r");
-       if (f)
-               ;
-       else if (import_marks_file_ignore_missing && errno == ENOENT)
-               goto done; /* Marks file does not exist */
-       else
-               die_errno("cannot read '%s'", import_marks_file);
        while (fgets(line, sizeof(line), f)) {
                uintmax_t mark;
                char *end;
                struct object_id oid;
-               struct object_entry *e;
+
+               /* Ensure SHA-1 objects are padded with zeros. */
+               memset(oid.hash, 0, sizeof(oid.hash));
 
                end = strchr(line, '\n');
                if (line[0] != ':' || !end)
@@ -1734,21 +1760,23 @@ static void read_marks(void)
                *end = 0;
                mark = strtoumax(line + 1, &end, 10);
                if (!mark || end == line + 1
-                       || *end != ' ' || get_oid_hex(end + 1, &oid))
+                       || *end != ' '
+                       || get_oid_hex_any(end + 1, &oid) == GIT_HASH_UNKNOWN)
                        die("corrupt mark line: %s", line);
-               e = find_object(&oid);
-               if (!e) {
-                       enum object_type type = oid_object_info(the_repository,
-                                                               &oid, NULL);
-                       if (type < 0)
-                               die("object not found: %s", oid_to_hex(&oid));
-                       e = insert_object(&oid);
-                       e->type = type;
-                       e->pack_id = MAX_PACK_ID;
-                       e->idx.offset = 1; /* just not zero! */
-               }
-               insert_mark(mark, e);
+               inserter(s, &oid, mark);
        }
+}
+
+static void read_marks(void)
+{
+       FILE *f = fopen(import_marks_file, "r");
+       if (f)
+               ;
+       else if (import_marks_file_ignore_missing && errno == ENOENT)
+               goto done; /* Marks file does not exist */
+       else
+               die_errno("cannot read '%s'", import_marks_file);
+       read_mark_file(marks, f, insert_object_entry);
        fclose(f);
 done:
        import_marks_file_done = 1;
@@ -2134,6 +2162,30 @@ static uintmax_t change_note_fanout(struct tree_entry *root,
        return do_change_note_fanout(root, root, hex_oid, 0, path, 0, fanout);
 }
 
+static int parse_mapped_oid_hex(const char *hex, struct object_id *oid, const char **end)
+{
+       int algo;
+       khiter_t it;
+
+       /* Make SHA-1 object IDs have all-zero padding. */
+       memset(oid->hash, 0, sizeof(oid->hash));
+
+       algo = parse_oid_hex_any(hex, oid, end);
+       if (algo == GIT_HASH_UNKNOWN)
+               return -1;
+
+       it = kh_get_oid_map(sub_oid_map, *oid);
+       /* No such object? */
+       if (it == kh_end(sub_oid_map)) {
+               /* If we're using the same algorithm, pass it through. */
+               if (hash_algos[algo].format_id == the_hash_algo->format_id)
+                       return 0;
+               return -1;
+       }
+       oidcpy(oid, kh_value(sub_oid_map, it));
+       return 0;
+}
+
 /*
  * Given a pointer into a string, parse a mark reference:
  *
@@ -2214,13 +2266,13 @@ static void file_change_m(const char *p, struct branch *b)
        }
 
        if (*p == ':') {
-               oe = find_mark(parse_mark_ref_space(&p));
+               oe = find_mark(marks, parse_mark_ref_space(&p));
                oidcpy(&oid, &oe->idx.oid);
        } else if (skip_prefix(p, "inline ", &p)) {
                inline_data = 1;
                oe = NULL; /* not used with inline_data, but makes gcc happy */
        } else {
-               if (parse_oid_hex(p, &oid, &p))
+               if (parse_mapped_oid_hex(p, &oid, &p))
                        die("Invalid dataref: %s", command_buf.buf);
                oe = find_object(&oid);
                if (*p++ != ' ')
@@ -2388,13 +2440,13 @@ static void note_change_n(const char *p, struct branch *b, unsigned char *old_fa
        /* Now parse the notemodify command. */
        /* <dataref> or 'inline' */
        if (*p == ':') {
-               oe = find_mark(parse_mark_ref_space(&p));
+               oe = find_mark(marks, parse_mark_ref_space(&p));
                oidcpy(&oid, &oe->idx.oid);
        } else if (skip_prefix(p, "inline ", &p)) {
                inline_data = 1;
                oe = NULL; /* not used with inline_data, but makes gcc happy */
        } else {
-               if (parse_oid_hex(p, &oid, &p))
+               if (parse_mapped_oid_hex(p, &oid, &p))
                        die("Invalid dataref: %s", command_buf.buf);
                oe = find_object(&oid);
                if (*p++ != ' ')
@@ -2409,7 +2461,7 @@ static void note_change_n(const char *p, struct branch *b, unsigned char *old_fa
                oidcpy(&commit_oid, &s->oid);
        } else if (*p == ':') {
                uintmax_t commit_mark = parse_mark_ref_eol(p);
-               struct object_entry *commit_oe = find_mark(commit_mark);
+               struct object_entry *commit_oe = find_mark(marks, commit_mark);
                if (commit_oe->type != OBJ_COMMIT)
                        die("Mark :%" PRIuMAX " not a commit", commit_mark);
                oidcpy(&commit_oid, &commit_oe->idx.oid);
@@ -2513,7 +2565,7 @@ static int parse_objectish(struct branch *b, const char *objectish)
                oidcpy(&b->branch_tree.versions[1].oid, t);
        } else if (*objectish == ':') {
                uintmax_t idnum = parse_mark_ref_eol(objectish);
-               struct object_entry *oe = find_mark(idnum);
+               struct object_entry *oe = find_mark(marks, idnum);
                if (oe->type != OBJ_COMMIT)
                        die("Mark :%" PRIuMAX " not a commit", idnum);
                if (!oideq(&b->oid, &oe->idx.oid)) {
@@ -2577,7 +2629,7 @@ static struct hash_list *parse_merge(unsigned int *count)
                        oidcpy(&n->oid, &s->oid);
                else if (*from == ':') {
                        uintmax_t idnum = parse_mark_ref_eol(from);
-                       struct object_entry *oe = find_mark(idnum);
+                       struct object_entry *oe = find_mark(marks, idnum);
                        if (oe->type != OBJ_COMMIT)
                                die("Mark :%" PRIuMAX " not a commit", idnum);
                        oidcpy(&n->oid, &oe->idx.oid);
@@ -2751,7 +2803,7 @@ static void parse_new_tag(const char *arg)
        } else if (*from == ':') {
                struct object_entry *oe;
                from_mark = parse_mark_ref_eol(from);
-               oe = find_mark(from_mark);
+               oe = find_mark(marks, from_mark);
                type = oe->type;
                oidcpy(&oid, &oe->idx.oid);
        } else if (!get_oid(from, &oid)) {
@@ -2909,7 +2961,7 @@ static void parse_get_mark(const char *p)
        if (*p != ':')
                die("Not a mark: %s", p);
 
-       oe = find_mark(parse_mark_ref_eol(p));
+       oe = find_mark(marks, parse_mark_ref_eol(p));
        if (!oe)
                die("Unknown mark: %s", command_buf.buf);
 
@@ -2924,12 +2976,12 @@ static void parse_cat_blob(const char *p)
 
        /* cat-blob SP <object> LF */
        if (*p == ':') {
-               oe = find_mark(parse_mark_ref_eol(p));
+               oe = find_mark(marks, parse_mark_ref_eol(p));
                if (!oe)
                        die("Unknown mark: %s", command_buf.buf);
                oidcpy(&oid, &oe->idx.oid);
        } else {
-               if (parse_oid_hex(p, &oid, &p))
+               if (parse_mapped_oid_hex(p, &oid, &p))
                        die("Invalid dataref: %s", command_buf.buf);
                if (*p)
                        die("Garbage after SHA1: %s", command_buf.buf);
@@ -2993,18 +3045,54 @@ static struct object_entry *dereference(struct object_entry *oe,
        return find_object(oid);
 }
 
+static void insert_mapped_mark(uintmax_t mark, void *object, void *cbp)
+{
+       struct object_id *fromoid = object;
+       struct object_id *tooid = find_mark(cbp, mark);
+       int ret;
+       khiter_t it;
+
+       it = kh_put_oid_map(sub_oid_map, *fromoid, &ret);
+       /* We've already seen this object. */
+       if (ret == 0)
+               return;
+       kh_value(sub_oid_map, it) = tooid;
+}
+
+static void build_mark_map_one(struct mark_set *from, struct mark_set *to)
+{
+       for_each_mark(from, 0, insert_mapped_mark, to);
+}
+
+static void build_mark_map(struct string_list *from, struct string_list *to)
+{
+       struct string_list_item *fromp, *top;
+
+       sub_oid_map = kh_init_oid_map();
+
+       for_each_string_list_item(fromp, from) {
+               top = string_list_lookup(to, fromp->string);
+               if (!fromp->util) {
+                       die(_("Missing from marks for submodule '%s'"), fromp->string);
+               } else if (!top || !top->util) {
+                       die(_("Missing to marks for submodule '%s'"), fromp->string);
+               }
+               build_mark_map_one(fromp->util, top->util);
+       }
+}
+
 static struct object_entry *parse_treeish_dataref(const char **p)
 {
        struct object_id oid;
        struct object_entry *e;
 
        if (**p == ':') {       /* <mark> */
-               e = find_mark(parse_mark_ref_space(p));
+               e = find_mark(marks, parse_mark_ref_space(p));
                if (!e)
                        die("Unknown mark: %s", command_buf.buf);
                oidcpy(&oid, &e->idx.oid);
        } else {        /* <sha1> */
-               if (parse_oid_hex(*p, &oid, p))
+               if (parse_mapped_oid_hex(*p, &oid, p))
                        die("Invalid dataref: %s", command_buf.buf);
                e = find_object(&oid);
                if (*(*p)++ != ' ')
@@ -3130,7 +3218,7 @@ static void parse_alias(void)
                die(_("Expected 'to' command, got %s"), command_buf.buf);
        e = find_object(&b.oid);
        assert(e);
-       insert_mark(next_mark, e);
+       insert_mark(marks, next_mark, e);
 }
 
 static char* make_fast_import_path(const char *path)
@@ -3210,6 +3298,26 @@ static void option_export_pack_edges(const char *edges)
        pack_edges = xfopen(edges, "a");
 }
 
+static void option_rewrite_submodules(const char *arg, struct string_list *list)
+{
+       struct mark_set *ms;
+       FILE *fp;
+       char *s = xstrdup(arg);
+       char *f = strchr(s, ':');
+       if (!f)
+               die(_("Expected format name:filename for submodule rewrite option"));
+       *f = '\0';
+       f++;
+       ms = xcalloc(1, sizeof(*ms));
+       string_list_insert(list, s)->util = ms;
+
+       fp = fopen(f, "r");
+       if (!fp)
+               die_errno("cannot read '%s'", f);
+       read_mark_file(ms, fp, insert_oid_entry);
+       fclose(fp);
+}
+
 static int parse_one_option(const char *option)
 {
        if (skip_prefix(option, "max-pack-size=", &option)) {
@@ -3272,6 +3380,11 @@ static int parse_one_feature(const char *feature, int from_stream)
                option_export_marks(arg);
        } else if (!strcmp(feature, "alias")) {
                ; /* Don't die - this feature is supported */
+       } else if (skip_prefix(feature, "rewrite-submodules-to=", &arg)) {
+               option_rewrite_submodules(arg, &sub_marks_to);
+       } else if (skip_prefix(feature, "rewrite-submodules-from=", &arg)) {
+               option_rewrite_submodules(arg, &sub_marks_from);
+       } else if (skip_prefix(feature, "rewrite-submodules-from=", &arg)) {
        } else if (!strcmp(feature, "get-mark")) {
                ; /* Don't die - this feature is supported */
        } else if (!strcmp(feature, "cat-blob")) {
@@ -3377,6 +3490,7 @@ static void parse_argv(void)
        seen_data_command = 1;
        if (import_marks_file)
                read_marks();
+       build_mark_map(&sub_marks_from, &sub_marks_to);
 }
 
 int cmd_main(int argc, const char **argv)
diff --git a/fsck.c b/fsck.c
index 640d813d8430c6835ab1068c0c4512560e91c2b8..73f30773f28acc06f4b9c04dda1e1a54b46c6930 100644 (file)
--- a/fsck.c
+++ b/fsck.c
@@ -9,12 +9,14 @@
 #include "tag.h"
 #include "fsck.h"
 #include "refs.h"
+#include "url.h"
 #include "utf8.h"
 #include "decorate.h"
 #include "oidset.h"
 #include "packfile.h"
 #include "submodule-config.h"
 #include "config.h"
+#include "credential.h"
 #include "help.h"
 
 static struct oidset gitmodules_found = OIDSET_INIT;
@@ -910,6 +912,149 @@ done:
        return ret;
 }
 
+/*
+ * Like builtin/submodule--helper.c's starts_with_dot_slash, but without
+ * relying on the platform-dependent is_dir_sep helper.
+ *
+ * This is for use in checking whether a submodule URL is interpreted as
+ * relative to the current directory on any platform, since \ is a
+ * directory separator on Windows but not on other platforms.
+ */
+static int starts_with_dot_slash(const char *str)
+{
+       return str[0] == '.' && (str[1] == '/' || str[1] == '\\');
+}
+
+/*
+ * Like starts_with_dot_slash, this is a variant of submodule--helper's
+ * helper of the same name with the twist that it accepts backslash as a
+ * directory separator even on non-Windows platforms.
+ */
+static int starts_with_dot_dot_slash(const char *str)
+{
+       return str[0] == '.' && starts_with_dot_slash(str + 1);
+}
+
+static int submodule_url_is_relative(const char *url)
+{
+       return starts_with_dot_slash(url) || starts_with_dot_dot_slash(url);
+}
+
+/*
+ * Count directory components that a relative submodule URL should chop
+ * from the remote_url it is to be resolved against.
+ *
+ * In other words, this counts "../" components at the start of a
+ * submodule URL.
+ *
+ * Returns the number of directory components to chop and writes a
+ * pointer to the next character of url after all leading "./" and
+ * "../" components to out.
+ */
+static int count_leading_dotdots(const char *url, const char **out)
+{
+       int result = 0;
+       while (1) {
+               if (starts_with_dot_dot_slash(url)) {
+                       result++;
+                       url += strlen("../");
+                       continue;
+               }
+               if (starts_with_dot_slash(url)) {
+                       url += strlen("./");
+                       continue;
+               }
+               *out = url;
+               return result;
+       }
+}
+/*
+ * Check whether a transport is implemented by git-remote-curl.
+ *
+ * If it is, returns 1 and writes the URL that would be passed to
+ * git-remote-curl to the "out" parameter.
+ *
+ * Otherwise, returns 0 and leaves "out" untouched.
+ *
+ * Examples:
+ *   http::https://example.com/repo.git -> 1, https://example.com/repo.git
+ *   https://example.com/repo.git -> 1, https://example.com/repo.git
+ *   git://example.com/repo.git -> 0
+ *
+ * This is for use in checking for previously exploitable bugs that
+ * required a submodule URL to be passed to git-remote-curl.
+ */
+static int url_to_curl_url(const char *url, const char **out)
+{
+       /*
+        * We don't need to check for case-aliases, "http.exe", and so
+        * on because in the default configuration, is_transport_allowed
+        * prevents URLs with those schemes from being cloned
+        * automatically.
+        */
+       if (skip_prefix(url, "http::", out) ||
+           skip_prefix(url, "https::", out) ||
+           skip_prefix(url, "ftp::", out) ||
+           skip_prefix(url, "ftps::", out))
+               return 1;
+       if (starts_with(url, "http://") ||
+           starts_with(url, "https://") ||
+           starts_with(url, "ftp://") ||
+           starts_with(url, "ftps://")) {
+               *out = url;
+               return 1;
+       }
+       return 0;
+}
+
+static int check_submodule_url(const char *url)
+{
+       const char *curl_url;
+
+       if (looks_like_command_line_option(url))
+               return -1;
+
+       if (submodule_url_is_relative(url)) {
+               char *decoded;
+               const char *next;
+               int has_nl;
+
+               /*
+                * This could be appended to an http URL and url-decoded;
+                * check for malicious characters.
+                */
+               decoded = url_decode(url);
+               has_nl = !!strchr(decoded, '\n');
+
+               free(decoded);
+               if (has_nl)
+                       return -1;
+
+               /*
+                * URLs which escape their root via "../" can overwrite
+                * the host field and previous components, resolving to
+                * URLs like https::example.com/submodule.git and
+                * https:///example.com/submodule.git that were
+                * susceptible to CVE-2020-11008.
+                */
+               if (count_leading_dotdots(url, &next) > 0 &&
+                   (*next == ':' || *next == '/'))
+                       return -1;
+       }
+
+       else if (url_to_curl_url(url, &curl_url)) {
+               struct credential c = CREDENTIAL_INIT;
+               int ret = 0;
+               if (credential_from_url_gently(&c, curl_url, 1) ||
+                   !*c.host)
+                       ret = -1;
+               credential_clear(&c);
+               return ret;
+       }
+
+       return 0;
+}
+
 struct fsck_gitmodules_data {
        const struct object_id *oid;
        struct fsck_options *options;
@@ -935,7 +1080,7 @@ static int fsck_gitmodules_fn(const char *var, const char *value, void *vdata)
                                    "disallowed submodule name: %s",
                                    name);
        if (!strcmp(key, "url") && value &&
-           looks_like_command_line_option(value))
+           check_submodule_url(value) < 0)
                data->ret |= report(data->options,
                                    data->oid, OBJ_BLOB,
                                    FSCK_MSG_GITMODULES_URL,
diff --git a/git-legacy-stash.sh b/git-legacy-stash.sh
deleted file mode 100755 (executable)
index 4d4ebb4..0000000
+++ /dev/null
@@ -1,798 +0,0 @@
-#!/bin/sh
-# Copyright (c) 2007, Nanako Shiraishi
-
-dashless=$(basename "$0" | sed -e 's/-/ /')
-USAGE="list [<options>]
-   or: $dashless show [<stash>]
-   or: $dashless drop [-q|--quiet] [<stash>]
-   or: $dashless ( pop | apply ) [--index] [-q|--quiet] [<stash>]
-   or: $dashless branch <branchname> [<stash>]
-   or: $dashless save [--patch] [-k|--[no-]keep-index] [-q|--quiet]
-                     [-u|--include-untracked] [-a|--all] [<message>]
-   or: $dashless [push [--patch] [-k|--[no-]keep-index] [-q|--quiet]
-                      [-u|--include-untracked] [-a|--all] [-m <message>]
-                      [-- <pathspec>...]]
-   or: $dashless clear"
-
-SUBDIRECTORY_OK=Yes
-OPTIONS_SPEC=
-START_DIR=$(pwd)
-. git-sh-setup
-require_work_tree
-prefix=$(git rev-parse --show-prefix) || exit 1
-cd_to_toplevel
-
-TMP="$GIT_DIR/.git-stash.$$"
-TMPindex=${GIT_INDEX_FILE-"$(git rev-parse --git-path index)"}.stash.$$
-trap 'rm -f "$TMP-"* "$TMPindex"' 0
-
-ref_stash=refs/stash
-
-if git config --get-colorbool color.interactive; then
-       help_color="$(git config --get-color color.interactive.help 'red bold')"
-       reset_color="$(git config --get-color '' reset)"
-else
-       help_color=
-       reset_color=
-fi
-
-no_changes () {
-       git diff-index --quiet --cached HEAD --ignore-submodules -- "$@" &&
-       git diff-files --quiet --ignore-submodules -- "$@" &&
-       (test -z "$untracked" || test -z "$(untracked_files "$@")")
-}
-
-untracked_files () {
-       if test "$1" = "-z"
-       then
-               shift
-               z=-z
-       else
-               z=
-       fi
-       excl_opt=--exclude-standard
-       test "$untracked" = "all" && excl_opt=
-       git ls-files -o $z $excl_opt -- "$@"
-}
-
-prepare_fallback_ident () {
-       if ! git -c user.useconfigonly=yes var GIT_COMMITTER_IDENT >/dev/null 2>&1
-       then
-               GIT_AUTHOR_NAME="git stash"
-               GIT_AUTHOR_EMAIL=git@stash
-               GIT_COMMITTER_NAME="git stash"
-               GIT_COMMITTER_EMAIL=git@stash
-               export GIT_AUTHOR_NAME
-               export GIT_AUTHOR_EMAIL
-               export GIT_COMMITTER_NAME
-               export GIT_COMMITTER_EMAIL
-       fi
-}
-
-clear_stash () {
-       if test $# != 0
-       then
-               die "$(gettext "git stash clear with parameters is unimplemented")"
-       fi
-       if current=$(git rev-parse --verify --quiet $ref_stash)
-       then
-               git update-ref -d $ref_stash $current
-       fi
-}
-
-maybe_quiet () {
-       case "$1" in
-       --keep-stdout)
-               shift
-               if test -n "$GIT_QUIET"
-               then
-                       "$@" 2>/dev/null
-               else
-                       "$@"
-               fi
-               ;;
-       *)
-               if test -n "$GIT_QUIET"
-               then
-                       "$@" >/dev/null 2>&1
-               else
-                       "$@"
-               fi
-               ;;
-       esac
-}
-
-create_stash () {
-
-       prepare_fallback_ident
-
-       stash_msg=
-       untracked=
-       while test $# != 0
-       do
-               case "$1" in
-               -m|--message)
-                       shift
-                       stash_msg=${1?"BUG: create_stash () -m requires an argument"}
-                       ;;
-               -m*)
-                       stash_msg=${1#-m}
-                       ;;
-               --message=*)
-                       stash_msg=${1#--message=}
-                       ;;
-               -u|--include-untracked)
-                       shift
-                       untracked=${1?"BUG: create_stash () -u requires an argument"}
-                       ;;
-               --)
-                       shift
-                       break
-                       ;;
-               esac
-               shift
-       done
-
-       git update-index -q --refresh
-       if maybe_quiet no_changes "$@"
-       then
-               exit 0
-       fi
-
-       # state of the base commit
-       if b_commit=$(maybe_quiet --keep-stdout git rev-parse --verify HEAD)
-       then
-               head=$(git rev-list --oneline -n 1 HEAD --)
-       elif test -n "$GIT_QUIET"
-       then
-               exit 1
-       else
-               die "$(gettext "You do not have the initial commit yet")"
-       fi
-
-       if branch=$(git symbolic-ref -q HEAD)
-       then
-               branch=${branch#refs/heads/}
-       else
-               branch='(no branch)'
-       fi
-       msg=$(printf '%s: %s' "$branch" "$head")
-
-       # state of the index
-       i_tree=$(git write-tree) &&
-       i_commit=$(printf 'index on %s\n' "$msg" |
-               git commit-tree $i_tree -p $b_commit) ||
-               die "$(gettext "Cannot save the current index state")"
-
-       if test -n "$untracked"
-       then
-               # Untracked files are stored by themselves in a parentless commit, for
-               # ease of unpacking later.
-               u_commit=$(
-                       untracked_files -z "$@" | (
-                               GIT_INDEX_FILE="$TMPindex" &&
-                               export GIT_INDEX_FILE &&
-                               rm -f "$TMPindex" &&
-                               git update-index -z --add --remove --stdin &&
-                               u_tree=$(git write-tree) &&
-                               printf 'untracked files on %s\n' "$msg" | git commit-tree $u_tree  &&
-                               rm -f "$TMPindex"
-               ) ) || die "$(gettext "Cannot save the untracked files")"
-
-               untracked_commit_option="-p $u_commit";
-       else
-               untracked_commit_option=
-       fi
-
-       if test -z "$patch_mode"
-       then
-
-               # state of the working tree
-               w_tree=$( (
-                       git read-tree --index-output="$TMPindex" -m $i_tree &&
-                       GIT_INDEX_FILE="$TMPindex" &&
-                       export GIT_INDEX_FILE &&
-                       git diff-index --name-only -z HEAD -- "$@" >"$TMP-stagenames" &&
-                       git update-index --ignore-skip-worktree-entries \
-                               -z --add --remove --stdin <"$TMP-stagenames" &&
-                       git write-tree &&
-                       rm -f "$TMPindex"
-               ) ) ||
-                       die "$(gettext "Cannot save the current worktree state")"
-
-       else
-
-               rm -f "$TMP-index" &&
-               GIT_INDEX_FILE="$TMP-index" git read-tree HEAD &&
-
-               # find out what the user wants
-               GIT_INDEX_FILE="$TMP-index" \
-                       git add --legacy-stash-p -- "$@" &&
-
-               # state of the working tree
-               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" &&
-               test -s "$TMP-patch" ||
-               die "$(gettext "No changes selected")"
-
-               rm -f "$TMP-index" ||
-               die "$(gettext "Cannot remove temporary index (can't happen)")"
-
-       fi
-
-       # create the stash
-       if test -z "$stash_msg"
-       then
-               stash_msg=$(printf 'WIP on %s' "$msg")
-       else
-               stash_msg=$(printf 'On %s: %s' "$branch" "$stash_msg")
-       fi
-       w_commit=$(printf '%s\n' "$stash_msg" |
-       git commit-tree $w_tree -p $b_commit -p $i_commit $untracked_commit_option) ||
-       die "$(gettext "Cannot record working tree state")"
-}
-
-store_stash () {
-       while test $# != 0
-       do
-               case "$1" in
-               -m|--message)
-                       shift
-                       stash_msg="$1"
-                       ;;
-               -m*)
-                       stash_msg=${1#-m}
-                       ;;
-               --message=*)
-                       stash_msg=${1#--message=}
-                       ;;
-               -q|--quiet)
-                       quiet=t
-                       ;;
-               *)
-                       break
-                       ;;
-               esac
-               shift
-       done
-       test $# = 1 ||
-       die "$(eval_gettext "\"$dashless store\" requires one <commit> argument")"
-
-       w_commit="$1"
-       if test -z "$stash_msg"
-       then
-               stash_msg="Created via \"git stash store\"."
-       fi
-
-       git update-ref --create-reflog -m "$stash_msg" $ref_stash $w_commit
-       ret=$?
-       test $ret != 0 && test -z "$quiet" &&
-       die "$(eval_gettext "Cannot update \$ref_stash with \$w_commit")"
-       return $ret
-}
-
-push_stash () {
-       keep_index=
-       patch_mode=
-       untracked=
-       stash_msg=
-       while test $# != 0
-       do
-               case "$1" in
-               -k|--keep-index)
-                       keep_index=t
-                       ;;
-               --no-keep-index)
-                       keep_index=n
-                       ;;
-               -p|--patch)
-                       patch_mode=t
-                       # only default to keep if we don't already have an override
-                       test -z "$keep_index" && keep_index=t
-                       ;;
-               -q|--quiet)
-                       GIT_QUIET=t
-                       ;;
-               -u|--include-untracked)
-                       untracked=untracked
-                       ;;
-               -a|--all)
-                       untracked=all
-                       ;;
-               -m|--message)
-                       shift
-                       test -z ${1+x} && usage
-                       stash_msg=$1
-                       ;;
-               -m*)
-                       stash_msg=${1#-m}
-                       ;;
-               --message=*)
-                       stash_msg=${1#--message=}
-                       ;;
-               --help)
-                       show_help
-                       ;;
-               --)
-                       shift
-                       break
-                       ;;
-               -*)
-                       option="$1"
-                       eval_gettextln "error: unknown option for 'stash push': \$option"
-                       usage
-                       ;;
-               *)
-                       break
-                       ;;
-               esac
-               shift
-       done
-
-       eval "set $(git rev-parse --sq --prefix "$prefix" -- "$@")"
-
-       if test -n "$patch_mode" && test -n "$untracked"
-       then
-               die "$(gettext "Can't use --patch and --include-untracked or --all at the same time")"
-       fi
-
-       test -n "$untracked" || git ls-files --error-unmatch -- "$@" >/dev/null || exit 1
-
-       git update-index -q --refresh
-       if maybe_quiet no_changes "$@"
-       then
-               say "$(gettext "No local changes to save")"
-               exit 0
-       fi
-
-       git reflog exists $ref_stash ||
-               clear_stash || die "$(gettext "Cannot initialize stash")"
-
-       create_stash -m "$stash_msg" -u "$untracked" -- "$@"
-       store_stash -m "$stash_msg" -q $w_commit ||
-       die "$(gettext "Cannot save the current status")"
-       say "$(eval_gettext "Saved working directory and index state \$stash_msg")"
-
-       if test -z "$patch_mode"
-       then
-               test "$untracked" = "all" && CLEAN_X_OPTION=-x || CLEAN_X_OPTION=
-               if test -n "$untracked" && test $# = 0
-               then
-                       git clean --force --quiet -d $CLEAN_X_OPTION
-               fi
-
-               if test $# != 0
-               then
-                       test -z "$untracked" && UPDATE_OPTION="-u" || UPDATE_OPTION=
-                       test "$untracked" = "all" && FORCE_OPTION="--force" || FORCE_OPTION=
-                       git add $UPDATE_OPTION $FORCE_OPTION -- "$@"
-                       git diff-index -p --cached --binary HEAD -- "$@" |
-                       git apply --index -R
-               else
-                       git reset --hard -q --no-recurse-submodules
-               fi
-
-               if test "$keep_index" = "t" && test -n "$i_tree"
-               then
-                       git read-tree --reset $i_tree
-                       git ls-files -z --modified -- "$@" |
-                       git checkout-index -z --force --stdin
-               fi
-       else
-               git apply -R < "$TMP-patch" ||
-               die "$(gettext "Cannot remove worktree changes")"
-
-               if test "$keep_index" != "t"
-               then
-                       git reset -q -- "$@"
-               fi
-       fi
-}
-
-save_stash () {
-       push_options=
-       while test $# != 0
-       do
-               case "$1" in
-               -q|--quiet)
-                       GIT_QUIET=t
-                       ;;
-               --)
-                       shift
-                       break
-                       ;;
-               -*)
-                       # pass all options through to push_stash
-                       push_options="$push_options $1"
-                       ;;
-               *)
-                       break
-                       ;;
-               esac
-               shift
-       done
-
-       stash_msg="$*"
-
-       if test -z "$stash_msg"
-       then
-               push_stash $push_options
-       else
-               push_stash $push_options -m "$stash_msg"
-       fi
-}
-
-have_stash () {
-       git rev-parse --verify --quiet $ref_stash >/dev/null
-}
-
-list_stash () {
-       have_stash || return 0
-       git log --format="%gd: %gs" -g --first-parent -m "$@" $ref_stash --
-}
-
-show_stash () {
-       ALLOW_UNKNOWN_FLAGS=t
-       assert_stash_like "$@"
-
-       if test -z "$FLAGS"
-       then
-               if test "$(git config --bool stash.showStat || echo true)" = "true"
-               then
-                       FLAGS=--stat
-               fi
-
-               if test "$(git config --bool stash.showPatch || echo false)" = "true"
-               then
-                       FLAGS=${FLAGS}${FLAGS:+ }-p
-               fi
-
-               if test -z "$FLAGS"
-               then
-                       return 0
-               fi
-       fi
-
-       git diff ${FLAGS} $b_commit $w_commit
-}
-
-show_help () {
-       exec git help stash
-       exit 1
-}
-
-#
-# Parses the remaining options looking for flags and
-# at most one revision defaulting to ${ref_stash}@{0}
-# if none found.
-#
-# Derives related tree and commit objects from the
-# revision, if one is found.
-#
-# stash records the work tree, and is a merge between the
-# base commit (first parent) and the index tree (second parent).
-#
-#   REV is set to the symbolic version of the specified stash-like commit
-#   IS_STASH_LIKE is non-blank if ${REV} looks like a stash
-#   IS_STASH_REF is non-blank if the ${REV} looks like a stash ref
-#   s is set to the SHA1 of the stash commit
-#   w_commit is set to the commit containing the working tree
-#   b_commit is set to the base commit
-#   i_commit is set to the commit containing the index tree
-#   u_commit is set to the commit containing the untracked files tree
-#   w_tree is set to the working tree
-#   b_tree is set to the base tree
-#   i_tree is set to the index tree
-#   u_tree is set to the untracked files tree
-#
-#   GIT_QUIET is set to t if -q is specified
-#   INDEX_OPTION is set to --index if --index is specified.
-#   FLAGS is set to the remaining flags (if allowed)
-#
-# dies if:
-#   * too many revisions specified
-#   * no revision is specified and there is no stash stack
-#   * a revision is specified which cannot be resolve to a SHA1
-#   * a non-existent stash reference is specified
-#   * unknown flags were set and ALLOW_UNKNOWN_FLAGS is not "t"
-#
-
-parse_flags_and_rev()
-{
-       test "$PARSE_CACHE" = "$*" && return 0 # optimisation
-       PARSE_CACHE="$*"
-
-       IS_STASH_LIKE=
-       IS_STASH_REF=
-       INDEX_OPTION=
-       s=
-       w_commit=
-       b_commit=
-       i_commit=
-       u_commit=
-       w_tree=
-       b_tree=
-       i_tree=
-       u_tree=
-
-       FLAGS=
-       REV=
-       for opt
-       do
-               case "$opt" in
-                       -q|--quiet)
-                               GIT_QUIET=-t
-                       ;;
-                       --index)
-                               INDEX_OPTION=--index
-                       ;;
-                       --help)
-                               show_help
-                       ;;
-                       -*)
-                               test "$ALLOW_UNKNOWN_FLAGS" = t ||
-                                       die "$(eval_gettext "unknown option: \$opt")"
-                               FLAGS="${FLAGS}${FLAGS:+ }$opt"
-                       ;;
-                       *)
-                               REV="${REV}${REV:+ }'$opt'"
-                       ;;
-               esac
-       done
-
-       eval set -- $REV
-
-       case $# in
-               0)
-                       have_stash || die "$(gettext "No stash entries found.")"
-                       set -- ${ref_stash}@{0}
-               ;;
-               1)
-                       :
-               ;;
-               *)
-                       die "$(eval_gettext "Too many revisions specified: \$REV")"
-               ;;
-       esac
-
-       case "$1" in
-               *[!0-9]*)
-                       :
-               ;;
-               *)
-                       set -- "${ref_stash}@{$1}"
-               ;;
-       esac
-
-       REV=$(git rev-parse --symbolic --verify --quiet "$1") || {
-               reference="$1"
-               die "$(eval_gettext "\$reference is not a valid reference")"
-       }
-
-       i_commit=$(git rev-parse --verify --quiet "$REV^2") &&
-       set -- $(git rev-parse "$REV" "$REV^1" "$REV:" "$REV^1:" "$REV^2:" 2>/dev/null) &&
-       s=$1 &&
-       w_commit=$1 &&
-       b_commit=$2 &&
-       w_tree=$3 &&
-       b_tree=$4 &&
-       i_tree=$5 &&
-       IS_STASH_LIKE=t &&
-       test "$ref_stash" = "$(git rev-parse --symbolic-full-name "${REV%@*}")" &&
-       IS_STASH_REF=t
-
-       u_commit=$(git rev-parse --verify --quiet "$REV^3") &&
-       u_tree=$(git rev-parse "$REV^3:" 2>/dev/null)
-}
-
-is_stash_like()
-{
-       parse_flags_and_rev "$@"
-       test -n "$IS_STASH_LIKE"
-}
-
-assert_stash_like() {
-       is_stash_like "$@" || {
-               args="$*"
-               die "$(eval_gettext "'\$args' is not a stash-like commit")"
-       }
-}
-
-is_stash_ref() {
-       is_stash_like "$@" && test -n "$IS_STASH_REF"
-}
-
-assert_stash_ref() {
-       is_stash_ref "$@" || {
-               args="$*"
-               die "$(eval_gettext "'\$args' is not a stash reference")"
-       }
-}
-
-apply_stash () {
-
-       assert_stash_like "$@"
-
-       git update-index -q --refresh || die "$(gettext "unable to refresh index")"
-
-       # current index state
-       c_tree=$(git write-tree) ||
-               die "$(gettext "Cannot apply a stash in the middle of a merge")"
-
-       unstashed_index_tree=
-       if test -n "$INDEX_OPTION" && test "$b_tree" != "$i_tree" &&
-                       test "$c_tree" != "$i_tree"
-       then
-               git diff-tree --binary $s^2^..$s^2 | git apply --cached
-               test $? -ne 0 &&
-                       die "$(gettext "Conflicts in index. Try without --index.")"
-               unstashed_index_tree=$(git write-tree) ||
-                       die "$(gettext "Could not save index tree")"
-               git reset
-       fi
-
-       if test -n "$u_tree"
-       then
-               GIT_INDEX_FILE="$TMPindex" git read-tree "$u_tree" &&
-               GIT_INDEX_FILE="$TMPindex" git checkout-index --all &&
-               rm -f "$TMPindex" ||
-               die "$(gettext "Could not restore untracked files from stash entry")"
-       fi
-
-       eval "
-               GITHEAD_$w_tree='Stashed changes' &&
-               GITHEAD_$c_tree='Updated upstream' &&
-               GITHEAD_$b_tree='Version stash was based on' &&
-               export GITHEAD_$w_tree GITHEAD_$c_tree GITHEAD_$b_tree
-       "
-
-       if test -n "$GIT_QUIET"
-       then
-               GIT_MERGE_VERBOSITY=0 && export GIT_MERGE_VERBOSITY
-       fi
-       if git merge-recursive $b_tree -- $c_tree $w_tree
-       then
-               # No conflict
-               if test -n "$unstashed_index_tree"
-               then
-                       git read-tree "$unstashed_index_tree"
-               else
-                       a="$TMP-added" &&
-                       git diff-index --cached --name-only --diff-filter=A $c_tree >"$a" &&
-                       git read-tree --reset $c_tree &&
-                       git update-index --add --stdin <"$a" ||
-                               die "$(gettext "Cannot unstage modified files")"
-                       rm -f "$a"
-               fi
-               squelch=
-               if test -n "$GIT_QUIET"
-               then
-                       squelch='>/dev/null 2>&1'
-               fi
-               (cd "$START_DIR" && eval "git status $squelch") || :
-       else
-               # Merge conflict; keep the exit status from merge-recursive
-               status=$?
-               git rerere
-               if test -n "$INDEX_OPTION"
-               then
-                       gettextln "Index was not unstashed." >&2
-               fi
-               exit $status
-       fi
-}
-
-pop_stash() {
-       assert_stash_ref "$@"
-
-       if apply_stash "$@"
-       then
-               drop_stash "$@"
-       else
-               status=$?
-               say "$(gettext "The stash entry is kept in case you need it again.")"
-               exit $status
-       fi
-}
-
-drop_stash () {
-       assert_stash_ref "$@"
-
-       git reflog delete --updateref --rewrite "${REV}" &&
-               say "$(eval_gettext "Dropped \${REV} (\$s)")" ||
-               die "$(eval_gettext "\${REV}: Could not drop stash entry")"
-
-       # clear_stash if we just dropped the last stash entry
-       git rev-parse --verify --quiet "$ref_stash@{0}" >/dev/null ||
-       clear_stash
-}
-
-apply_to_branch () {
-       test -n "$1" || die "$(gettext "No branch name specified")"
-       branch=$1
-       shift 1
-
-       set -- --index "$@"
-       assert_stash_like "$@"
-
-       git checkout -b $branch $REV^ &&
-       apply_stash "$@" && {
-               test -z "$IS_STASH_REF" || drop_stash "$@"
-       }
-}
-
-test "$1" = "-p" && set "push" "$@"
-
-PARSE_CACHE='--not-parsed'
-# The default command is "push" if nothing but options are given
-seen_non_option=
-for opt
-do
-       case "$opt" in
-       --) break ;;
-       -*) ;;
-       *) seen_non_option=t; break ;;
-       esac
-done
-
-test -n "$seen_non_option" || set "push" "$@"
-
-# Main command set
-case "$1" in
-list)
-       shift
-       list_stash "$@"
-       ;;
-show)
-       shift
-       show_stash "$@"
-       ;;
-save)
-       shift
-       save_stash "$@"
-       ;;
-push)
-       shift
-       push_stash "$@"
-       ;;
-apply)
-       shift
-       apply_stash "$@"
-       ;;
-clear)
-       shift
-       clear_stash "$@"
-       ;;
-create)
-       shift
-       create_stash -m "$*" && echo "$w_commit"
-       ;;
-store)
-       shift
-       store_stash "$@"
-       ;;
-drop)
-       shift
-       drop_stash "$@"
-       ;;
-pop)
-       shift
-       pop_stash "$@"
-       ;;
-branch)
-       shift
-       apply_to_branch "$@"
-       ;;
-*)
-       case $# in
-       0)
-               push_stash &&
-               say "$(gettext "(To restore them type \"git stash apply\")")"
-               ;;
-       *)
-               usage
-       esac
-       ;;
-esac
index 9a71a6690db35d674456c677be1212e44e57a582..1fe03cd3392f209ce985f1446ac403c8c0dca9bb 100755 (executable)
--- a/git-p4.py
+++ b/git-p4.py
 # pylint: disable=too-many-branches,too-many-nested-blocks
 #
 import sys
-if sys.hexversion < 0x02040000:
-    # The limiter is the subprocess module
-    sys.stderr.write("git-p4: requires Python 2.4 or later.\n")
+if sys.version_info.major < 3 and sys.version_info.minor < 7:
+    sys.stderr.write("git-p4: requires Python 2.7 or later.\n")
     sys.exit(1)
 import os
 import optparse
+import functools
 import marshal
 import subprocess
 import tempfile
@@ -35,36 +35,15 @@ import zlib
 import ctypes
 import errno
 
+# On python2.7 where raw_input() and input() are both availble,
+# we want raw_input's semantics, but aliased to input for python3
+# compatibility
 # support basestring in python3
 try:
-    unicode = unicode
-except NameError:
-    # 'unicode' is undefined, must be Python 3
-    str = str
-    unicode = str
-    bytes = bytes
-    basestring = (str,bytes)
-else:
-    # 'unicode' exists, must be Python 2
-    str = str
-    unicode = unicode
-    bytes = str
-    basestring = basestring
-
-try:
-    from subprocess import CalledProcessError
-except ImportError:
-    # from python2.7:subprocess.py
-    # Exception classes used by this module.
-    class CalledProcessError(Exception):
-        """This exception is raised when a process run by check_call() returns
-        a non-zero exit status.  The exit status will be stored in the
-        returncode attribute."""
-        def __init__(self, returncode, cmd):
-            self.returncode = returncode
-            self.cmd = cmd
-        def __str__(self):
-            return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode)
+    if raw_input and input:
+        input = raw_input
+except:
+    pass
 
 verbose = False
 
@@ -113,7 +92,7 @@ def p4_build_cmd(cmd):
         # Provide a way to not pass this option by setting git-p4.retries to 0
         real_cmd += ["-r", str(retries)]
 
-    if isinstance(cmd,basestring):
+    if not isinstance(cmd, list):
         real_cmd = ' '.join(real_cmd) + ' ' + cmd
     else:
         real_cmd += cmd
@@ -186,18 +165,48 @@ def prompt(prompt_text):
     """
     choices = set(m.group(1) for m in re.finditer(r"\[(.)\]", prompt_text))
     while True:
-        response = raw_input(prompt_text).strip().lower()
+        response = input(prompt_text).strip().lower()
         if not response:
             continue
         response = response[0]
         if response in choices:
             return response
 
+# We need different encoding/decoding strategies for text data being passed
+# around in pipes depending on python version
+if bytes is not str:
+    # For python3, always encode and decode as appropriate
+    def decode_text_stream(s):
+        return s.decode() if isinstance(s, bytes) else s
+    def encode_text_stream(s):
+        return s.encode() if isinstance(s, str) else s
+else:
+    # For python2.7, pass read strings as-is, but also allow writing unicode
+    def decode_text_stream(s):
+        return s
+    def encode_text_stream(s):
+        return s.encode('utf_8') if isinstance(s, unicode) else s
+
+def decode_path(path):
+    """Decode a given string (bytes or otherwise) using configured path encoding options
+    """
+    encoding = gitConfig('git-p4.pathEncoding') or 'utf_8'
+    if bytes is not str:
+        return path.decode(encoding, errors='replace') if isinstance(path, bytes) else path
+    else:
+        try:
+            path.decode('ascii')
+        except:
+            path = path.decode(encoding, errors='replace')
+            if verbose:
+                print('Path with non-ASCII characters detected. Used {} to decode: {}'.format(encoding, path))
+        return path
+
 def write_pipe(c, stdin):
     if verbose:
         sys.stderr.write('Writing pipe: %s\n' % str(c))
 
-    expand = isinstance(c,basestring)
+    expand = not isinstance(c, list)
     p = subprocess.Popen(c, stdin=subprocess.PIPE, shell=expand)
     pipe = p.stdin
     val = pipe.write(stdin)
@@ -209,6 +218,8 @@ def write_pipe(c, stdin):
 
 def p4_write_pipe(c, stdin):
     real_cmd = p4_build_cmd(c)
+    if bytes is not str and isinstance(stdin, str):
+        stdin = encode_text_stream(stdin)
     return write_pipe(real_cmd, stdin)
 
 def read_pipe_full(c):
@@ -219,15 +230,17 @@ def read_pipe_full(c):
     if verbose:
         sys.stderr.write('Reading pipe: %s\n' % str(c))
 
-    expand = isinstance(c,basestring)
+    expand = not isinstance(c, list)
     p = subprocess.Popen(c, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=expand)
     (out, err) = p.communicate()
-    return (p.returncode, out, err)
+    return (p.returncode, out, decode_text_stream(err))
 
-def read_pipe(c, ignore_error=False):
+def read_pipe(c, ignore_error=False, raw=False):
     """ Read output from  command. Returns the output text on
         success. On failure, terminates execution, unless
         ignore_error is True, when it returns an empty string.
+
+        If raw is True, do not attempt to decode output text.
     """
     (retcode, out, err) = read_pipe_full(c)
     if retcode != 0:
@@ -235,6 +248,8 @@ def read_pipe(c, ignore_error=False):
             out = ""
         else:
             die('Command failed: %s\nError: %s' % (str(c), err))
+    if not raw:
+        out = decode_text_stream(out)
     return out
 
 def read_pipe_text(c):
@@ -245,23 +260,22 @@ def read_pipe_text(c):
     if retcode != 0:
         return None
     else:
-        return out.rstrip()
+        return decode_text_stream(out).rstrip()
 
-def p4_read_pipe(c, ignore_error=False):
+def p4_read_pipe(c, ignore_error=False, raw=False):
     real_cmd = p4_build_cmd(c)
-    return read_pipe(real_cmd, ignore_error)
+    return read_pipe(real_cmd, ignore_error, raw=raw)
 
 def read_pipe_lines(c):
     if verbose:
         sys.stderr.write('Reading pipe: %s\n' % str(c))
 
-    expand = isinstance(c, basestring)
+    expand = not isinstance(c, list)
     p = subprocess.Popen(c, stdout=subprocess.PIPE, shell=expand)
     pipe = p.stdout
-    val = pipe.readlines()
+    val = [decode_text_stream(line) for line in pipe.readlines()]
     if pipe.close() or p.wait():
         die('Command failed: %s' % str(c))
-
     return val
 
 def p4_read_pipe_lines(c):
@@ -289,6 +303,7 @@ def p4_has_move_command():
     cmd = p4_build_cmd(["move", "-k", "@from", "@to"])
     p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
     (out, err) = p.communicate()
+    err = decode_text_stream(err)
     # return code will be 1 in either case
     if err.find("Invalid option") >= 0:
         return False
@@ -298,7 +313,7 @@ def p4_has_move_command():
     return True
 
 def system(cmd, ignore_error=False):
-    expand = isinstance(cmd,basestring)
+    expand = not isinstance(cmd, list)
     if verbose:
         sys.stderr.write("executing %s\n" % str(cmd))
     retcode = subprocess.call(cmd, shell=expand)
@@ -310,7 +325,7 @@ def system(cmd, ignore_error=False):
 def p4_system(cmd):
     """Specifically invoke p4 as the system command. """
     real_cmd = p4_build_cmd(cmd)
-    expand = isinstance(real_cmd, basestring)
+    expand = not isinstance(real_cmd, list)
     retcode = subprocess.call(real_cmd, shell=expand)
     if retcode:
         raise CalledProcessError(retcode, real_cmd)
@@ -548,7 +563,7 @@ def getP4OpenedType(file):
 # Return the set of all p4 labels
 def getP4Labels(depotPaths):
     labels = set()
-    if isinstance(depotPaths,basestring):
+    if not isinstance(depotPaths, list):
         depotPaths = [depotPaths]
 
     for l in p4CmdList(["labels"] + ["%s..." % p for p in depotPaths]):
@@ -565,12 +580,7 @@ def getGitTags():
         gitTags.add(tag)
     return gitTags
 
-def diffTreePattern():
-    # This is a simple generator for the diff tree regex pattern. This could be
-    # a class variable if this and parseDiffTreeEntry were a part of a class.
-    pattern = re.compile(':(\d+) (\d+) (\w+) (\w+) ([A-Z])(\d+)?\t(.*?)((\t(.*))|$)')
-    while True:
-        yield pattern
+_diff_tree_pattern = None
 
 def parseDiffTreeEntry(entry):
     """Parses a single diff tree entry into its component elements.
@@ -591,7 +601,11 @@ def parseDiffTreeEntry(entry):
 
     If the pattern is not matched, None is returned."""
 
-    match = diffTreePattern().next().match(entry)
+    global _diff_tree_pattern
+    if not _diff_tree_pattern:
+        _diff_tree_pattern = re.compile(':(\d+) (\d+) (\w+) (\w+) ([A-Z])(\d+)?\t(.*?)((\t(.*))|$)')
+
+    match = _diff_tree_pattern.match(entry)
     if match:
         return {
             'src_mode': match.group(1),
@@ -643,7 +657,7 @@ def isModeExecChanged(src_mode, dst_mode):
 def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None, skip_info=False,
         errors_as_exceptions=False):
 
-    if isinstance(cmd,basestring):
+    if not isinstance(cmd, list):
         cmd = "-G " + cmd
         expand = True
     else:
@@ -660,11 +674,12 @@ def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None, skip_info=False,
     stdin_file = None
     if stdin is not None:
         stdin_file = tempfile.TemporaryFile(prefix='p4-stdin', mode=stdin_mode)
-        if isinstance(stdin,basestring):
+        if not isinstance(stdin, list):
             stdin_file.write(stdin)
         else:
             for i in stdin:
-                stdin_file.write(i + '\n')
+                stdin_file.write(encode_text_stream(i))
+                stdin_file.write(b'\n')
         stdin_file.flush()
         stdin_file.seek(0)
 
@@ -677,6 +692,20 @@ def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None, skip_info=False,
     try:
         while True:
             entry = marshal.load(p4.stdout)
+            if bytes is not str:
+                # Decode unmarshalled dict to use str keys and values, except for:
+                #   - `data` which may contain arbitrary binary data
+                #   - `depotFile[0-9]*`, `path`, or `clientFile` which may contain non-UTF8 encoded text
+                decoded_entry = {}
+                for key, value in entry.items():
+                    key = key.decode()
+                    if isinstance(value, bytes) and not (key in ('data', 'path', 'clientFile') or key.startswith('depotFile')):
+                        value = value.decode()
+                    decoded_entry[key] = value
+                # Parse out data if it's an error response
+                if decoded_entry.get('code') == 'error' and 'data' in decoded_entry:
+                    decoded_entry['data'] = decoded_entry['data'].decode()
+                entry = decoded_entry
             if skip_info:
                 if 'code' in entry and entry['code'] == 'info':
                     continue
@@ -727,7 +756,8 @@ def p4Where(depotPath):
         if "depotFile" in entry:
             # Search for the base client side depot path, as long as it starts with the branch's P4 path.
             # The base path always ends with "/...".
-            if entry["depotFile"].find(depotPath) == 0 and entry["depotFile"][-4:] == "/...":
+            entry_path = decode_path(entry['depotFile'])
+            if entry_path.find(depotPath) == 0 and entry_path[-4:] == "/...":
                 output = entry
                 break
         elif "data" in entry:
@@ -742,11 +772,11 @@ def p4Where(depotPath):
         return ""
     clientPath = ""
     if "path" in output:
-        clientPath = output.get("path")
+        clientPath = decode_path(output['path'])
     elif "data" in output:
         data = output.get("data")
-        lastSpace = data.rfind(" ")
-        clientPath = data[lastSpace + 1:]
+        lastSpace = data.rfind(b" ")
+        clientPath = decode_path(data[lastSpace + 1:])
 
     if clientPath.endswith("..."):
         clientPath = clientPath[:-3]
@@ -894,6 +924,7 @@ def branch_exists(branch):
     cmd = [ "git", "rev-parse", "--symbolic", "--verify", branch ]
     p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
     out, _ = p.communicate()
+    out = decode_text_stream(out)
     if p.returncode:
         return False
     # expect exactly one line of output: the branch name
@@ -1171,7 +1202,7 @@ class LargeFileSystem(object):
         assert False, "Method 'pushFile' required in " + self.__class__.__name__
 
     def hasLargeFileExtension(self, relPath):
-        return reduce(
+        return functools.reduce(
             lambda a, b: a or b,
             [relPath.endswith('.' + e) for e in gitConfigList('git-p4.largeFileExtensions')],
             False
@@ -1278,7 +1309,7 @@ class GitLFS(LargeFileSystem):
             ['git', 'lfs', 'pointer', '--file=' + contentFile],
             stdout=subprocess.PIPE
         )
-        pointerFile = pointerProcess.stdout.read()
+        pointerFile = decode_text_stream(pointerProcess.stdout.read())
         if pointerProcess.wait():
             os.remove(contentFile)
             die('git-lfs pointer command failed. Did you install the extension?')
@@ -1414,14 +1445,14 @@ class P4UserMap:
         for (key, val) in self.users.items():
             s += "%s\t%s\n" % (key.expandtabs(1), val.expandtabs(1))
 
-        open(self.getUserCacheFilename(), "wb").write(s)
+        open(self.getUserCacheFilename(), 'w').write(s)
         self.userMapFromPerforceServer = True
 
     def loadUserMapFromCache(self):
         self.users = {}
         self.userMapFromPerforceServer = False
         try:
-            cache = open(self.getUserCacheFilename(), "rb")
+            cache = open(self.getUserCacheFilename(), 'r')
             lines = cache.readlines()
             cache.close()
             for line in lines:
@@ -1698,7 +1729,8 @@ class P4Submit(Command, P4UserMap):
         c = changes[0]
         if c['User'] == newUser: return   # nothing to do
         c['User'] = newUser
-        input = marshal.dumps(c)
+        # p4 does not understand format version 3 and above
+        input = marshal.dumps(c, 2)
 
         result = p4CmdList("change -f -i", stdin=input)
         for r in result:
@@ -1762,7 +1794,7 @@ class P4Submit(Command, P4UserMap):
                 break
         if not change_entry:
             die('Failed to decode output of p4 change -o')
-        for key, value in change_entry.iteritems():
+        for key, value in change_entry.items():
             if key.startswith('File'):
                 if 'depot-paths' in settings:
                     if not [p for p in settings['depot-paths']
@@ -2042,7 +2074,7 @@ class P4Submit(Command, P4UserMap):
         tmpFile = os.fdopen(handle, "w+b")
         if self.isWindows:
             submitTemplate = submitTemplate.replace("\n", "\r\n")
-        tmpFile.write(submitTemplate)
+        tmpFile.write(encode_text_stream(submitTemplate))
         tmpFile.close()
 
         if self.prepare_p4_only:
@@ -2089,7 +2121,7 @@ class P4Submit(Command, P4UserMap):
             if self.edit_template(fileName):
                 # read the edited message and submit
                 tmpFile = open(fileName, "rb")
-                message = tmpFile.read()
+                message = decode_text_stream(tmpFile.read())
                 tmpFile.close()
                 if self.isWindows:
                     message = message.replace("\r\n", "\n")
@@ -2509,7 +2541,7 @@ class View(object):
 
     def convert_client_path(self, clientFile):
         # chop off //client/ part to make it relative
-        if not clientFile.startswith(self.client_prefix):
+        if not decode_path(clientFile).startswith(self.client_prefix):
             die("No prefix '%s' on clientFile '%s'" %
                 (self.client_prefix, clientFile))
         return clientFile[len(self.client_prefix):]
@@ -2518,7 +2550,7 @@ class View(object):
         """ Caching file paths by "p4 where" batch query """
 
         # List depot file paths exclude that already cached
-        fileArgs = [f['path'] for f in files if f['path'] not in self.client_spec_path_cache]
+        fileArgs = [f['path'] for f in files if decode_path(f['path']) not in self.client_spec_path_cache]
 
         if len(fileArgs) == 0:
             return  # All files in cache
@@ -2533,16 +2565,18 @@ class View(object):
             if "unmap" in res:
                 # it will list all of them, but only one not unmap-ped
                 continue
+            depot_path = decode_path(res['depotFile'])
             if gitConfigBool("core.ignorecase"):
-                res['depotFile'] = res['depotFile'].lower()
-            self.client_spec_path_cache[res['depotFile']] = self.convert_client_path(res["clientFile"])
+                depot_path = depot_path.lower()
+            self.client_spec_path_cache[depot_path] = self.convert_client_path(res["clientFile"])
 
         # not found files or unmap files set to ""
         for depotFile in fileArgs:
+            depotFile = decode_path(depotFile)
             if gitConfigBool("core.ignorecase"):
                 depotFile = depotFile.lower()
             if depotFile not in self.client_spec_path_cache:
-                self.client_spec_path_cache[depotFile] = ""
+                self.client_spec_path_cache[depotFile] = b''
 
     def map_in_client(self, depot_path):
         """Return the relative location in the client where this
@@ -2647,6 +2681,7 @@ class P4Sync(Command, P4UserMap):
     def checkpoint(self):
         self.gitStream.write("checkpoint\n\n")
         self.gitStream.write("progress checkpoint\n\n")
+        self.gitStream.flush()
         out = self.gitOutput.readline()
         if self.verbose:
             print("checkpoint finished: " + out)
@@ -2660,7 +2695,7 @@ class P4Sync(Command, P4UserMap):
             elif path.lower() == p.lower():
                 return False
         for p in self.depotPaths:
-            if p4PathStartsWith(path, p):
+            if p4PathStartsWith(path, decode_path(p)):
                 return True
         return False
 
@@ -2669,7 +2704,7 @@ class P4Sync(Command, P4UserMap):
         fnum = 0
         while "depotFile%s" % fnum in commit:
             path =  commit["depotFile%s" % fnum]
-            found = self.isPathWanted(path)
+            found = self.isPathWanted(decode_path(path))
             if not found:
                 fnum = fnum + 1
                 continue
@@ -2703,7 +2738,7 @@ class P4Sync(Command, P4UserMap):
         if self.useClientSpec:
             # branch detection moves files up a level (the branch name)
             # from what client spec interpretation gives
-            path = self.clientSpecDirs.map_in_client(path)
+            path = decode_path(self.clientSpecDirs.map_in_client(path))
             if self.detectBranches:
                 for b in self.knownBranches:
                     if p4PathStartsWith(path, b + "/"):
@@ -2737,14 +2772,15 @@ class P4Sync(Command, P4UserMap):
         branches = {}
         fnum = 0
         while "depotFile%s" % fnum in commit:
-            path =  commit["depotFile%s" % fnum]
+            raw_path = commit["depotFile%s" % fnum]
+            path = decode_path(raw_path)
             found = self.isPathWanted(path)
             if not found:
                 fnum = fnum + 1
                 continue
 
             file = {}
-            file["path"] = path
+            file["path"] = raw_path
             file["rev"] = commit["rev%s" % fnum]
             file["action"] = commit["action%s" % fnum]
             file["type"] = commit["type%s" % fnum]
@@ -2753,7 +2789,7 @@ class P4Sync(Command, P4UserMap):
             # start with the full relative path where this file would
             # go in a p4 client
             if self.useClientSpec:
-                relPath = self.clientSpecDirs.map_in_client(path)
+                relPath = decode_path(self.clientSpecDirs.map_in_client(path))
             else:
                 relPath = self.stripRepoPath(path, self.depotPaths)
 
@@ -2769,7 +2805,7 @@ class P4Sync(Command, P4UserMap):
         return branches
 
     def writeToGitStream(self, gitMode, relPath, contents):
-        self.gitStream.write('M %s inline %s\n' % (gitMode, relPath))
+        self.gitStream.write(encode_text_stream(u'M {} inline {}\n'.format(gitMode, relPath)))
         self.gitStream.write('data %d\n' % sum(len(d) for d in contents))
         for d in contents:
             self.gitStream.write(d)
@@ -2791,14 +2827,15 @@ class P4Sync(Command, P4UserMap):
     # - helper for streamP4Files
 
     def streamOneP4File(self, file, contents):
-        relPath = self.stripRepoPath(file['depotFile'], self.branchPrefixes)
-        relPath = self.encodeWithUTF8(relPath)
+        file_path = file['depotFile']
+        relPath = self.stripRepoPath(decode_path(file_path), self.branchPrefixes)
+
         if verbose:
             if 'fileSize' in self.stream_file:
                 size = int(self.stream_file['fileSize'])
             else:
                 size = 0 # deleted files don't get a fileSize apparently
-            sys.stdout.write('\r%s --> %s (%i MB)\n' % (file['depotFile'], relPath, size/1024/1024))
+            sys.stdout.write('\r%s --> %s (%i MB)\n' % (file_path, relPath, size/1024/1024))
             sys.stdout.flush()
 
         (type_base, type_mods) = split_p4_type(file["type"])
@@ -2810,13 +2847,13 @@ class P4Sync(Command, P4UserMap):
             git_mode = "120000"
             # p4 print on a symlink sometimes contains "target\n";
             # if it does, remove the newline
-            data = ''.join(contents)
+            data = ''.join(decode_text_stream(c) for c in contents)
             if not data:
                 # Some version of p4 allowed creating a symlink that pointed
                 # to nothing.  This causes p4 errors when checking out such
                 # a change, and errors here too.  Work around it by ignoring
                 # the bad symlink; hopefully a future change fixes it.
-                print("\nIgnoring empty symlink in %s" % file['depotFile'])
+                print("\nIgnoring empty symlink in %s" % file_path)
                 return
             elif data[-1] == '\n':
                 contents = [data[:-1]]
@@ -2835,7 +2872,7 @@ class P4Sync(Command, P4UserMap):
             # just the native "NT" type.
             #
             try:
-                text = p4_read_pipe(['print', '-q', '-o', '-', '%s@%s' % (file['depotFile'], file['change'])])
+                text = p4_read_pipe(['print', '-q', '-o', '-', '%s@%s' % (decode_path(file['depotFile']), file['change'])], raw=True)
             except Exception as e:
                 if 'Translation of file content failed' in str(e):
                     type_base = 'binary'
@@ -2843,7 +2880,7 @@ class P4Sync(Command, P4UserMap):
                     raise e
             else:
                 if p4_version_string().find('/NT') >= 0:
-                    text = text.replace('\r\n', '\n')
+                    text = text.replace(b'\r\n', b'\n')
                 contents = [ text ]
 
         if type_base == "apple":
@@ -2864,7 +2901,7 @@ class P4Sync(Command, P4UserMap):
         pattern = p4_keywords_regexp_for_type(type_base, type_mods)
         if pattern:
             regexp = re.compile(pattern, re.VERBOSE)
-            text = ''.join(contents)
+            text = ''.join(decode_text_stream(c) for c in contents)
             text = regexp.sub(r'$\1$', text)
             contents = [ text ]
 
@@ -2874,12 +2911,11 @@ class P4Sync(Command, P4UserMap):
         self.writeToGitStream(git_mode, relPath, contents)
 
     def streamOneP4Deletion(self, file):
-        relPath = self.stripRepoPath(file['path'], self.branchPrefixes)
-        relPath = self.encodeWithUTF8(relPath)
+        relPath = self.stripRepoPath(decode_path(file['path']), self.branchPrefixes)
         if verbose:
             sys.stdout.write("delete %s\n" % relPath)
             sys.stdout.flush()
-        self.gitStream.write("D %s\n" % relPath)
+        self.gitStream.write(encode_text_stream(u'D {}\n'.format(relPath)))
 
         if self.largeFileSystem and self.largeFileSystem.isLargeFile(relPath):
             self.largeFileSystem.removeLargeFile(relPath)
@@ -2979,9 +3015,9 @@ class P4Sync(Command, P4UserMap):
                 if 'shelved_cl' in f:
                     # Handle shelved CLs using the "p4 print file@=N" syntax to print
                     # the contents
-                    fileArg = '%s@=%d' % (f['path'], f['shelved_cl'])
+                    fileArg = f['path'] + encode_text_stream('@={}'.format(f['shelved_cl']))
                 else:
-                    fileArg = '%s#%s' % (f['path'], f['rev'])
+                    fileArg = f['path'] + encode_text_stream('#{}'.format(f['rev']))
 
                 fileArgs.append(fileArg)
 
@@ -3062,8 +3098,8 @@ class P4Sync(Command, P4UserMap):
         if self.clientSpecDirs:
             self.clientSpecDirs.update_client_spec_path_cache(files)
 
-        files = [f for f in files
-            if self.inClientSpec(f['path']) and self.hasBranchPrefix(f['path'])]
+        files = [f for (f, path) in ((f, decode_path(f['path'])) for f in files)
+            if self.inClientSpec(path) and self.hasBranchPrefix(path)]
 
         if gitConfigBool('git-p4.keepEmptyCommits'):
             allow_empty = True
@@ -3635,6 +3671,15 @@ class P4Sync(Command, P4UserMap):
         self.gitStream = self.importProcess.stdin
         self.gitError = self.importProcess.stderr
 
+        if bytes is not str:
+            # Wrap gitStream.write() so that it can be called using `str` arguments
+            def make_encoded_write(write):
+                def encoded_write(s):
+                    return write(s.encode() if isinstance(s, str) else s)
+                return encoded_write
+
+            self.gitStream.write = make_encoded_write(self.gitStream.write)
+
     def closeStreams(self):
         if self.gitStream is None:
             return
diff --git a/git.c b/git.c
index 7be7ad34bd053884ec48923706e70c81719a8660..b07198fe036fdd91400434facabba70bf6a8867e 100644 (file)
--- a/git.c
+++ b/git.c
@@ -574,12 +574,7 @@ static struct cmd_struct commands[] = {
        { "show-ref", cmd_show_ref, RUN_SETUP },
        { "sparse-checkout", cmd_sparse_checkout, RUN_SETUP | NEED_WORK_TREE },
        { "stage", cmd_add, RUN_SETUP | NEED_WORK_TREE },
-       /*
-        * NEEDSWORK: Until the builtin stash is thoroughly robust and no
-        * longer needs redirection to the stash shell script this is kept as
-        * is, then should be changed to RUN_SETUP | NEED_WORK_TREE
-        */
-       { "stash", cmd_stash },
+       { "stash", cmd_stash, RUN_SETUP | NEED_WORK_TREE },
        { "status", cmd_status, RUN_SETUP | NEED_WORK_TREE },
        { "stripspace", cmd_stripspace },
        { "submodule--helper", cmd_submodule__helper, RUN_SETUP | SUPPORT_SUPER_PREFIX | NO_PARSEOPT },
index 165274d74a84a8f30b21a049ebcbccf8aaecc6fb..2d538bcd6e30d1314f76d89764284f8735cd187f 100644 (file)
@@ -256,6 +256,55 @@ error:
        FREE_AND_NULL(sigc->key);
 }
 
+static int verify_signed_buffer(const char *payload, size_t payload_size,
+                               const char *signature, size_t signature_size,
+                               struct strbuf *gpg_output,
+                               struct strbuf *gpg_status)
+{
+       struct child_process gpg = CHILD_PROCESS_INIT;
+       struct gpg_format *fmt;
+       struct tempfile *temp;
+       int ret;
+       struct strbuf buf = STRBUF_INIT;
+
+       temp = mks_tempfile_t(".git_vtag_tmpXXXXXX");
+       if (!temp)
+               return error_errno(_("could not create temporary file"));
+       if (write_in_full(temp->fd, signature, signature_size) < 0 ||
+           close_tempfile_gently(temp) < 0) {
+               error_errno(_("failed writing detached signature to '%s'"),
+                           temp->filename.buf);
+               delete_tempfile(&temp);
+               return -1;
+       }
+
+       fmt = get_format_by_sig(signature);
+       if (!fmt)
+               BUG("bad signature '%s'", signature);
+
+       argv_array_push(&gpg.args, fmt->program);
+       argv_array_pushv(&gpg.args, fmt->verify_args);
+       argv_array_pushl(&gpg.args,
+                        "--status-fd=1",
+                        "--verify", temp->filename.buf, "-",
+                        NULL);
+
+       if (!gpg_status)
+               gpg_status = &buf;
+
+       sigchain_push(SIGPIPE, SIG_IGN);
+       ret = pipe_command(&gpg, payload, payload_size,
+                          gpg_status, 0, gpg_output, 0);
+       sigchain_pop(SIGPIPE);
+
+       delete_tempfile(&temp);
+
+       ret |= !strstr(gpg_status->buf, "\n[GNUPG:] GOODSIG ");
+       strbuf_release(&buf); /* no matter it was used or not */
+
+       return ret;
+}
+
 int check_signature(const char *payload, size_t plen, const char *signature,
        size_t slen, struct signature_check *sigc)
 {
@@ -418,51 +467,3 @@ int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *sig
 
        return 0;
 }
-
-int verify_signed_buffer(const char *payload, size_t payload_size,
-                        const char *signature, size_t signature_size,
-                        struct strbuf *gpg_output, struct strbuf *gpg_status)
-{
-       struct child_process gpg = CHILD_PROCESS_INIT;
-       struct gpg_format *fmt;
-       struct tempfile *temp;
-       int ret;
-       struct strbuf buf = STRBUF_INIT;
-
-       temp = mks_tempfile_t(".git_vtag_tmpXXXXXX");
-       if (!temp)
-               return error_errno(_("could not create temporary file"));
-       if (write_in_full(temp->fd, signature, signature_size) < 0 ||
-           close_tempfile_gently(temp) < 0) {
-               error_errno(_("failed writing detached signature to '%s'"),
-                           temp->filename.buf);
-               delete_tempfile(&temp);
-               return -1;
-       }
-
-       fmt = get_format_by_sig(signature);
-       if (!fmt)
-               BUG("bad signature '%s'", signature);
-
-       argv_array_push(&gpg.args, fmt->program);
-       argv_array_pushv(&gpg.args, fmt->verify_args);
-       argv_array_pushl(&gpg.args,
-                        "--status-fd=1",
-                        "--verify", temp->filename.buf, "-",
-                        NULL);
-
-       if (!gpg_status)
-               gpg_status = &buf;
-
-       sigchain_push(SIGPIPE, SIG_IGN);
-       ret = pipe_command(&gpg, payload, payload_size,
-                          gpg_status, 0, gpg_output, 0);
-       sigchain_pop(SIGPIPE);
-
-       delete_tempfile(&temp);
-
-       ret |= !strstr(gpg_status->buf, "\n[GNUPG:] GOODSIG ");
-       strbuf_release(&buf); /* no matter it was used or not */
-
-       return ret;
-}
index 796571e9e90fa50bc6f368bdeefacc8e86a9c520..f4e9b4f3715a0b0d4be3ef526388bf6b87b56fda 100644 (file)
@@ -54,15 +54,6 @@ size_t parse_signature(const char *buf, size_t size);
 int sign_buffer(struct strbuf *buffer, struct strbuf *signature,
                const char *signing_key);
 
-/*
- * Run "gpg" to see if the payload matches the detached signature.
- * gpg_output, when set, receives the diagnostic output from GPG.
- * gpg_status, when set, receives the status 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 strbuf *gpg_status);
-
 int git_gpg_config(const char *, const char *, void *);
 void set_signing_key(const char *);
 const char *get_signing_key(void);
diff --git a/hash.h b/hash.h
index 52a4f1a3f43089f02bbdcfb2759da479841c7405..e0f3f16b06b7930e448912bdc699a47125a19b0c 100644 (file)
--- a/hash.h
+++ b/hash.h
@@ -16,6 +16,7 @@
 #endif
 
 #if defined(SHA256_GCRYPT)
+#define SHA256_NEEDS_CLONE_HELPER
 #include "sha256/gcrypt.h"
 #elif defined(SHA256_OPENSSL)
 #include <openssl/sha.h>
 #define git_SHA256_Update      platform_SHA256_Update
 #define git_SHA256_Final       platform_SHA256_Final
 
+#ifdef platform_SHA256_Clone
+#define git_SHA256_Clone       platform_SHA256_Clone
+#endif
+
 #ifdef SHA1_MAX_BLOCK_SIZE
 #include "compat/sha1-chunked.h"
 #undef git_SHA1_Update
 #define git_SHA1_Update                git_SHA1_Update_Chunked
 #endif
 
+static inline void git_SHA1_Clone(git_SHA_CTX *dst, const git_SHA_CTX *src)
+{
+       memcpy(dst, src, sizeof(*dst));
+}
+
+#ifndef SHA256_NEEDS_CLONE_HELPER
+static inline void git_SHA256_Clone(git_SHA256_CTX *dst, const git_SHA256_CTX *src)
+{
+       memcpy(dst, src, sizeof(*dst));
+}
+#endif
+
 /*
  * Note that these constants are suitable for indexing the hash_algos array and
  * comparing against each other, but are otherwise arbitrary, so they should not
@@ -85,6 +102,7 @@ union git_hash_ctx {
 typedef union git_hash_ctx git_hash_ctx;
 
 typedef void (*git_hash_init_fn)(git_hash_ctx *ctx);
+typedef void (*git_hash_clone_fn)(git_hash_ctx *dst, const git_hash_ctx *src);
 typedef void (*git_hash_update_fn)(git_hash_ctx *ctx, const void *in, size_t len);
 typedef void (*git_hash_final_fn)(unsigned char *hash, git_hash_ctx *ctx);
 
@@ -110,6 +128,9 @@ struct git_hash_algo {
        /* The hash initialization function. */
        git_hash_init_fn init_fn;
 
+       /* The hash context cloning function. */
+       git_hash_clone_fn clone_fn;
+
        /* The hash update function. */
        git_hash_update_fn update_fn;
 
diff --git a/hex.c b/hex.c
index fd7f00c43f969a30a76fe8ac8bf2255ac0320676..da51e64929a4f4973406d001a66e5ec72e76e628 100644 (file)
--- a/hex.c
+++ b/hex.c
@@ -47,32 +47,73 @@ int hex_to_bytes(unsigned char *binary, const char *hex, size_t len)
        return 0;
 }
 
-int get_sha1_hex(const char *hex, unsigned char *sha1)
+static int get_hash_hex_algop(const char *hex, unsigned char *hash,
+                             const struct git_hash_algo *algop)
 {
        int i;
-       for (i = 0; i < the_hash_algo->rawsz; i++) {
+       for (i = 0; i < algop->rawsz; i++) {
                int val = hex2chr(hex);
                if (val < 0)
                        return -1;
-               *sha1++ = val;
+               *hash++ = val;
                hex += 2;
        }
        return 0;
 }
 
+int get_sha1_hex(const char *hex, unsigned char *sha1)
+{
+       return get_hash_hex_algop(hex, sha1, the_hash_algo);
+}
+
+int get_oid_hex_algop(const char *hex, struct object_id *oid,
+                     const struct git_hash_algo *algop)
+{
+       return get_hash_hex_algop(hex, oid->hash, algop);
+}
+
+/*
+ * NOTE: This function relies on hash algorithms being in order from shortest
+ * length to longest length.
+ */
+int get_oid_hex_any(const char *hex, struct object_id *oid)
+{
+       int i;
+       for (i = GIT_HASH_NALGOS - 1; i > 0; i--) {
+               if (!get_hash_hex_algop(hex, oid->hash, &hash_algos[i]))
+                       return i;
+       }
+       return GIT_HASH_UNKNOWN;
+}
+
 int get_oid_hex(const char *hex, struct object_id *oid)
 {
-       return get_sha1_hex(hex, oid->hash);
+       return get_oid_hex_algop(hex, oid, the_hash_algo);
 }
 
-int parse_oid_hex(const char *hex, struct object_id *oid, const char **end)
+int parse_oid_hex_algop(const char *hex, struct object_id *oid,
+                       const char **end,
+                       const struct git_hash_algo *algop)
 {
-       int ret = get_oid_hex(hex, oid);
+       int ret = get_hash_hex_algop(hex, oid->hash, algop);
        if (!ret)
-               *end = hex + the_hash_algo->hexsz;
+               *end = hex + algop->hexsz;
        return ret;
 }
 
+int parse_oid_hex_any(const char *hex, struct object_id *oid, const char **end)
+{
+       int ret = get_oid_hex_any(hex, oid);
+       if (ret)
+               *end = hex + hash_algos[ret].hexsz;
+       return ret;
+}
+
+int parse_oid_hex(const char *hex, struct object_id *oid, const char **end)
+{
+       return parse_oid_hex_algop(hex, oid, end, the_hash_algo);
+}
+
 char *hash_to_hex_algop_r(char *buffer, const unsigned char *hash,
                          const struct git_hash_algo *algop)
 {
diff --git a/http.c b/http.c
index 00a0e507633b3a464e0ef3c7bdd6c04e044f441a..62aa995245324dc30fec699ed8c570af7b152b8f 100644 (file)
--- a/http.c
+++ b/http.c
@@ -86,6 +86,13 @@ static long curl_low_speed_time = -1;
 static int curl_ftp_no_epsv;
 static const char *curl_http_proxy;
 static const char *http_proxy_authmethod;
+
+static const char *http_proxy_ssl_cert;
+static const char *http_proxy_ssl_key;
+static const char *http_proxy_ssl_ca_info;
+static struct credential proxy_cert_auth = CREDENTIAL_INIT;
+static int proxy_ssl_cert_password_required;
+
 static struct {
        const char *name;
        long curlauth_param;
@@ -365,6 +372,20 @@ static int http_options(const char *var, const char *value, void *cb)
        if (!strcmp("http.proxyauthmethod", var))
                return git_config_string(&http_proxy_authmethod, var, value);
 
+       if (!strcmp("http.proxysslcert", var))
+               return git_config_string(&http_proxy_ssl_cert, var, value);
+
+       if (!strcmp("http.proxysslkey", var))
+               return git_config_string(&http_proxy_ssl_key, var, value);
+
+       if (!strcmp("http.proxysslcainfo", var))
+               return git_config_string(&http_proxy_ssl_ca_info, var, value);
+
+       if (!strcmp("http.proxysslcertpasswordprotected", var)) {
+               proxy_ssl_cert_password_required = git_config_bool(var, value);
+               return 0;
+       }
+
        if (!strcmp("http.cookiefile", var))
                return git_config_pathname(&curl_cookie_file, var, value);
        if (!strcmp("http.savecookies", var)) {
@@ -558,6 +579,7 @@ static int has_cert_password(void)
                return 0;
        if (!cert_auth.password) {
                cert_auth.protocol = xstrdup("cert");
+               cert_auth.host = xstrdup("");
                cert_auth.username = xstrdup("");
                cert_auth.path = xstrdup(ssl_cert);
                credential_fill(&cert_auth);
@@ -565,6 +587,22 @@ static int has_cert_password(void)
        return 1;
 }
 
+#if LIBCURL_VERSION_NUM >= 0x073400
+static int has_proxy_cert_password(void)
+{
+       if (http_proxy_ssl_cert == NULL || proxy_ssl_cert_password_required != 1)
+               return 0;
+       if (!proxy_cert_auth.password) {
+               proxy_cert_auth.protocol = xstrdup("cert");
+               proxy_cert_auth.host = xstrdup("");
+               proxy_cert_auth.username = xstrdup("");
+               proxy_cert_auth.path = xstrdup(http_proxy_ssl_cert);
+               credential_fill(&proxy_cert_auth);
+       }
+       return 1;
+}
+#endif
+
 #if LIBCURL_VERSION_NUM >= 0x071900
 static void set_curl_keepalive(CURL *c)
 {
@@ -924,8 +962,14 @@ static CURL *get_curl_handle(void)
 #if LIBCURL_VERSION_NUM >= 0x073400
                curl_easy_setopt(result, CURLOPT_PROXY_CAINFO, NULL);
 #endif
-       } else if (ssl_cainfo != NULL)
-               curl_easy_setopt(result, CURLOPT_CAINFO, ssl_cainfo);
+       } else if (ssl_cainfo != NULL || http_proxy_ssl_ca_info != NULL) {
+               if (ssl_cainfo != NULL)
+                       curl_easy_setopt(result, CURLOPT_CAINFO, ssl_cainfo);
+#if LIBCURL_VERSION_NUM >= 0x073400
+               if (http_proxy_ssl_ca_info != NULL)
+                       curl_easy_setopt(result, CURLOPT_PROXY_CAINFO, http_proxy_ssl_ca_info);
+#endif
+       }
 
        if (curl_low_speed_limit > 0 && curl_low_speed_time > 0) {
                curl_easy_setopt(result, CURLOPT_LOW_SPEED_LIMIT,
@@ -1018,9 +1062,18 @@ static CURL *get_curl_handle(void)
                                CURLOPT_PROXYTYPE, CURLPROXY_SOCKS4);
 #endif
 #if LIBCURL_VERSION_NUM >= 0x073400
-               else if (starts_with(curl_http_proxy, "https"))
-                       curl_easy_setopt(result,
-                               CURLOPT_PROXYTYPE, CURLPROXY_HTTPS);
+               else if (starts_with(curl_http_proxy, "https")) {
+                       curl_easy_setopt(result, CURLOPT_PROXYTYPE, CURLPROXY_HTTPS);
+
+                       if (http_proxy_ssl_cert)
+                               curl_easy_setopt(result, CURLOPT_PROXY_SSLCERT, http_proxy_ssl_cert);
+
+                       if (http_proxy_ssl_key)
+                               curl_easy_setopt(result, CURLOPT_PROXY_SSLKEY, http_proxy_ssl_key);
+
+                       if (has_proxy_cert_password())
+                               curl_easy_setopt(result, CURLOPT_PROXY_KEYPASSWD, proxy_cert_auth.password);
+               }
 #endif
                if (strstr(curl_http_proxy, "://"))
                        credential_from_url(&proxy_auth, curl_http_proxy);
@@ -1160,6 +1213,13 @@ void http_init(struct remote *remote, const char *url, int proactive_auth)
                max_requests = DEFAULT_MAX_REQUESTS;
 #endif
 
+       set_from_env(&http_proxy_ssl_cert, "GIT_PROXY_SSL_CERT");
+       set_from_env(&http_proxy_ssl_key, "GIT_PROXY_SSL_KEY");
+       set_from_env(&http_proxy_ssl_ca_info, "GIT_PROXY_SSL_CAINFO");
+
+       if (getenv("GIT_PROXY_SSL_CERT_PASSWORD_PROTECTED"))
+               proxy_ssl_cert_password_required = 1;
+
        if (getenv("GIT_CURL_FTP_NO_EPSV"))
                curl_ftp_no_epsv = 1;
 
@@ -1230,6 +1290,12 @@ void http_cleanup(void)
        }
        ssl_cert_password_required = 0;
 
+       if (proxy_cert_auth.password != NULL) {
+               memset(proxy_cert_auth.password, 0, strlen(proxy_cert_auth.password));
+               FREE_AND_NULL(proxy_cert_auth.password);
+       }
+       proxy_ssl_cert_password_required = 0;
+
        FREE_AND_NULL(cached_accept_language);
 }
 
index 52127427ffeace62621a3761dc540a174671f4a5..897a90233e470f5314fa5b6607875b74b139479d 100644 (file)
@@ -449,22 +449,21 @@ 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;
+       struct signature_check sigc = { 0 };
        int status;
 
        if (parse_signed_commit(commit, &payload, &signature) <= 0)
                goto out;
 
-       status = verify_signed_buffer(payload.buf, payload.len,
-                                     signature.buf, signature.len,
-                                     &gpg_output, NULL);
-       if (status && !gpg_output.len)
-               strbuf_addstr(&gpg_output, "No signature\n");
-
-       show_sig_lines(opt, status, gpg_output.buf);
+       status = check_signature(payload.buf, payload.len, signature.buf,
+                                signature.len, &sigc);
+       if (status && !sigc.gpg_output)
+               show_sig_lines(opt, status, "No signature\n");
+       else
+               show_sig_lines(opt, status, sigc.gpg_output);
+       signature_check_clear(&sigc);
 
  out:
-       strbuf_release(&gpg_output);
        strbuf_release(&payload);
        strbuf_release(&signature);
 }
@@ -497,8 +496,9 @@ static int show_one_mergetag(struct commit *commit,
        struct object_id oid;
        struct tag *tag;
        struct strbuf verify_message;
+       struct signature_check sigc = { 0 };
        int status, nth;
-       size_t payload_size, gpg_message_offset;
+       size_t payload_size;
 
        hash_object_file(the_hash_algo, extra->value, extra->len,
                         type_name(OBJ_TAG), &oid);
@@ -520,19 +520,19 @@ static int show_one_mergetag(struct commit *commit,
        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);
        status = -1;
        if (extra->len > payload_size) {
                /* could have a good signature */
-               if (!verify_signed_buffer(extra->value, payload_size,
-                                         extra->value + payload_size,
-                                         extra->len - payload_size,
-                                         &verify_message, NULL))
-                       status = 0; /* good */
-               else if (verify_message.len <= gpg_message_offset)
+               status = check_signature(extra->value, payload_size,
+                                        extra->value + payload_size,
+                                        extra->len - payload_size, &sigc);
+               if (sigc.gpg_output)
+                       strbuf_addstr(&verify_message, sigc.gpg_output);
+               else
                        strbuf_addstr(&verify_message, "No signature\n");
+               signature_check_clear(&sigc);
                /* otherwise we couldn't verify, which is shown as bad */
        }
 
index 7a4e6f20fa8c8b163239d2872795e4d51a3d9b34..d92e2acf1ed2cfb4d4ce1e50fee3a64fb70b91ac 100644 (file)
@@ -958,7 +958,7 @@ static int update_file_flags(struct merge_options *opt,
                if (S_ISREG(contents->mode)) {
                        struct strbuf strbuf = STRBUF_INIT;
                        if (convert_to_working_tree(opt->repo->index,
-                                                   path, buf, size, &strbuf)) {
+                                                   path, buf, size, &strbuf, NULL)) {
                                free(buf);
                                size = strbuf.len;
                                buf = strbuf_detach(&strbuf, NULL);
diff --git a/merge.c b/merge.c
index 7c1d756c3fad6f9570b31c2c36f96e9f90df39f4..aa36de2f64a4e1195a274f5d50c471c6806d4db8 100644 (file)
--- a/merge.c
+++ b/merge.c
@@ -94,6 +94,7 @@ int checkout_fast_forward(struct repository *r,
        opts.verbose_update = 1;
        opts.merge = 1;
        opts.fn = twoway_merge;
+       init_checkout_metadata(&opts.meta, NULL, remote, NULL);
        setup_unpack_trees_porcelain(&opts, "merge");
 
        if (unpack_trees(nr_trees, t, &opts)) {
index 5346563b0bccb602b8de745d1308793e8995a5e5..d8a106b12751bd2ad44209a0c48b469205a9d443 100644 (file)
--- a/oidset.h
+++ b/oidset.h
@@ -1,7 +1,6 @@
 #ifndef OIDSET_H
 #define OIDSET_H
 
-#include "hashmap.h"
 #include "khash.h"
 
 /**
diff --git a/path.c b/path.c
index 88cf59300738a2f10750ff458fa35b1211f00ab8..9bd717c3073f4f4af8d69a14f18ec94f06079c6e 100644 (file)
--- a/path.c
+++ b/path.c
@@ -723,7 +723,7 @@ static struct passwd *getpw_str(const char *username, size_t len)
  * then it is a newly allocated string. Returns NULL on getpw failure or
  * if path is NULL.
  *
- * If real_home is true, real_path($HOME) is used in the expansion.
+ * If real_home is true, strbuf_realpath($HOME) is used in the expansion.
  */
 char *expand_user_path(const char *path, int real_home)
 {
@@ -850,8 +850,8 @@ const char *enter_repo(const char *path, int strict)
        }
 
        if (is_git_directory(".")) {
-               set_git_dir(".");
-               check_repository_format();
+               set_git_dir(".", 0);
+               check_repository_format(NULL);
                return path;
        }
 
index a703e407a3f0a9c55c9a18ad1db6bf345f1165dd..dc6817daa9537c3877dae686368ef58708ac0512 100644 (file)
@@ -45,6 +45,8 @@ void prepare_repo_settings(struct repository *r)
 
        if (!repo_config_get_bool(r, "pack.usesparse", &value))
                r->settings.pack_use_sparse = value;
+       UPDATE_DEFAULT_BOOL(r->settings.pack_use_sparse, 1);
+
        if (!repo_config_get_bool(r, "feature.manyfiles", &value) && value) {
                UPDATE_DEFAULT_BOOL(r->settings.index_version, 4);
                UPDATE_DEFAULT_BOOL(r->settings.core_untracked_cache, UNTRACKED_CACHE_WRITE);
@@ -52,7 +54,6 @@ void prepare_repo_settings(struct repository *r)
        if (!repo_config_get_bool(r, "fetch.writecommitgraph", &value))
                r->settings.fetch_write_commit_graph = value;
        if (!repo_config_get_bool(r, "feature.experimental", &value) && value) {
-               UPDATE_DEFAULT_BOOL(r->settings.pack_use_sparse, 1);
                UPDATE_DEFAULT_BOOL(r->settings.fetch_negotiation_algorithm, FETCH_NEGOTIATION_SKIPPING);
                UPDATE_DEFAULT_BOOL(r->settings.fetch_write_commit_graph, 1);
        }
index a4174ddb0629cdd690142fb42c5d4182492485c4..6f7f6f002b1a45faa52305ca8c370e2e959b2f5e 100644 (file)
@@ -89,6 +89,10 @@ void repo_set_gitdir(struct repository *repo,
 void repo_set_hash_algo(struct repository *repo, int hash_algo)
 {
        repo->hash_algo = &hash_algos[hash_algo];
+#ifndef ENABLE_SHA256
+       if (hash_algo != GIT_HASH_SHA1)
+               die(_("The hash algorithm %s is not supported in this build."), repo->hash_algo->name);
+#endif
 }
 
 /*
index e528225e787f4e26de604807efc798e8b967010e..6fd2674632bb2a524e22536e22658dbb3d06d6c3 100644 (file)
@@ -40,7 +40,7 @@ static const char cherry_picked_prefix[] = "(cherry picked from commit ";
 
 GIT_PATH_FUNC(git_path_commit_editmsg, "COMMIT_EDITMSG")
 
-GIT_PATH_FUNC(git_path_seq_dir, "sequencer")
+static GIT_PATH_FUNC(git_path_seq_dir, "sequencer")
 
 static GIT_PATH_FUNC(git_path_todo_file, "sequencer/todo")
 static GIT_PATH_FUNC(git_path_opts_file, "sequencer/opts")
@@ -1323,7 +1323,7 @@ static int try_to_commit(struct repository *r,
                return -1;
 
        if (flags & AMEND_MSG) {
-               const char *exclude_gpgsig[] = { "gpgsig", NULL };
+               const char *exclude_gpgsig[] = { "gpgsig", "gpgsig-sha256", NULL };
                const char *out_enc = get_commit_output_encoding();
                const char *message = logmsg_reencode(current_head, NULL,
                                                      out_enc);
@@ -1433,9 +1433,19 @@ out:
        return res;
 }
 
+static int write_rebase_head(struct object_id *oid)
+{
+       if (update_ref("rebase", "REBASE_HEAD", oid,
+                      NULL, REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR))
+               return error(_("could not update %s"), "REBASE_HEAD");
+
+       return 0;
+}
+
 static int do_commit(struct repository *r,
                     const char *msg_file, const char *author,
-                    struct replay_opts *opts, unsigned int flags)
+                    struct replay_opts *opts, unsigned int flags,
+                    struct object_id *oid)
 {
        int res = 1;
 
@@ -1460,8 +1470,12 @@ static int do_commit(struct repository *r,
                        return res;
                }
        }
-       if (res == 1)
+       if (res == 1) {
+               if (is_rebase_i(opts) && oid)
+                       if (write_rebase_head(oid))
+                           return -1;
                return run_git_commit(r, msg_file, opts, flags);
+       }
 
        return res;
 }
@@ -1929,7 +1943,9 @@ static int do_pick_commit(struct repository *r,
         * However, if the merge did not even start, then we don't want to
         * write it at all.
         */
-       if (command == TODO_PICK && !opts->no_commit && (res == 0 || res == 1) &&
+       if ((command == TODO_PICK || command == TODO_REWORD ||
+            command == TODO_EDIT) && !opts->no_commit &&
+           (res == 0 || res == 1) &&
            update_ref(NULL, "CHERRY_PICK_HEAD", &commit->object.oid, NULL,
                       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR))
                res = -1;
@@ -1965,7 +1981,8 @@ static int do_pick_commit(struct repository *r,
        } /* else allow == 0 and there's nothing special to do */
        if (!opts->no_commit && !drop_commit) {
                if (author || command == TODO_REVERT || (flags & AMEND_MSG))
-                       res = do_commit(r, msg_file, author, opts, flags);
+                       res = do_commit(r, msg_file, author, opts, flags,
+                                       commit? &commit->object.oid : NULL);
                else
                        res = error(_("unable to parse commit author"));
                *check_todo = !!(flags & EDIT_MSG);
@@ -3000,9 +3017,7 @@ static int make_patch(struct repository *r,
        p = short_commit_name(commit);
        if (write_message(p, strlen(p), rebase_path_stopped_sha(), 1) < 0)
                return -1;
-       if (update_ref("rebase", "REBASE_HEAD", &commit->object.oid,
-                      NULL, REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR))
-               res |= error(_("could not update %s"), "REBASE_HEAD");
+       res |= write_rebase_head(&commit->object.oid);
 
        strbuf_addf(&buf, "%s/patch", get_dir(opts));
        memset(&log_tree_opt, 0, sizeof(log_tree_opt));
@@ -3290,6 +3305,7 @@ static int do_reset(struct repository *r,
        unpack_tree_opts.fn = oneway_merge;
        unpack_tree_opts.merge = 1;
        unpack_tree_opts.update = 1;
+       init_checkout_metadata(&unpack_tree_opts.meta, name, &oid, NULL);
 
        if (repo_read_index_unmerged(r)) {
                rollback_lock_file(&lock);
@@ -5315,3 +5331,24 @@ int todo_list_rearrange_squash(struct todo_list *todo_list)
 
        return 0;
 }
+
+int sequencer_determine_whence(struct repository *r, enum commit_whence *whence)
+{
+       if (file_exists(git_path_cherry_pick_head(r))) {
+               struct object_id cherry_pick_head, rebase_head;
+
+               if (file_exists(git_path_seq_dir()))
+                       *whence = FROM_CHERRY_PICK_MULTI;
+               if (file_exists(rebase_path()) &&
+                   !get_oid("REBASE_HEAD", &rebase_head) &&
+                   !get_oid("CHERRY_PICK_HEAD", &cherry_pick_head) &&
+                   oideq(&rebase_head, &cherry_pick_head))
+                       *whence = FROM_REBASE_PICK;
+               else
+                       *whence = FROM_CHERRY_PICK_SINGLE;
+
+               return 1;
+       }
+
+       return 0;
+}
index 718a07426dae930dc07417f902fa1ad5682eb337..0bee85093e5391ad8bf111f20194c03e7d1b2774 100644 (file)
@@ -3,12 +3,12 @@
 
 #include "cache.h"
 #include "strbuf.h"
+#include "wt-status.h"
 
 struct commit;
 struct repository;
 
 const char *git_path_commit_editmsg(void);
-const char *git_path_seq_dir(void);
 const char *rebase_path_todo(void);
 const char *rebase_path_todo_backup(void);
 const char *rebase_path_dropped(void);
@@ -206,4 +206,5 @@ int write_basic_state(struct replay_opts *opts, const char *head_name,
 void sequencer_post_commit_cleanup(struct repository *r, int verbose);
 int sequencer_get_last_command(struct repository* r,
                               enum replay_action *action);
+int sequencer_determine_whence(struct repository *r, enum commit_whence *whence);
 #endif /* SEQUENCER_H */
diff --git a/setup.c b/setup.c
index 5ea9285a128c019018e48db1abea577f553f1785..65fe5ecefbe196eba04070978bc668b9af17257d 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -32,6 +32,7 @@ static int abspath_part_inside_repo(char *path)
        char *path0;
        int off;
        const char *work_tree = get_git_work_tree();
+       struct strbuf realpath = STRBUF_INIT;
 
        if (!work_tree)
                return -1;
@@ -60,8 +61,10 @@ static int abspath_part_inside_repo(char *path)
                path++;
                if (*path == '/') {
                        *path = '\0';
-                       if (fspathcmp(real_path(path0), work_tree) == 0) {
+                       strbuf_realpath(&realpath, path0, 1);
+                       if (fspathcmp(realpath.buf, work_tree) == 0) {
                                memmove(path0, path + 1, len - (path - path0));
+                               strbuf_release(&realpath);
                                return 0;
                        }
                        *path = '/';
@@ -69,11 +72,14 @@ static int abspath_part_inside_repo(char *path)
        }
 
        /* check whole path */
-       if (fspathcmp(real_path(path0), work_tree) == 0) {
+       strbuf_realpath(&realpath, path0, 1);
+       if (fspathcmp(realpath.buf, work_tree) == 0) {
                *path0 = '\0';
+               strbuf_release(&realpath);
                return 0;
        }
 
+       strbuf_release(&realpath);
        return -1;
 }
 
@@ -623,6 +629,7 @@ const char *read_gitfile_gently(const char *path, int *return_error_code)
        struct stat st;
        int fd;
        ssize_t len;
+       static struct strbuf realpath = STRBUF_INIT;
 
        if (stat(path, &st)) {
                /* NEEDSWORK: discern between ENOENT vs other errors */
@@ -673,7 +680,9 @@ const char *read_gitfile_gently(const char *path, int *return_error_code)
                error_code = READ_GITFILE_ERR_NOT_A_REPO;
                goto cleanup_return;
        }
-       path = real_path(dir);
+
+       strbuf_realpath(&realpath, dir, 1);
+       path = realpath.buf;
 
 cleanup_return:
        if (return_error_code)
@@ -729,7 +738,7 @@ static const char *setup_explicit_git_dir(const char *gitdirenv,
                }
 
                /* #18, #26 */
-               set_git_dir(gitdirenv);
+               set_git_dir(gitdirenv, 0);
                free(gitfile);
                return NULL;
        }
@@ -751,7 +760,7 @@ static const char *setup_explicit_git_dir(const char *gitdirenv,
        }
        else if (!git_env_bool(GIT_IMPLICIT_WORK_TREE_ENVIRONMENT, 1)) {
                /* #16d */
-               set_git_dir(gitdirenv);
+               set_git_dir(gitdirenv, 0);
                free(gitfile);
                return NULL;
        }
@@ -763,14 +772,14 @@ static const char *setup_explicit_git_dir(const char *gitdirenv,
 
        /* both get_git_work_tree() and cwd are already normalized */
        if (!strcmp(cwd->buf, worktree)) { /* cwd == worktree */
-               set_git_dir(gitdirenv);
+               set_git_dir(gitdirenv, 0);
                free(gitfile);
                return NULL;
        }
 
        offset = dir_inside_of(cwd->buf, worktree);
        if (offset >= 0) {      /* cwd inside worktree? */
-               set_git_dir(real_path(gitdirenv));
+               set_git_dir(gitdirenv, 1);
                if (chdir(worktree))
                        die_errno(_("cannot chdir to '%s'"), worktree);
                strbuf_addch(cwd, '/');
@@ -779,7 +788,7 @@ static const char *setup_explicit_git_dir(const char *gitdirenv,
        }
 
        /* cwd outside worktree */
-       set_git_dir(gitdirenv);
+       set_git_dir(gitdirenv, 0);
        free(gitfile);
        return NULL;
 }
@@ -808,7 +817,7 @@ static const char *setup_discovered_git_dir(const char *gitdir,
 
        /* #16.2, #17.2, #20.2, #21.2, #24, #25, #28, #29 (see t1510) */
        if (is_bare_repository_cfg > 0) {
-               set_git_dir(offset == cwd->len ? gitdir : real_path(gitdir));
+               set_git_dir(gitdir, (offset != cwd->len));
                if (chdir(cwd->buf))
                        die_errno(_("cannot come back to cwd"));
                return NULL;
@@ -817,7 +826,7 @@ static const char *setup_discovered_git_dir(const char *gitdir,
        /* #0, #1, #5, #8, #9, #12, #13 */
        set_git_work_tree(".");
        if (strcmp(gitdir, DEFAULT_GIT_DIR_ENVIRONMENT))
-               set_git_dir(gitdir);
+               set_git_dir(gitdir, 0);
        inside_git_dir = 0;
        inside_work_tree = 1;
        if (offset >= cwd->len)
@@ -860,10 +869,10 @@ static const char *setup_bare_git_dir(struct strbuf *cwd, int offset,
                        die_errno(_("cannot come back to cwd"));
                root_len = offset_1st_component(cwd->buf);
                strbuf_setlen(cwd, offset > root_len ? offset : root_len);
-               set_git_dir(cwd->buf);
+               set_git_dir(cwd->buf, 0);
        }
        else
-               set_git_dir(".");
+               set_git_dir(".", 0);
        return NULL;
 }
 
@@ -881,7 +890,7 @@ static dev_t get_device_or_die(const char *path, const char *prefix, int prefix_
 
 /*
  * A "string_list_each_func_t" function that canonicalizes an entry
- * from GIT_CEILING_DIRECTORIES using real_path_if_valid(), or
+ * from GIT_CEILING_DIRECTORIES using real_pathdup(), or
  * discards it if unusable.  The presence of an empty entry in
  * GIT_CEILING_DIRECTORIES turns off canonicalization for all
  * subsequent entries.
@@ -1257,10 +1266,12 @@ int git_config_perm(const char *var, const char *value)
        return -(i & 0666);
 }
 
-void check_repository_format(void)
+void check_repository_format(struct repository_format *fmt)
 {
        struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;
-       check_repository_format_gently(get_git_dir(), &repo_fmt, NULL);
+       if (!fmt)
+               fmt = &repo_fmt;
+       check_repository_format_gently(get_git_dir(), fmt, NULL);
        startup_info->have_repository = 1;
        clear_repository_format(&repo_fmt);
 }
index 616886799e5906988ac4834d71cd259ee1e540a8..69268517242802283ce28a24fcbc49be2b8bdfd9 100644 (file)
@@ -74,6 +74,11 @@ static void git_hash_sha1_init(git_hash_ctx *ctx)
        git_SHA1_Init(&ctx->sha1);
 }
 
+static void git_hash_sha1_clone(git_hash_ctx *dst, const git_hash_ctx *src)
+{
+       git_SHA1_Clone(&dst->sha1, &src->sha1);
+}
+
 static void git_hash_sha1_update(git_hash_ctx *ctx, const void *data, size_t len)
 {
        git_SHA1_Update(&ctx->sha1, data, len);
@@ -90,6 +95,11 @@ static void git_hash_sha256_init(git_hash_ctx *ctx)
        git_SHA256_Init(&ctx->sha256);
 }
 
+static void git_hash_sha256_clone(git_hash_ctx *dst, const git_hash_ctx *src)
+{
+       git_SHA256_Clone(&dst->sha256, &src->sha256);
+}
+
 static void git_hash_sha256_update(git_hash_ctx *ctx, const void *data, size_t len)
 {
        git_SHA256_Update(&ctx->sha256, data, len);
@@ -105,6 +115,11 @@ static void git_hash_unknown_init(git_hash_ctx *ctx)
        BUG("trying to init unknown hash");
 }
 
+static void git_hash_unknown_clone(git_hash_ctx *dst, const git_hash_ctx *src)
+{
+       BUG("trying to clone unknown hash");
+}
+
 static void git_hash_unknown_update(git_hash_ctx *ctx, const void *data, size_t len)
 {
        BUG("trying to update unknown hash");
@@ -123,6 +138,7 @@ const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = {
                0,
                0,
                git_hash_unknown_init,
+               git_hash_unknown_clone,
                git_hash_unknown_update,
                git_hash_unknown_final,
                NULL,
@@ -136,6 +152,7 @@ const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = {
                GIT_SHA1_HEXSZ,
                GIT_SHA1_BLKSZ,
                git_hash_sha1_init,
+               git_hash_sha1_clone,
                git_hash_sha1_update,
                git_hash_sha1_final,
                &empty_tree_oid,
@@ -149,6 +166,7 @@ const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = {
                GIT_SHA256_HEXSZ,
                GIT_SHA256_BLKSZ,
                git_hash_sha256_init,
+               git_hash_sha256_clone,
                git_hash_sha256_update,
                git_hash_sha256_final,
                &empty_tree_oid_sha256,
@@ -676,20 +694,15 @@ void add_to_alternates_memory(const char *reference)
 char *compute_alternate_path(const char *path, struct strbuf *err)
 {
        char *ref_git = NULL;
-       const char *repo, *ref_git_s;
+       const char *repo;
        int seen_error = 0;
 
-       ref_git_s = real_path_if_valid(path);
-       if (!ref_git_s) {
+       ref_git = real_pathdup(path, 0);
+       if (!ref_git) {
                seen_error = 1;
                strbuf_addf(err, _("path '%s' does not exist"), path);
                goto out;
-       } else
-               /*
-                * Beware: read_gitfile(), real_path() and mkpath()
-                * return static buffer
-                */
-               ref_git = xstrdup(ref_git_s);
+       }
 
        repo = read_gitfile(ref_git);
        if (!repo)
index 09bd8bb20062ae6047a9680162cc761ad7ca8531..501da5ed9197ec61497e244879c7f60ba58617c1 100644 (file)
@@ -22,8 +22,14 @@ inline void gcrypt_SHA256_Final(unsigned char *digest, gcrypt_SHA256_CTX *ctx)
        memcpy(digest, gcry_md_read(*ctx, GCRY_MD_SHA256), SHA256_DIGEST_SIZE);
 }
 
+inline void gcrypt_SHA256_Clone(gcrypt_SHA256_CTX *dst, const gcrypt_SHA256_CTX *src)
+{
+       gcry_md_copy(dst, *src);
+}
+
 #define platform_SHA256_CTX gcrypt_SHA256_CTX
 #define platform_SHA256_Init gcrypt_SHA256_Init
+#define platform_SHA256_Clone gcrypt_SHA256_Clone
 #define platform_SHA256_Update gcrypt_SHA256_Update
 #define platform_SHA256_Final gcrypt_SHA256_Final
 
index 31f391d7d2541c1498387248e669248de3430b5d..c3aadf3fff8c450c53a286c15f0b1528100779c1 100644 (file)
@@ -2168,13 +2168,13 @@ void absorb_git_dir_into_superproject(const char *path,
        }
 }
 
-const char *get_superproject_working_tree(void)
+int get_superproject_working_tree(struct strbuf *buf)
 {
        struct child_process cp = CHILD_PROCESS_INIT;
        struct strbuf sb = STRBUF_INIT;
-       const char *one_up = real_path_if_valid("../");
+       struct strbuf one_up = STRBUF_INIT;
        const char *cwd = xgetcwd();
-       const char *ret = NULL;
+       int ret = 0;
        const char *subpath;
        int code;
        ssize_t len;
@@ -2185,12 +2185,13 @@ const char *get_superproject_working_tree(void)
                 * We might have a superproject, but it is harder
                 * to determine.
                 */
-               return NULL;
+               return 0;
 
-       if (!one_up)
-               return NULL;
+       if (!strbuf_realpath(&one_up, "../", 0))
+               return 0;
 
-       subpath = relative_path(cwd, one_up, &sb);
+       subpath = relative_path(cwd, one_up.buf, &sb);
+       strbuf_release(&one_up);
 
        prepare_submodule_repo_env(&cp.env_array);
        argv_array_pop(&cp.env_array);
@@ -2231,7 +2232,8 @@ const char *get_superproject_working_tree(void)
                super_wt = xstrdup(cwd);
                super_wt[cwd_len - super_sub_len] = '\0';
 
-               ret = real_path(super_wt);
+               strbuf_realpath(buf, super_wt, 1);
+               ret = 1;
                free(super_wt);
        }
        strbuf_release(&sb);
@@ -2240,10 +2242,10 @@ const char *get_superproject_working_tree(void)
 
        if (code == 128)
                /* '../' is not a git repository */
-               return NULL;
+               return 0;
        if (code == 0 && len == 0)
                /* There is an unrelated git repository at '../' */
-               return NULL;
+               return 0;
        if (code)
                die(_("ls-tree returned unexpected return code %d"), code);
 
index c81ec1a9b6c8ff3061c8119ad68b32f850314d9b..4dad649f94220e3897e629a5737da5f08705a5fb 100644 (file)
@@ -152,8 +152,8 @@ void absorb_git_dir_into_superproject(const char *path,
 /*
  * Return the absolute path of the working tree of the superproject, which this
  * project is a submodule of. If this repository is not a submodule of
- * another repository, return NULL.
+ * another repository, return 0.
  */
-const char *get_superproject_working_tree(void);
+int get_superproject_working_tree(struct strbuf *buf);
 
 #endif
index 080bc59fc49ffc1aec71940473bec996dfa23b2f..d12efcd3a42e604e5c3aa09c9df2e92740ac2142 100644 (file)
--- a/t/README
+++ b/t/README
@@ -387,17 +387,13 @@ GIT_TEST_INDEX_VERSION=<n> exercises the index read/write code path
 for the index version specified.  Can be set to any valid version
 (currently 2, 3, or 4).
 
-GIT_TEST_PACK_SPARSE=<boolean> if enabled will default the pack-objects
-builtin to use the sparse object walk. This can still be overridden by
-the --no-sparse command-line argument.
+GIT_TEST_PACK_SPARSE=<boolean> if disabled will default the pack-objects
+builtin to use the non-sparse object walk. This can still be overridden by
+the --sparse command-line argument.
 
 GIT_TEST_PRELOAD_INDEX=<boolean> exercises the preload-index code path
 by overriding the minimum number of cache entries required per thread.
 
-GIT_TEST_STASH_USE_BUILTIN=<boolean>, when false, disables the
-built-in version of git-stash. See 'stash.useBuiltin' in
-git-config(1).
-
 GIT_TEST_ADD_I_USE_BUILTIN=<boolean>, when true, enables the
 built-in version of git add -i. See 'add.interactive.useBuiltin' in
 git-config(1).
diff --git a/t/helper/test-advise.c b/t/helper/test-advise.c
new file mode 100644 (file)
index 0000000..38cdc28
--- /dev/null
@@ -0,0 +1,22 @@
+#include "test-tool.h"
+#include "cache.h"
+#include "advice.h"
+#include "config.h"
+
+int cmd__advise_if_enabled(int argc, const char **argv)
+{
+       if (!argv[1])
+       die("usage: %s <advice>", argv[0]);
+
+       setup_git_directory();
+       git_config(git_default_config, NULL);
+
+       /*
+        * Any advice type can be used for testing, but NESTED_TAG was
+        * selected here and in t0018 where this command is being
+        * executed.
+        */
+       advise_if_enabled(ADVICE_NESTED_TAG, argv[1]);
+
+       return 0;
+}
index 63c689d6ee9d018efd32aa228ee3cf784fa5b71d..a209880eb37a2ce4de4b9f916ec3dbf52b987957 100644 (file)
@@ -13,6 +13,8 @@ int cmd__dump_split_index(int ac, const char **av)
        struct split_index *si;
        int i;
 
+       setup_git_directory();
+
        do_read_index(&the_index, av[1], 1);
        printf("own %s\n", oid_to_hex(&the_index.oid));
        si = the_index.split_index;
index 409034cf4eef59363653b39cf3aae3fc37c07856..313a153209c48f9001f122fa61f6c42c39ac02f8 100644 (file)
@@ -290,11 +290,14 @@ int cmd__path_utils(int argc, const char **argv)
        }
 
        if (argc >= 2 && !strcmp(argv[1], "real_path")) {
+               struct strbuf realpath = STRBUF_INIT;
                while (argc > 2) {
-                       puts(real_path(argv[2]));
+                       strbuf_realpath(&realpath, argv[2], 1);
+                       puts(realpath.buf);
                        argc--;
                        argv++;
                }
+               strbuf_release(&realpath);
                return 0;
        }
 
index f7f861844560e0578eda989e39c4bd0572dcd5d6..56f0e3c1bef293dd505e28af4b48dfce11490b3c 100644 (file)
@@ -19,12 +19,11 @@ static void test_parse_commit_in_graph(const char *gitdir, const char *worktree,
 
        memset(the_repository, 0, sizeof(*the_repository));
 
-       /* TODO: Needed for temporary hack in hashcmp, see 183a638b7da. */
-       repo_set_hash_algo(the_repository, GIT_HASH_SHA1);
-
        if (repo_init(&r, gitdir, worktree))
                die("Couldn't init repo");
 
+       repo_set_hash_algo(the_repository, hash_algo_by_ptr(r.hash_algo));
+
        c = lookup_commit(&r, commit_oid);
 
        if (!parse_commit_in_graph(&r, c))
@@ -50,12 +49,11 @@ static void test_get_commit_tree_in_graph(const char *gitdir,
 
        memset(the_repository, 0, sizeof(*the_repository));
 
-       /* TODO: Needed for temporary hack in hashcmp, see 183a638b7da. */
-       repo_set_hash_algo(the_repository, GIT_HASH_SHA1);
-
        if (repo_init(&r, gitdir, worktree))
                die("Couldn't init repo");
 
+       repo_set_hash_algo(the_repository, hash_algo_by_ptr(r.hash_algo));
+
        c = lookup_commit(&r, commit_oid);
 
        /*
@@ -75,6 +73,10 @@ static void test_get_commit_tree_in_graph(const char *gitdir,
 
 int cmd__repository(int argc, const char **argv)
 {
+       int nongit_ok = 0;
+
+       setup_git_directory_gently(&nongit_ok);
+
        if (argc < 2)
                die("must have at least 2 arguments");
        if (!strcmp(argv[1], "parse_commit_in_graph")) {
index c9a232d23897b70b051bfd76488ca0e971830b83..31eedcd241fbe24d2dfcbe1931462e3994a9f7ab 100644 (file)
@@ -14,6 +14,7 @@ struct test_cmd {
 };
 
 static struct test_cmd cmds[] = {
+       { "advise", cmd__advise_if_enabled },
        { "chmtime", cmd__chmtime },
        { "config", cmd__config },
        { "ctype", cmd__ctype },
index c8549fd87f23b6372a41b2ad5d7ba28df3e258c7..4eb5e6609e17dfced0f3fd07e8c2f59c9ab915de 100644 (file)
@@ -4,6 +4,7 @@
 #define USE_THE_INDEX_COMPATIBILITY_MACROS
 #include "git-compat-util.h"
 
+int cmd__advise_if_enabled(int argc, const char **argv);
 int cmd__chmtime(int argc, const char **argv);
 int cmd__config(int argc, const char **argv);
 int cmd__ctype(int argc, const char **argv);
index 937b831ea675230c1e0fbe8ba7ebb3004de71b3b..bb88cc01087ba153ff57e856764c59d39b0f320f 100755 (executable)
@@ -19,7 +19,7 @@ check() {
                false
        fi &&
        test_cmp expect-stdout stdout &&
-       test_cmp expect-stderr stderr
+       test_i18ncmp expect-stderr stderr
 }
 
 read_chunk() {
index 8d28652b729b46cb08de8ddd4347dddda9e246a7..9fc5241228e800bd237d48018989c6aed3ef942b 100755 (executable)
@@ -1,14 +1,25 @@
-#!/bin/sh
+# We always set GNUPGHOME, even if no usable GPG was found, as
+#
+# - It does not hurt, and
+#
+# - we cannot set global environment variables in lazy prereqs because they are
+#   executed in an eval'ed subshell that changes the working directory to a
+#   temporary one.
+
+GNUPGHOME="$PWD/gpghome"
+export GNUPGHOME
+
+test_lazy_prereq GPG '
+       gpg_version=$(gpg --version 2>&1)
+       test $? != 127 || exit 1
 
-gpg_version=$(gpg --version 2>&1)
-if test $? != 127
-then
        # As said here: http://www.gnupg.org/documentation/faqs.html#q6.19
-       # the gpg version 1.0.6 didn't parse trust packets correctly, so for
+       # the gpg version 1.0.6 did not parse trust packets correctly, so for
        # that version, creation of signed tags using the generated key fails.
        case "$gpg_version" in
-       'gpg (GnuPG) 1.0.6'*)
+       "gpg (GnuPG) 1.0.6"*)
                say "Your version of gpg (1.0.6) is too buggy for testing"
+               exit 1
                ;;
        *)
                # Available key info:
@@ -27,55 +38,54 @@ then
                # To export ownertrust:
                #       gpg --homedir /tmp/gpghome --export-ownertrust \
                #               > lib-gpg/ownertrust
-               mkdir ./gpghome &&
-               chmod 0700 ./gpghome &&
-               GNUPGHOME="$(pwd)/gpghome" &&
-               export GNUPGHOME &&
-               (gpgconf --kill gpg-agent >/dev/null 2>&1 || : ) &&
-               gpg --homedir "${GNUPGHOME}" 2>/dev/null --import \
+               mkdir "$GNUPGHOME" &&
+               chmod 0700 "$GNUPGHOME" &&
+               (gpgconf --kill gpg-agent || : ) &&
+               gpg --homedir "${GNUPGHOME}" --import \
                        "$TEST_DIRECTORY"/lib-gpg/keyring.gpg &&
-               gpg --homedir "${GNUPGHOME}" 2>/dev/null --import-ownertrust \
+               gpg --homedir "${GNUPGHOME}" --import-ownertrust \
                        "$TEST_DIRECTORY"/lib-gpg/ownertrust &&
-               gpg --homedir "${GNUPGHOME}" </dev/null >/dev/null 2>&1 \
-                       --sign -u committer@example.com &&
-               test_set_prereq GPG &&
-               # Available key info:
-               # * see t/lib-gpg/gpgsm-gen-key.in
-               # To generate new certificate:
-               #  * no passphrase
-               #       gpgsm --homedir /tmp/gpghome/ \
-               #               -o /tmp/gpgsm.crt.user \
-               #               --generate-key \
-               #               --batch t/lib-gpg/gpgsm-gen-key.in
-               # To import certificate:
-               #       gpgsm --homedir /tmp/gpghome/ \
-               #               --import /tmp/gpgsm.crt.user
-               # To export into a .p12 we can later import:
-               #       gpgsm --homedir /tmp/gpghome/ \
-               #               -o t/lib-gpg/gpgsm_cert.p12 \
-               #               --export-secret-key-p12 "committer@example.com"
-               echo | gpgsm --homedir "${GNUPGHOME}" 2>/dev/null \
-                       --passphrase-fd 0 --pinentry-mode loopback \
-                       --import "$TEST_DIRECTORY"/lib-gpg/gpgsm_cert.p12 &&
-
-               gpgsm --homedir "${GNUPGHOME}" 2>/dev/null -K |
-               grep fingerprint: |
-               cut -d" " -f4 |
-               tr -d '\n' >"${GNUPGHOME}/trustlist.txt" &&
-
-               echo " S relax" >>"${GNUPGHOME}/trustlist.txt" &&
-               echo hello | gpgsm --homedir "${GNUPGHOME}" >/dev/null \
-                       -u committer@example.com -o /dev/null --sign - 2>&1 &&
-               test_set_prereq GPGSM
+               gpg --homedir "${GNUPGHOME}" </dev/null >/dev/null \
+                       --sign -u committer@example.com
                ;;
        esac
-fi
+'
+
+test_lazy_prereq GPGSM '
+       test_have_prereq GPG &&
+       # Available key info:
+       # * see t/lib-gpg/gpgsm-gen-key.in
+       # To generate new certificate:
+       #  * no passphrase
+       #       gpgsm --homedir /tmp/gpghome/ \
+       #               -o /tmp/gpgsm.crt.user \
+       #               --generate-key \
+       #               --batch t/lib-gpg/gpgsm-gen-key.in
+       # To import certificate:
+       #       gpgsm --homedir /tmp/gpghome/ \
+       #               --import /tmp/gpgsm.crt.user
+       # To export into a .p12 we can later import:
+       #       gpgsm --homedir /tmp/gpghome/ \
+       #               -o t/lib-gpg/gpgsm_cert.p12 \
+       #               --export-secret-key-p12 "committer@example.com"
+       echo | gpgsm --homedir "${GNUPGHOME}" \
+               --passphrase-fd 0 --pinentry-mode loopback \
+               --import "$TEST_DIRECTORY"/lib-gpg/gpgsm_cert.p12 &&
+
+       gpgsm --homedir "${GNUPGHOME}" -K |
+       grep fingerprint: |
+       cut -d" " -f4 |
+       tr -d "\\n" >"${GNUPGHOME}/trustlist.txt" &&
+
+       echo " S relax" >>"${GNUPGHOME}/trustlist.txt" &&
+       echo hello | gpgsm --homedir "${GNUPGHOME}" >/dev/null \
+              -u committer@example.com -o /dev/null --sign -
+'
 
-if test_have_prereq GPG &&
-    echo | gpg --homedir "${GNUPGHOME}" -b --rfc1991 >/dev/null 2>&1
-then
-       test_set_prereq RFC1991
-fi
+test_lazy_prereq RFC1991 '
+       test_have_prereq GPG &&
+       echo | gpg --homedir "${GNUPGHOME}" -b --rfc1991 >/dev/null
+'
 
 sanitize_pgp() {
        perl -ne '
index 1dd17fc03e1203ba54bcfc45746033271172ad9c..64fc6487dd97b2b81dfa4ee718912ee02a8cf496 100755 (executable)
@@ -297,7 +297,7 @@ test_submodule_content () {
 # - Directory containing tracked files replaced by submodule
 # - Submodule replaced by tracked files in directory
 # - Submodule replaced by tracked file with the same name
-# - tracked file replaced by submodule
+# - Tracked file replaced by submodule
 #
 # The default is that submodule contents aren't changed until "git submodule
 # update" is run. And even then that command doesn't delete the work tree of
@@ -621,11 +621,13 @@ test_submodule_forced_switch () {
 # - Directory containing tracked files replaced by submodule
 # - Submodule replaced by tracked files in directory
 # - Submodule replaced by tracked file with the same name
-# - tracked file replaced by submodule
+# - Tracked file replaced by submodule
 #
 # New test cases
 # - Removing a submodule with a git directory absorbs the submodules
 #   git directory first into the superproject.
+# - Switching from no submodule to nested submodules
+# - Switching from nested submodules to no submodule
 
 # Internal function; use test_submodule_switch_recursing_with_args() or
 # test_submodule_forced_switch_recursing_with_args() instead.
@@ -658,22 +660,6 @@ test_submodule_recursing_with_args_common() {
                        test_submodule_content sub1 origin/add_sub1
                )
        '
-       test_expect_success "$command: submodule branch is not changed, detach HEAD instead" '
-               prolog &&
-               reset_work_tree_to_interested add_sub1 &&
-               (
-                       cd submodule_update &&
-                       git -C sub1 checkout -b keep_branch &&
-                       git -C sub1 rev-parse HEAD >expect &&
-                       git branch -t modify_sub1 origin/modify_sub1 &&
-                       $command modify_sub1 &&
-                       test_superproject_content origin/modify_sub1 &&
-                       test_submodule_content sub1 origin/modify_sub1 &&
-                       git -C sub1 rev-parse keep_branch >actual &&
-                       test_cmp expect actual &&
-                       test_must_fail git -C sub1 symbolic-ref HEAD
-               )
-       '
 
        # Replacing a tracked file with a submodule produces a checked out submodule
        test_expect_success "$command: replace tracked file with submodule checks out submodule" '
@@ -699,6 +685,19 @@ test_submodule_recursing_with_args_common() {
                        test_submodule_content sub1 origin/replace_directory_with_sub1
                )
        '
+       # Switching to a commit with nested submodules recursively checks them out
+       test_expect_success "$command: nested submodules are checked out" '
+               prolog &&
+               reset_work_tree_to_interested no_submodule &&
+               (
+                       cd submodule_update &&
+                       git branch -t modify_sub1_recursively origin/modify_sub1_recursively &&
+                       $command modify_sub1_recursively &&
+                       test_superproject_content origin/modify_sub1_recursively &&
+                       test_submodule_content sub1 origin/modify_sub1_recursively &&
+                       test_submodule_content -C sub1 sub2 origin/modify_sub1_recursively
+               )
+       '
 
        ######################## Disappearing submodule #######################
        # Removing a submodule removes its work tree ...
@@ -762,6 +761,21 @@ test_submodule_recursing_with_args_common() {
                )
        '
 
+       # Switching to a commit without nested submodules removes their worktrees
+       test_expect_success "$command: worktrees of nested submodules are removed" '
+               prolog &&
+               reset_work_tree_to_interested add_nested_sub &&
+               (
+                       cd submodule_update &&
+                       git branch -t no_submodule origin/no_submodule &&
+                       $command no_submodule &&
+                       test_superproject_content origin/no_submodule &&
+                       ! test_path_is_dir sub1 &&
+                       test_must_fail git config -f .git/modules/sub1/config core.worktree &&
+                       test_must_fail git config -f .git/modules/sub1/modules/sub2/config core.worktree
+               )
+       '
+
        ########################## Modified submodule #########################
        # Updating a submodule sha1 updates the submodule's work tree
        test_expect_success "$command: modified submodule updates submodule work tree" '
@@ -789,6 +803,23 @@ test_submodule_recursing_with_args_common() {
                        test_submodule_content sub1 origin/add_sub1
                )
        '
+       # Updating a submodule does not touch the currently checked out branch in the submodule
+       test_expect_success "$command: submodule branch is not changed, detach HEAD instead" '
+               prolog &&
+               reset_work_tree_to_interested add_sub1 &&
+               (
+                       cd submodule_update &&
+                       git -C sub1 checkout -b keep_branch &&
+                       git -C sub1 rev-parse HEAD >expect &&
+                       git branch -t modify_sub1 origin/modify_sub1 &&
+                       $command modify_sub1 &&
+                       test_superproject_content origin/modify_sub1 &&
+                       test_submodule_content sub1 origin/modify_sub1 &&
+                       git -C sub1 rev-parse keep_branch >actual &&
+                       test_cmp expect actual &&
+                       test_must_fail git -C sub1 symbolic-ref HEAD
+               )
+       '
 }
 
 # Declares and invokes several tests that, in various situations, checks that
@@ -908,7 +939,6 @@ test_submodule_switch_recursing_with_args () {
                )
        '
 
-       # recursing deeper than one level doesn't work yet.
        test_expect_success "$command: modified submodule updates submodule recursively" '
                prolog &&
                reset_work_tree_to_interested add_nested_sub &&
index 3e440c078d5752620a9ce9dc91f61e9441cb3665..b85972162003847481f73c280e074e5bcd2c0e91 100755 (executable)
@@ -833,6 +833,19 @@ then
        exit 1
 fi
 
+test_expect_success 'lazy prereqs do not turn off tracing' "
+       run_sub_test_lib_test lazy-prereq-and-tracing \
+               'lazy prereqs and -x' -v -x <<-\\EOF &&
+       test_lazy_prereq LAZY true
+
+       test_expect_success lazy 'test_have_prereq LAZY && echo trace'
+
+       test_done
+       EOF
+
+       grep 'echo trace' lazy-prereq-and-tracing/err
+"
+
 test_expect_success 'tests clean up even on failures' "
        run_sub_test_lib_test_err \
                failing-cleanup 'Failing tests with cleanup commands' <<-\\EOF &&
diff --git a/t/t0018-advice.sh b/t/t0018-advice.sh
new file mode 100755 (executable)
index 0000000..e03554d
--- /dev/null
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+test_description='Test advise_if_enabled functionality'
+
+. ./test-lib.sh
+
+test_expect_success 'advice should be printed when config variable is unset' '
+       cat >expect <<-\EOF &&
+       hint: This is a piece of advice
+       hint: Disable this message with "git config advice.nestedTag false"
+       EOF
+       test-tool advise "This is a piece of advice" 2>actual &&
+       test_i18ncmp expect actual
+'
+
+test_expect_success 'advice should be printed when config variable is set to true' '
+       cat >expect <<-\EOF &&
+       hint: This is a piece of advice
+       hint: Disable this message with "git config advice.nestedTag false"
+       EOF
+       test_config advice.nestedTag true &&
+       test-tool advise "This is a piece of advice" 2>actual &&
+       test_i18ncmp expect actual
+'
+
+test_expect_success 'advice should not be printed when config variable is set to false' '
+       test_config advice.nestedTag false &&
+       test-tool advise "This is a piece of advice" 2>actual &&
+       test_must_be_empty actual
+'
+
+test_done
index dc664da551a3e9711df96f7291df4a1c8070d4d7..4bfffa9c314bf8236c9c7d74fca166ed86d5952a 100755 (executable)
@@ -364,6 +364,10 @@ test_expect_success PERL 'required process filter should filter data' '
                S=$(file_size test.r) &&
                S2=$(file_size test2.r) &&
                S3=$(file_size "testsubdir/test3 '\''sq'\'',\$x=.r") &&
+               M=$(git hash-object test.r) &&
+               M2=$(git hash-object test2.r) &&
+               M3=$(git hash-object "testsubdir/test3 '\''sq'\'',\$x=.r") &&
+               EMPTY=$(git hash-object /dev/null) &&
 
                filter_git add . &&
                cat >expected.log <<-EOF &&
@@ -378,14 +382,16 @@ test_expect_success PERL 'required process filter should filter data' '
                test_cmp_count expected.log debug.log &&
 
                git commit -m "test commit 2" &&
+               MASTER=$(git rev-parse --verify master) &&
+               META="ref=refs/heads/master treeish=$MASTER" &&
                rm -f test2.r "testsubdir/test3 '\''sq'\'',\$x=.r" &&
 
                filter_git checkout --quiet --no-progress . &&
                cat >expected.log <<-EOF &&
                        START
                        init handshake complete
-                       IN: smudge test2.r $S2 [OK] -- OUT: $S2 . [OK]
-                       IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $S3 [OK] -- OUT: $S3 . [OK]
+                       IN: smudge test2.r blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
+                       IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r blob=$M3 $S3 [OK] -- OUT: $S3 . [OK]
                        STOP
                EOF
                test_cmp_exclude_clean expected.log debug.log &&
@@ -406,10 +412,10 @@ test_expect_success PERL 'required process filter should filter data' '
                cat >expected.log <<-EOF &&
                        START
                        init handshake complete
-                       IN: smudge test.r $S [OK] -- OUT: $S . [OK]
-                       IN: smudge test2.r $S2 [OK] -- OUT: $S2 . [OK]
-                       IN: smudge test4-empty.r 0 [OK] -- OUT: 0  [OK]
-                       IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $S3 [OK] -- OUT: $S3 . [OK]
+                       IN: smudge test.r $META blob=$M $S [OK] -- OUT: $S . [OK]
+                       IN: smudge test2.r $META blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
+                       IN: smudge test4-empty.r $META blob=$EMPTY 0 [OK] -- OUT: 0  [OK]
+                       IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $META blob=$M3 $S3 [OK] -- OUT: $S3 . [OK]
                        STOP
                EOF
                test_cmp_exclude_clean expected.log debug.log &&
@@ -420,6 +426,117 @@ test_expect_success PERL 'required process filter should filter data' '
        )
 '
 
+test_expect_success PERL 'required process filter should filter data for various subcommands' '
+       test_config_global filter.protocol.process "rot13-filter.pl debug.log clean smudge" &&
+       test_config_global filter.protocol.required true &&
+       (
+               cd repo &&
+
+               S=$(file_size test.r) &&
+               S2=$(file_size test2.r) &&
+               S3=$(file_size "testsubdir/test3 '\''sq'\'',\$x=.r") &&
+               M=$(git hash-object test.r) &&
+               M2=$(git hash-object test2.r) &&
+               M3=$(git hash-object "testsubdir/test3 '\''sq'\'',\$x=.r") &&
+               EMPTY=$(git hash-object /dev/null) &&
+
+               MASTER=$(git rev-parse --verify master) &&
+
+               cp "$TEST_ROOT/test.o" test5.r &&
+               git add test5.r &&
+               git commit -m "test commit 3" &&
+               git checkout empty-branch &&
+               filter_git rebase --onto empty-branch master^^ master &&
+               MASTER2=$(git rev-parse --verify master) &&
+               META="ref=refs/heads/master treeish=$MASTER2" &&
+               cat >expected.log <<-EOF &&
+                       START
+                       init handshake complete
+                       IN: smudge test.r $META blob=$M $S [OK] -- OUT: $S . [OK]
+                       IN: smudge test2.r $META blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
+                       IN: smudge test4-empty.r $META blob=$EMPTY 0 [OK] -- OUT: 0  [OK]
+                       IN: smudge test5.r $META blob=$M $S [OK] -- OUT: $S . [OK]
+                       IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $META blob=$M3 $S3 [OK] -- OUT: $S3 . [OK]
+                       STOP
+               EOF
+               test_cmp_exclude_clean expected.log debug.log &&
+
+               git reset --hard empty-branch &&
+               filter_git reset --hard $MASTER &&
+               META="treeish=$MASTER" &&
+               cat >expected.log <<-EOF &&
+                       START
+                       init handshake complete
+                       IN: smudge test.r $META blob=$M $S [OK] -- OUT: $S . [OK]
+                       IN: smudge test2.r $META blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
+                       IN: smudge test4-empty.r $META blob=$EMPTY 0 [OK] -- OUT: 0  [OK]
+                       IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $META blob=$M3 $S3 [OK] -- OUT: $S3 . [OK]
+                       STOP
+               EOF
+               test_cmp_exclude_clean expected.log debug.log &&
+
+               git branch old-master $MASTER &&
+               git reset --hard empty-branch &&
+               filter_git reset --hard old-master &&
+               META="ref=refs/heads/old-master treeish=$MASTER" &&
+               cat >expected.log <<-EOF &&
+                       START
+                       init handshake complete
+                       IN: smudge test.r $META blob=$M $S [OK] -- OUT: $S . [OK]
+                       IN: smudge test2.r $META blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
+                       IN: smudge test4-empty.r $META blob=$EMPTY 0 [OK] -- OUT: 0  [OK]
+                       IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $META blob=$M3 $S3 [OK] -- OUT: $S3 . [OK]
+                       STOP
+               EOF
+               test_cmp_exclude_clean expected.log debug.log &&
+
+               git checkout -b merge empty-branch &&
+               git branch -f master $MASTER2 &&
+               filter_git merge master &&
+               META="treeish=$MASTER2" &&
+               cat >expected.log <<-EOF &&
+                       START
+                       init handshake complete
+                       IN: smudge test.r $META blob=$M $S [OK] -- OUT: $S . [OK]
+                       IN: smudge test2.r $META blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
+                       IN: smudge test4-empty.r $META blob=$EMPTY 0 [OK] -- OUT: 0  [OK]
+                       IN: smudge test5.r $META blob=$M $S [OK] -- OUT: $S . [OK]
+                       IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $META blob=$M3 $S3 [OK] -- OUT: $S3 . [OK]
+                       STOP
+               EOF
+               test_cmp_exclude_clean expected.log debug.log &&
+
+               filter_git archive master >/dev/null &&
+               META="ref=refs/heads/master treeish=$MASTER2" &&
+               cat >expected.log <<-EOF &&
+                       START
+                       init handshake complete
+                       IN: smudge test.r $META blob=$M $S [OK] -- OUT: $S . [OK]
+                       IN: smudge test2.r $META blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
+                       IN: smudge test4-empty.r $META blob=$EMPTY 0 [OK] -- OUT: 0  [OK]
+                       IN: smudge test5.r $META blob=$M $S [OK] -- OUT: $S . [OK]
+                       IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $META blob=$M3 $S3 [OK] -- OUT: $S3 . [OK]
+                       STOP
+               EOF
+               test_cmp_exclude_clean expected.log debug.log &&
+
+               TREE="$(git rev-parse $MASTER2^{tree})" &&
+               filter_git archive $TREE >/dev/null &&
+               META="treeish=$TREE" &&
+               cat >expected.log <<-EOF &&
+                       START
+                       init handshake complete
+                       IN: smudge test.r $META blob=$M $S [OK] -- OUT: $S . [OK]
+                       IN: smudge test2.r $META blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
+                       IN: smudge test4-empty.r $META blob=$EMPTY 0 [OK] -- OUT: 0  [OK]
+                       IN: smudge test5.r $META blob=$M $S [OK] -- OUT: $S . [OK]
+                       IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $META blob=$M3 $S3 [OK] -- OUT: $S3 . [OK]
+                       STOP
+               EOF
+               test_cmp_exclude_clean expected.log debug.log
+       )
+'
+
 test_expect_success PERL 'required process filter takes precedence' '
        test_config_global filter.protocol.clean false &&
        test_config_global filter.protocol.process "rot13-filter.pl debug.log clean" &&
@@ -519,17 +636,22 @@ test_expect_success PERL 'required process filter should process multiple packet
                EOF
                test_cmp_count expected.log debug.log &&
 
-               rm -f *.file &&
+               M1="blob=$(git hash-object 1pkt_1__.file)" &&
+               M2="blob=$(git hash-object 2pkt_1+1.file)" &&
+               M3="blob=$(git hash-object 2pkt_2-1.file)" &&
+               M4="blob=$(git hash-object 2pkt_2__.file)" &&
+               M5="blob=$(git hash-object 3pkt_2+1.file)" &&
+               rm -f *.file debug.log &&
 
                filter_git checkout --quiet --no-progress -- *.file &&
                cat >expected.log <<-EOF &&
                        START
                        init handshake complete
-                       IN: smudge 1pkt_1__.file $(($S    )) [OK] -- OUT: $(($S    )) . [OK]
-                       IN: smudge 2pkt_1+1.file $(($S  +1)) [OK] -- OUT: $(($S  +1)) .. [OK]
-                       IN: smudge 2pkt_2-1.file $(($S*2-1)) [OK] -- OUT: $(($S*2-1)) .. [OK]
-                       IN: smudge 2pkt_2__.file $(($S*2  )) [OK] -- OUT: $(($S*2  )) .. [OK]
-                       IN: smudge 3pkt_2+1.file $(($S*2+1)) [OK] -- OUT: $(($S*2+1)) ... [OK]
+                       IN: smudge 1pkt_1__.file $M1 $(($S    )) [OK] -- OUT: $(($S    )) . [OK]
+                       IN: smudge 2pkt_1+1.file $M2 $(($S  +1)) [OK] -- OUT: $(($S  +1)) .. [OK]
+                       IN: smudge 2pkt_2-1.file $M3 $(($S*2-1)) [OK] -- OUT: $(($S*2-1)) .. [OK]
+                       IN: smudge 2pkt_2__.file $M4 $(($S*2  )) [OK] -- OUT: $(($S*2  )) .. [OK]
+                       IN: smudge 3pkt_2+1.file $M5 $(($S*2+1)) [OK] -- OUT: $(($S*2+1)) ... [OK]
                        STOP
                EOF
                test_cmp_exclude_clean expected.log debug.log &&
@@ -578,6 +700,10 @@ test_expect_success PERL 'process filter should restart after unexpected write f
                S=$(file_size test.r) &&
                S2=$(file_size test2.r) &&
                SF=$(file_size smudge-write-fail.r) &&
+               M=$(git hash-object test.r) &&
+               M2=$(git hash-object test2.r) &&
+               MF=$(git hash-object smudge-write-fail.r) &&
+               rm -f debug.log &&
 
                git add . &&
                rm -f *.r &&
@@ -591,11 +717,11 @@ test_expect_success PERL 'process filter should restart after unexpected write f
                cat >expected.log <<-EOF &&
                        START
                        init handshake complete
-                       IN: smudge smudge-write-fail.r $SF [OK] -- [WRITE FAIL]
+                       IN: smudge smudge-write-fail.r blob=$MF $SF [OK] -- [WRITE FAIL]
                        START
                        init handshake complete
-                       IN: smudge test.r $S [OK] -- OUT: $S . [OK]
-                       IN: smudge test2.r $S2 [OK] -- OUT: $S2 . [OK]
+                       IN: smudge test.r blob=$M $S [OK] -- OUT: $S . [OK]
+                       IN: smudge test2.r blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
                        STOP
                EOF
                test_cmp_exclude_clean expected.log debug.log &&
@@ -629,6 +755,10 @@ test_expect_success PERL 'process filter should not be restarted if it signals a
                S=$(file_size test.r) &&
                S2=$(file_size test2.r) &&
                SE=$(file_size error.r) &&
+               M=$(git hash-object test.r) &&
+               M2=$(git hash-object test2.r) &&
+               ME=$(git hash-object error.r) &&
+               rm -f debug.log &&
 
                git add . &&
                rm -f *.r &&
@@ -637,9 +767,9 @@ test_expect_success PERL 'process filter should not be restarted if it signals a
                cat >expected.log <<-EOF &&
                        START
                        init handshake complete
-                       IN: smudge error.r $SE [OK] -- [ERROR]
-                       IN: smudge test.r $S [OK] -- OUT: $S . [OK]
-                       IN: smudge test2.r $S2 [OK] -- OUT: $S2 . [OK]
+                       IN: smudge error.r blob=$ME $SE [OK] -- [ERROR]
+                       IN: smudge test.r blob=$M $S [OK] -- OUT: $S . [OK]
+                       IN: smudge test2.r blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
                        STOP
                EOF
                test_cmp_exclude_clean expected.log debug.log &&
@@ -665,18 +795,21 @@ test_expect_success PERL 'process filter abort stops processing of all further f
                echo "error this blob and all future blobs" >abort.o &&
                cp abort.o abort.r &&
 
+               M="blob=$(git hash-object abort.r)" &&
+               rm -f debug.log &&
                SA=$(file_size abort.r) &&
 
                git add . &&
                rm -f *.r &&
 
+
                # Note: This test assumes that Git filters files in alphabetical
                # order ("abort.r" before "test.r").
                filter_git checkout --quiet --no-progress . &&
                cat >expected.log <<-EOF &&
                        START
                        init handshake complete
-                       IN: smudge abort.r $SA [OK] -- [ABORT]
+                       IN: smudge abort.r $M $SA [OK] -- [ABORT]
                        STOP
                EOF
                test_cmp_exclude_clean expected.log debug.log &&
@@ -727,27 +860,29 @@ test_expect_success PERL 'delayed checkout in process filter' '
        ) &&
 
        S=$(file_size "$TEST_ROOT/test.o") &&
+       PM="ref=refs/heads/master treeish=$(git -C repo rev-parse --verify master) " &&
+       M="${PM}blob=$(git -C repo rev-parse --verify master:test.a)" &&
        cat >a.exp <<-EOF &&
                START
                init handshake complete
-               IN: smudge test.a $S [OK] -- OUT: $S . [OK]
-               IN: smudge test-delay10.a $S [OK] -- [DELAYED]
-               IN: smudge test-delay11.a $S [OK] -- [DELAYED]
-               IN: smudge test-delay20.a $S [OK] -- [DELAYED]
+               IN: smudge test.a $M $S [OK] -- OUT: $S . [OK]
+               IN: smudge test-delay10.a $M $S [OK] -- [DELAYED]
+               IN: smudge test-delay11.a $M $S [OK] -- [DELAYED]
+               IN: smudge test-delay20.a $M $S [OK] -- [DELAYED]
                IN: list_available_blobs test-delay10.a test-delay11.a [OK]
-               IN: smudge test-delay10.a 0 [OK] -- OUT: $S . [OK]
-               IN: smudge test-delay11.a 0 [OK] -- OUT: $S . [OK]
+               IN: smudge test-delay10.a $M 0 [OK] -- OUT: $S . [OK]
+               IN: smudge test-delay11.a $M 0 [OK] -- OUT: $S . [OK]
                IN: list_available_blobs test-delay20.a [OK]
-               IN: smudge test-delay20.a 0 [OK] -- OUT: $S . [OK]
+               IN: smudge test-delay20.a $M 0 [OK] -- OUT: $S . [OK]
                IN: list_available_blobs [OK]
                STOP
        EOF
        cat >b.exp <<-EOF &&
                START
                init handshake complete
-               IN: smudge test-delay10.b $S [OK] -- [DELAYED]
+               IN: smudge test-delay10.b $M $S [OK] -- [DELAYED]
                IN: list_available_blobs test-delay10.b [OK]
-               IN: smudge test-delay10.b 0 [OK] -- OUT: $S . [OK]
+               IN: smudge test-delay10.b $M 0 [OK] -- OUT: $S . [OK]
                IN: list_available_blobs [OK]
                STOP
        EOF
@@ -767,8 +902,11 @@ test_expect_success PERL 'delayed checkout in process filter' '
 
                rm *.a *.b &&
                filter_git checkout . &&
-               test_cmp_count ../a.exp a.log &&
-               test_cmp_count ../b.exp b.log &&
+               # We are not checking out a ref here, so filter out ref metadata.
+               sed -e "s!$PM!!" ../a.exp >a.exp.filtered &&
+               sed -e "s!$PM!!" ../b.exp >b.exp.filtered &&
+               test_cmp_count a.exp.filtered a.log &&
+               test_cmp_count b.exp.filtered b.log &&
 
                test_cmp_committed_rot13 "$TEST_ROOT/test.o" test.a &&
                test_cmp_committed_rot13 "$TEST_ROOT/test.o" test-delay10.a &&
index 470107248eb161b9314ceb0ab93f21f072cf86cd..cd32a82da5c3bc4ac5b6c1748ef3368ad6f4ca70 100644 (file)
@@ -135,7 +135,13 @@ while (1) {
                                if ( exists $DELAY{$pathname} and $DELAY{$pathname}{"requested"} == 0 ) {
                                        $DELAY{$pathname}{"requested"} = 1;
                                }
+                       } elsif ($buffer =~ /^(ref|treeish|blob)=/) {
+                               print $debug " $buffer";
                        } else {
+                               # In general, filters need to be graceful about
+                               # new metadata, since it's documented that we
+                               # can pass any key-value pairs, but for tests,
+                               # let's be a little stricter.
                                die "Unknown message '$buffer'";
                        }
 
index 39f097ea9e5094530b981c49193df8df2cbda406..5555a1524f1df58e640c4f2cc59e6c60a824d7f9 100755 (executable)
@@ -22,6 +22,11 @@ test_expect_success 'setup helper scripts' '
        exit 0
        EOF
 
+       write_script git-credential-quit <<-\EOF &&
+       . ./dump
+       echo quit=1
+       EOF
+
        write_script git-credential-verbatim <<-\EOF &&
        user=$1; shift
        pass=$1; shift
@@ -35,43 +40,71 @@ test_expect_success 'setup helper scripts' '
 
 test_expect_success 'credential_fill invokes helper' '
        check fill "verbatim foo bar" <<-\EOF
+       protocol=http
+       host=example.com
        --
+       protocol=http
+       host=example.com
        username=foo
        password=bar
        --
        verbatim: get
+       verbatim: protocol=http
+       verbatim: host=example.com
        EOF
 '
 
 test_expect_success 'credential_fill invokes multiple helpers' '
        check fill useless "verbatim foo bar" <<-\EOF
+       protocol=http
+       host=example.com
        --
+       protocol=http
+       host=example.com
        username=foo
        password=bar
        --
        useless: get
+       useless: protocol=http
+       useless: host=example.com
        verbatim: get
+       verbatim: protocol=http
+       verbatim: host=example.com
        EOF
 '
 
 test_expect_success 'credential_fill stops when we get a full response' '
        check fill "verbatim one two" "verbatim three four" <<-\EOF
+       protocol=http
+       host=example.com
        --
+       protocol=http
+       host=example.com
        username=one
        password=two
        --
        verbatim: get
+       verbatim: protocol=http
+       verbatim: host=example.com
        EOF
 '
 
 test_expect_success 'credential_fill continues through partial response' '
        check fill "verbatim one \"\"" "verbatim two three" <<-\EOF
+       protocol=http
+       host=example.com
        --
+       protocol=http
+       host=example.com
        username=two
        password=three
        --
        verbatim: get
+       verbatim: protocol=http
+       verbatim: host=example.com
        verbatim: get
+       verbatim: protocol=http
+       verbatim: host=example.com
        verbatim: username=one
        EOF
 '
@@ -97,14 +130,20 @@ test_expect_success 'credential_fill passes along metadata' '
 
 test_expect_success 'credential_approve calls all helpers' '
        check approve useless "verbatim one two" <<-\EOF
+       protocol=http
+       host=example.com
        username=foo
        password=bar
        --
        --
        useless: store
+       useless: protocol=http
+       useless: host=example.com
        useless: username=foo
        useless: password=bar
        verbatim: store
+       verbatim: protocol=http
+       verbatim: host=example.com
        verbatim: username=foo
        verbatim: password=bar
        EOF
@@ -112,6 +151,8 @@ test_expect_success 'credential_approve calls all helpers' '
 
 test_expect_success 'do not bother storing password-less credential' '
        check approve useless <<-\EOF
+       protocol=http
+       host=example.com
        username=foo
        --
        --
@@ -121,14 +162,20 @@ test_expect_success 'do not bother storing password-less credential' '
 
 test_expect_success 'credential_reject calls all helpers' '
        check reject useless "verbatim one two" <<-\EOF
+       protocol=http
+       host=example.com
        username=foo
        password=bar
        --
        --
        useless: erase
+       useless: protocol=http
+       useless: host=example.com
        useless: username=foo
        useless: password=bar
        verbatim: erase
+       verbatim: protocol=http
+       verbatim: host=example.com
        verbatim: username=foo
        verbatim: password=bar
        EOF
@@ -136,33 +183,49 @@ test_expect_success 'credential_reject calls all helpers' '
 
 test_expect_success 'usernames can be preserved' '
        check fill "verbatim \"\" three" <<-\EOF
+       protocol=http
+       host=example.com
        username=one
        --
+       protocol=http
+       host=example.com
        username=one
        password=three
        --
        verbatim: get
+       verbatim: protocol=http
+       verbatim: host=example.com
        verbatim: username=one
        EOF
 '
 
 test_expect_success 'usernames can be overridden' '
        check fill "verbatim two three" <<-\EOF
+       protocol=http
+       host=example.com
        username=one
        --
+       protocol=http
+       host=example.com
        username=two
        password=three
        --
        verbatim: get
+       verbatim: protocol=http
+       verbatim: host=example.com
        verbatim: username=one
        EOF
 '
 
 test_expect_success 'do not bother completing already-full credential' '
        check fill "verbatim three four" <<-\EOF
+       protocol=http
+       host=example.com
        username=one
        password=two
        --
+       protocol=http
+       host=example.com
        username=one
        password=two
        --
@@ -174,23 +237,31 @@ test_expect_success 'do not bother completing already-full credential' '
 # askpass helper is run, we know the internal getpass is working.
 test_expect_success 'empty helper list falls back to internal getpass' '
        check fill <<-\EOF
+       protocol=http
+       host=example.com
        --
+       protocol=http
+       host=example.com
        username=askpass-username
        password=askpass-password
        --
-       askpass: Username:
-       askpass: Password:
+       askpass: Username for '\''http://example.com'\'':
+       askpass: Password for '\''http://askpass-username@example.com'\'':
        EOF
 '
 
 test_expect_success 'internal getpass does not ask for known username' '
        check fill <<-\EOF
+       protocol=http
+       host=example.com
        username=foo
        --
+       protocol=http
+       host=example.com
        username=foo
        password=askpass-password
        --
-       askpass: Password:
+       askpass: Password for '\''http://foo@example.com'\'':
        EOF
 '
 
@@ -202,7 +273,11 @@ HELPER="!f() {
 test_expect_success 'respect configured credentials' '
        test_config credential.helper "$HELPER" &&
        check fill <<-\EOF
+       protocol=http
+       host=example.com
        --
+       protocol=http
+       host=example.com
        username=foo
        password=bar
        --
@@ -419,21 +494,85 @@ test_expect_success 'context uses urlmatch' '
 
 test_expect_success 'helpers can abort the process' '
        test_must_fail git \
-               -c credential.helper="!f() { echo quit=1; }; f" \
+               -c credential.helper=quit \
                -c credential.helper="verbatim foo bar" \
-               credential fill >stdout &&
-       test_must_be_empty stdout
+               credential fill >stdout 2>stderr <<-\EOF &&
+       protocol=http
+       host=example.com
+       EOF
+       test_must_be_empty stdout &&
+       cat >expect <<-\EOF &&
+       quit: get
+       quit: protocol=http
+       quit: host=example.com
+       fatal: credential helper '\''quit'\'' told us to quit
+       EOF
+       test_i18ncmp expect stderr
 '
 
 test_expect_success 'empty helper spec resets helper list' '
        test_config credential.helper "verbatim file file" &&
        check fill "" "verbatim cmdline cmdline" <<-\EOF
+       protocol=http
+       host=example.com
        --
+       protocol=http
+       host=example.com
        username=cmdline
        password=cmdline
        --
        verbatim: get
+       verbatim: protocol=http
+       verbatim: host=example.com
+       EOF
+'
+
+test_expect_success 'url parser rejects embedded newlines' '
+       test_must_fail git credential fill 2>stderr <<-\EOF &&
+       url=https://one.example.com?%0ahost=two.example.com/
+       EOF
+       cat >expect <<-\EOF &&
+       warning: url contains a newline in its host component: https://one.example.com?%0ahost=two.example.com/
+       fatal: credential url cannot be parsed: https://one.example.com?%0ahost=two.example.com/
+       EOF
+       test_i18ncmp expect stderr
+'
+
+test_expect_success 'host-less URLs are parsed as empty host' '
+       check fill "verbatim foo bar" <<-\EOF
+       url=cert:///path/to/cert.pem
+       --
+       protocol=cert
+       host=
+       path=path/to/cert.pem
+       username=foo
+       password=bar
+       --
+       verbatim: get
+       verbatim: protocol=cert
+       verbatim: host=
+       verbatim: path=path/to/cert.pem
+       EOF
+'
+
+test_expect_success 'credential system refuses to work with missing host' '
+       test_must_fail git credential fill 2>stderr <<-\EOF &&
+       protocol=http
+       EOF
+       cat >expect <<-\EOF &&
+       fatal: refusing to work with credential missing host field
+       EOF
+       test_i18ncmp expect stderr
+'
+
+test_expect_success 'credential system refuses to work with missing protocol' '
+       test_must_fail git credential fill 2>stderr <<-\EOF &&
+       host=example.com
+       EOF
+       cat >expect <<-\EOF &&
+       fatal: refusing to work with credential missing protocol field
        EOF
+       test_i18ncmp expect stderr
 '
 
 test_done
index d09eff503c65aa4f9b6e95f6c1d163f0936e89f9..449ebc5657c57259c0096d1294adb96ba6f585d0 100755 (executable)
@@ -133,6 +133,30 @@ test_expect_success 'other worktree HEAD link pointing at a funny place' '
        test_i18ngrep "worktrees/other/HEAD points to something strange" out
 '
 
+test_expect_success 'commit with multiple signatures is okay' '
+       git cat-file commit HEAD >basis &&
+       cat >sigs <<-EOF &&
+       gpgsig -----BEGIN PGP SIGNATURE-----
+         VGhpcyBpcyBub3QgcmVhbGx5IGEgc2lnbmF0dXJlLg==
+         -----END PGP SIGNATURE-----
+       gpgsig-sha256 -----BEGIN PGP SIGNATURE-----
+         VGhpcyBpcyBub3QgcmVhbGx5IGEgc2lnbmF0dXJlLg==
+         -----END PGP SIGNATURE-----
+       EOF
+       sed -e "/^committer/q" basis >okay &&
+       cat sigs >>okay &&
+       echo >>okay &&
+       sed -e "1,/^$/d" basis >>okay &&
+       cat okay &&
+       new=$(git hash-object -t commit -w --stdin <okay) &&
+       test_when_finished "remove_object $new" &&
+       git update-ref refs/heads/bogus "$new" &&
+       test_when_finished "git update-ref -d refs/heads/bogus" &&
+       git fsck 2>out &&
+       cat out &&
+       ! grep "commit $new" out
+'
+
 test_expect_success 'email without @ is okay' '
        git cat-file commit HEAD >basis &&
        sed "s/@/AT/" basis >okay &&
index 69ffe865b43965bb84bbe60402fc9c03ecc8ba9b..52585ec2aa8ba6d9104060361523c1b307bebfda 100755 (executable)
@@ -152,7 +152,7 @@ test_expect_success 'linked worktrees are sorted' '
 '
 
 test_expect_success 'worktree path when called in .git directory' '
-       git worktree list >list1&&
+       git worktree list >list1 &&
        git -C .git worktree list >list2 &&
        test_cmp list1 list2
 '
index ee8a8dba528697569a8a3f469bd28200f555d763..a927774910bc058a9d661e59b1670f4fd043fc5f 100755 (executable)
@@ -29,6 +29,13 @@ test_expect_success setup '
        test_tick &&
        git commit -m reverted-goodbye &&
        git tag reverted-goodbye &&
+       git checkout goodbye &&
+       test_tick &&
+       GIT_AUTHOR_NAME="Another Author" \
+               GIT_AUTHOR_EMAIL="another.author@example.com" \
+               git commit --amend --no-edit -m amended-goodbye &&
+       test_tick &&
+       git tag amended-goodbye &&
 
        git checkout -f skip-reference &&
        echo moo > hello &&
@@ -85,6 +92,78 @@ test_expect_success 'moved back to branch correctly' '
 
 test_debug 'gitk --all & sleep 1'
 
+test_expect_success 'correct advice upon picking empty commit' '
+       test_when_finished "git rebase --abort" &&
+       test_must_fail git rebase -i --onto goodbye \
+               amended-goodbye^ amended-goodbye 2>err &&
+       test_i18ngrep "previous cherry-pick is now empty" err &&
+       test_i18ngrep "git rebase --skip" err &&
+       test_must_fail git commit &&
+       test_i18ngrep "git rebase --skip" err
+'
+
+test_expect_success 'correct authorship when committing empty pick' '
+       test_when_finished "git rebase --abort" &&
+       test_must_fail git rebase -i --onto goodbye \
+               amended-goodbye^ amended-goodbye &&
+       git commit --allow-empty &&
+       git log --pretty=format:"%an <%ae>%n%ad%B" -1 amended-goodbye >expect &&
+       git log --pretty=format:"%an <%ae>%n%ad%B" -1 HEAD >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'correct advice upon rewording empty commit' '
+       test_when_finished "git rebase --abort" &&
+       (
+               set_fake_editor &&
+               test_must_fail env FAKE_LINES="reword 1" git rebase -i \
+                       --onto goodbye amended-goodbye^ amended-goodbye 2>err
+       ) &&
+       test_i18ngrep "previous cherry-pick is now empty" err &&
+       test_i18ngrep "git rebase --skip" err &&
+       test_must_fail git commit &&
+       test_i18ngrep "git rebase --skip" err
+'
+
+test_expect_success 'correct advice upon editing empty commit' '
+       test_when_finished "git rebase --abort" &&
+       (
+               set_fake_editor &&
+               test_must_fail env FAKE_LINES="edit 1" git rebase -i \
+                       --onto goodbye amended-goodbye^ amended-goodbye 2>err
+       ) &&
+       test_i18ngrep "previous cherry-pick is now empty" err &&
+       test_i18ngrep "git rebase --skip" err &&
+       test_must_fail git commit &&
+       test_i18ngrep "git rebase --skip" err
+'
+
+test_expect_success 'correct advice upon cherry-picking an empty commit during a rebase' '
+       test_when_finished "git rebase --abort" &&
+       (
+               set_fake_editor &&
+               test_must_fail env FAKE_LINES="1 exec_git_cherry-pick_amended-goodbye" \
+                       git rebase -i goodbye^ goodbye 2>err
+       ) &&
+       test_i18ngrep "previous cherry-pick is now empty" err &&
+       test_i18ngrep "git cherry-pick --skip" err &&
+       test_must_fail git commit 2>err &&
+       test_i18ngrep "git cherry-pick --skip" err
+'
+
+test_expect_success 'correct advice upon multi cherry-pick picking an empty commit during a rebase' '
+       test_when_finished "git rebase --abort" &&
+       (
+               set_fake_editor &&
+               test_must_fail env FAKE_LINES="1 exec_git_cherry-pick_goodbye_amended-goodbye" \
+                       git rebase -i goodbye^^ goodbye 2>err
+       ) &&
+       test_i18ngrep "previous cherry-pick is now empty" err &&
+       test_i18ngrep "git cherry-pick --skip" err &&
+       test_must_fail git commit 2>err &&
+       test_i18ngrep "git cherry-pick --skip" err
+'
+
 test_expect_success 'fixup that empties commit fails' '
        test_when_finished "git rebase --abort" &&
        (
index c5ce3ab760eae5b438dddf20fc1389824f21c7e3..4a7d21f898f7361ffcdf988b2458e52089b758ba 100755 (executable)
@@ -187,7 +187,7 @@ test_expect_success 'no changes are a nop' '
        git checkout branch2 &&
        git rebase -i F &&
        test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch2" &&
-       test $(git rev-parse I) = $(git rev-parse HEAD)
+       test_cmp_rev I HEAD
 '
 
 test_expect_success 'test the [branch] option' '
@@ -196,16 +196,16 @@ test_expect_success 'test the [branch] option' '
        git commit -m "stop here" &&
        git rebase -i F branch2 &&
        test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch2" &&
-       test $(git rev-parse I) = $(git rev-parse branch2) &&
-       test $(git rev-parse I) = $(git rev-parse HEAD)
+       test_cmp_rev I branch2 &&
+       test_cmp_rev I HEAD
 '
 
 test_expect_success 'test --onto <branch>' '
        git checkout -b test-onto branch2 &&
        git rebase -i --onto branch1 F &&
        test "$(git symbolic-ref -q HEAD)" = "refs/heads/test-onto" &&
-       test $(git rev-parse HEAD^) = $(git rev-parse branch1) &&
-       test $(git rev-parse I) = $(git rev-parse branch2)
+       test_cmp_rev HEAD^ branch1 &&
+       test_cmp_rev I branch2
 '
 
 test_expect_success 'rebase on top of a non-conflicting commit' '
@@ -214,12 +214,12 @@ test_expect_success 'rebase on top of a non-conflicting commit' '
        git rebase -i branch2 &&
        test file6 = $(git diff --name-only original-branch1) &&
        test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch1" &&
-       test $(git rev-parse I) = $(git rev-parse branch2) &&
-       test $(git rev-parse I) = $(git rev-parse HEAD~2)
+       test_cmp_rev I branch2 &&
+       test_cmp_rev I HEAD~2
 '
 
 test_expect_success 'reflog for the branch shows state before rebase' '
-       test $(git rev-parse branch1@{1}) = $(git rev-parse original-branch1)
+       test_cmp_rev branch1@{1} original-branch1
 '
 
 test_expect_success 'reflog for the branch shows correct finish message' '
@@ -279,7 +279,7 @@ test_expect_success 'show conflicted patch' '
 
 test_expect_success 'abort' '
        git rebase --abort &&
-       test $(git rev-parse new-branch1) = $(git rev-parse HEAD) &&
+       test_cmp_rev new-branch1 HEAD &&
        test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch1" &&
        test_path_is_missing .git/rebase-merge
 '
@@ -322,7 +322,7 @@ test_expect_success 'retain authorship w/ conflicts' '
        echo resolved >conflict &&
        git add conflict &&
        git rebase --continue &&
-       test $(git rev-parse conflict-a^0) = $(git rev-parse HEAD^) &&
+       test_cmp_rev conflict-a^0 HEAD^ &&
        git show >out &&
        grep AttributeMe out
 '
@@ -339,7 +339,7 @@ test_expect_success 'squash' '
                        git rebase -i --onto master HEAD~2
        ) &&
        test B = $(cat file7) &&
-       test $(git rev-parse HEAD^) = $(git rev-parse master)
+       test_cmp_rev HEAD^ master
 '
 
 test_expect_success 'retain authorship when squashing' '
@@ -398,9 +398,9 @@ test_expect_success REBASE_P 'preserve merges with -p' '
        git update-index --refresh &&
        git diff-files --quiet &&
        git diff-index --quiet --cached HEAD -- &&
-       test $(git rev-parse HEAD~6) = $(git rev-parse branch1) &&
-       test $(git rev-parse HEAD~4^2) = $(git rev-parse to-be-preserved) &&
-       test $(git rev-parse HEAD^^2^) = $(git rev-parse HEAD^^^) &&
+       test_cmp_rev HEAD~6 branch1 &&
+       test_cmp_rev HEAD~4^2 to-be-preserved &&
+       test_cmp_rev HEAD^^2^ HEAD^^^ &&
        test $(git show HEAD~5:file1) = B &&
        test $(git show HEAD~3:file1) = C &&
        test $(git show HEAD:file1) = E &&
@@ -432,7 +432,7 @@ test_expect_success '--continue tries to commit' '
                git add file1 &&
                FAKE_COMMIT_MESSAGE="chouette!" git rebase --continue
        ) &&
-       test $(git rev-parse HEAD^) = $(git rev-parse new-branch1) &&
+       test_cmp_rev HEAD^ new-branch1 &&
        git show HEAD | grep chouette
 '
 
@@ -739,7 +739,7 @@ test_expect_success 'do "noop" when there is nothing to cherry-pick' '
                --author="Somebody else <somebody@else.com>" &&
        test $(git rev-parse branch3) != $(git rev-parse branch4) &&
        git rebase -i branch3 &&
-       test $(git rev-parse branch3) = $(git rev-parse branch4)
+       test_cmp_rev branch3 branch4
 
 '
 
@@ -798,7 +798,7 @@ test_expect_success 'rebase -i continue with unstaged submodule' '
        test_must_fail git rebase -i submodule-base &&
        git reset &&
        git rebase --continue &&
-       test $(git rev-parse submodule-base) = $(git rev-parse HEAD)
+       test_cmp_rev submodule-base HEAD
 '
 
 test_expect_success 'avoid unnecessary reset' '
@@ -821,7 +821,7 @@ test_expect_success 'reword' '
                        git rebase -i A &&
                git show HEAD | grep "E changed" &&
                test $(git rev-parse master) != $(git rev-parse HEAD) &&
-               test $(git rev-parse master^) = $(git rev-parse HEAD^) &&
+               test_cmp_rev master^ HEAD^ &&
                FAKE_LINES="1 2 reword 3 4" FAKE_COMMIT_MESSAGE="D changed" \
                        git rebase -i A &&
                git show HEAD^ | grep "D changed" &&
@@ -885,7 +885,7 @@ test_expect_success 'always cherry-pick with --no-ff' '
                git diff HEAD~$p original-no-ff-branch~$p > out &&
                test_must_be_empty out
        done &&
-       test $(git rev-parse HEAD~3) = $(git rev-parse original-no-ff-branch~3) &&
+       test_cmp_rev HEAD~3 original-no-ff-branch~3 &&
        git diff HEAD~3 original-no-ff-branch~3 > out &&
        test_must_be_empty out
 '
@@ -1734,6 +1734,32 @@ test_expect_success 'post-commit hook is called' '
        test_cmp expect actual
 '
 
+test_expect_success 'correct error message for partial commit after empty pick' '
+       test_when_finished "git rebase --abort" &&
+       (
+               set_fake_editor &&
+               FAKE_LINES="2 1 1" &&
+               export FAKE_LINES &&
+               test_must_fail git rebase -i A D
+       ) &&
+       echo x >file1 &&
+       test_must_fail git commit file1 2>err &&
+       test_i18ngrep "cannot do a partial commit during a rebase." err
+'
+
+test_expect_success 'correct error message for commit --amend after empty pick' '
+       test_when_finished "git rebase --abort" &&
+       (
+               set_fake_editor &&
+               FAKE_LINES="1 1" &&
+               export FAKE_LINES &&
+               test_must_fail git rebase -i A D
+       ) &&
+       echo x>file1 &&
+       test_must_fail git commit -a --amend 2>err &&
+       test_i18ngrep "middle of a rebase -- cannot amend." err
+'
+
 # This must be the last test in this file
 test_expect_success '$EDITOR and friends are unchanged' '
        test_editor_unchanged
index e85cdc7037bcb6184d1ce6f9aa7ff2cfc4af2096..946e92f8dac84edcad260d4f7354b996e54b61ec 100755 (executable)
@@ -52,7 +52,7 @@ test_expect_success 'blank line at end of file; extend at end of file' '
        git commit --allow-empty -m "Initial empty commit" &&
        git add file && git commit -m first &&
        mv second file &&
-       git add file && git commit -m second &&
+       git add file && git commit -m second &&
        git rebase --whitespace=fix HEAD^^ &&
        git diff --exit-code HEAD^:file expect-first &&
        test_cmp expect-second file
@@ -118,7 +118,7 @@ test_expect_success 'at beginning of file' '
        for i in 1 2 3 4 5; do
                echo $i
        done >> file &&
-       git commit -m more file &&
+       git commit -m more file &&
        git rebase --whitespace=fix HEAD^^ &&
        test_cmp expect-beginning file
 '
index d93458377690cdddddf73621f24d7782de959aec..1f32faa4a4db3d0307365af0c9cbd5bf58ff964f 100755 (executable)
@@ -4,15 +4,6 @@ test_description='git rebase - test patch id computation'
 
 . ./test-lib.sh
 
-count () {
-       i=0
-       while test $i -lt $1
-       do
-               echo "$i"
-               i=$(($i+1))
-       done
-}
-
 scramble () {
        i=0
        while read x
@@ -26,76 +17,55 @@ scramble () {
        mv -f "$1.new" "$1"
 }
 
-run () {
-       echo \$ "$@"
-       /usr/bin/time "$@" >/dev/null
-}
-
 test_expect_success 'setup' '
        git commit --allow-empty -m initial &&
        git tag root
 '
 
-do_tests () {
-       nlines=$1 pr=${2-}
-
-       test_expect_success $pr "setup: $nlines lines" "
-               rm -f .gitattributes &&
-               git checkout -q -f master &&
-               git reset --hard root &&
-               count $nlines >file &&
-               git add file &&
-               git commit -q -m initial &&
-               git branch -f other &&
-
-               scramble file &&
-               git add file &&
-               git commit -q -m 'change big file' &&
-
-               git checkout -q other &&
-               : >newfile &&
-               git add newfile &&
-               git commit -q -m 'add small file' &&
-
-               git cherry-pick master >/dev/null 2>&1
-       "
-
-       test_debug "
-               run git diff master^\!
-       "
-
-       test_expect_success $pr 'setup attributes' "
-               echo 'file binary' >.gitattributes
-       "
-
-       test_debug "
-               run git format-patch --stdout master &&
-               run git format-patch --stdout --ignore-if-in-upstream master
-       "
+test_expect_success 'setup: 500 lines' '
+       rm -f .gitattributes &&
+       git checkout -q -f master &&
+       git reset --hard root &&
+       test_seq 500 >file &&
+       git add file &&
+       git commit -q -m initial &&
+       git branch -f other &&
+
+       scramble file &&
+       git add file &&
+       git commit -q -m "change big file" &&
+
+       git checkout -q other &&
+       : >newfile &&
+       git add newfile &&
+       git commit -q -m "add small file" &&
+
+       git cherry-pick master >/dev/null 2>&1
+'
 
-       test_expect_success $pr 'detect upstream patch' '
-               git checkout -q master &&
-               scramble file &&
-               git add file &&
-               git commit -q -m "change big file again" &&
-               git checkout -q other^{} &&
-               git rebase master &&
-               git rev-list master...HEAD~ >revs &&
-               test_must_be_empty revs
-       '
+test_expect_success 'setup attributes' '
+       echo "file binary" >.gitattributes
+'
 
-       test_expect_success $pr 'do not drop patch' '
-               git branch -f squashed master &&
-               git checkout -q -f squashed &&
-               git reset -q --soft HEAD~2 &&
-               git commit -q -m squashed &&
-               git checkout -q other^{} &&
-               test_must_fail git rebase squashed &&
-               git rebase --quit
-       '
-}
+test_expect_success 'detect upstream patch' '
+       git checkout -q master &&
+       scramble file &&
+       git add file &&
+       git commit -q -m "change big file again" &&
+       git checkout -q other^{} &&
+       git rebase master &&
+       git rev-list master...HEAD~ >revs &&
+       test_must_be_empty revs
+'
 
-do_tests 500
-do_tests 50000 EXPENSIVE
+test_expect_success 'do not drop patch' '
+       git branch -f squashed master &&
+       git checkout -q -f squashed &&
+       git reset -q --soft HEAD~2 &&
+       git commit -q -m squashed &&
+       git checkout -q other^{} &&
+       test_must_fail git rebase squashed &&
+       git rebase --quit
+'
 
 test_done
index 78851b9a2ae84d6b73b3cffc0026082abce322e1..172562789e474042922db296c0fce842f2d25437 100755 (executable)
@@ -47,11 +47,31 @@ test_rebase 'G F B A' --keep-base
 test_rebase 'G F C E D B A' --no-fork-point
 test_rebase 'G F C D B A' --no-fork-point --onto D
 test_rebase 'G F C B A' --no-fork-point --keep-base
+
 test_rebase 'G F E D B A' --fork-point refs/heads/master
+test_rebase 'G F E D B A' --fork-point master
+
 test_rebase 'G F D B A' --fork-point --onto D refs/heads/master
+test_rebase 'G F D B A' --fork-point --onto D master
+
 test_rebase 'G F B A' --fork-point --keep-base refs/heads/master
+test_rebase 'G F B A' --fork-point --keep-base master
+
 test_rebase 'G F C E D B A' refs/heads/master
+test_rebase 'G F C E D B A' master
+
 test_rebase 'G F C D B A' --onto D refs/heads/master
+test_rebase 'G F C D B A' --onto D master
+
 test_rebase 'G F C B A' --keep-base refs/heads/master
+test_rebase 'G F C B A' --keep-base master
+
+test_expect_success 'git rebase --fork-point with ambigous refname' '
+       git checkout master &&
+       git checkout -b one &&
+       git checkout side &&
+       git tag one &&
+       test_must_fail git rebase --fork-point --onto D one
+'
 
 test_done
index 9bd482ce3b8912b2016fe2997c50d59b5a5162c5..752bc43487196a4be33ff7df770384f331a68315 100755 (executable)
@@ -161,6 +161,29 @@ test_expect_success 'successful commit clears CHERRY_PICK_HEAD' '
 
        test_must_fail git rev-parse --verify CHERRY_PICK_HEAD
 '
+
+test_expect_success 'partial commit of cherry-pick fails' '
+       pristine_detach initial &&
+
+       test_must_fail git cherry-pick picked &&
+       echo resolved >foo &&
+       git add foo &&
+       test_must_fail git commit foo 2>err &&
+
+       test_i18ngrep "cannot do a partial commit during a cherry-pick." err
+'
+
+test_expect_success 'commit --amend of cherry-pick fails' '
+       pristine_detach initial &&
+
+       test_must_fail git cherry-pick picked &&
+       echo resolved >foo &&
+       git add foo &&
+       test_must_fail git commit --amend 2>err &&
+
+       test_i18ngrep "in the middle of a cherry-pick -- cannot amend." err
+'
+
 test_expect_success 'successful final commit clears cherry-pick state' '
        pristine_detach initial &&
 
index 793bcc7fe3246e8fc375b8678bb99f7bc875a48c..5b94fdaa6713a2bcf7958d6630925a09fea05ed6 100755 (executable)
@@ -123,7 +123,8 @@ test_expect_success 'revert --skip to skip commit' '
 test_expect_success 'skip "empty" commit' '
        pristine_detach picked &&
        test_commit dummy foo d &&
-       test_must_fail git cherry-pick anotherpick &&
+       test_must_fail git cherry-pick anotherpick 2>err &&
+       test_i18ngrep "git cherry-pick --skip" err &&
        git cherry-pick --skip &&
        test_cmp_rev dummy HEAD
 '
index 88bc799807f7dd99ba98cda2b698ef83e027f7b7..b7d4ba608cbc96998fdac714ebb53a0f53c46f9e 100755 (executable)
@@ -192,7 +192,7 @@ test_expect_success 'git add --refresh with pathspec' '
        test_must_be_empty actual &&
 
        git diff-files --name-only >actual &&
-       ! grep bar actual&&
+       ! grep bar actual &&
        grep baz actual
 '
 
index 5bae6e50f1f3e76c1615bf5e3031e99f92d619b9..b3d8bb7577661c0211bc349900cc321f9971ff05 100755 (executable)
@@ -780,7 +780,7 @@ test_expect_success 'add -p patch editing works with pathological context lines'
 test_expect_success 'checkout -p works with pathological context lines' '
        test_write_lines a a a a a a >a &&
        git add a &&
-       test_write_lines a b a b a b a b a b a > a&&
+       test_write_lines a b a b a b a b a b a >&&
        test_write_lines s n n y q | git checkout -p &&
        test_write_lines a b a b a a b a b a >expect &&
        test_cmp expect a
index 3ad23e2502bea0e861d21822e5ef6b3559e0f8bd..9f7ca9896755ecf38ad09f136cc75d6cbb18542b 100755 (executable)
@@ -1290,4 +1290,18 @@ test_expect_success 'stash handles skip-worktree entries nicely' '
        git rev-parse --verify refs/stash:A.t
 '
 
+test_expect_success 'stash -c stash.useBuiltin=false warning ' '
+       expected="stash.useBuiltin support has been removed" &&
+
+       git -c stash.useBuiltin=false stash 2>err &&
+       test_i18ngrep "$expected" err &&
+       env GIT_TEST_STASH_USE_BUILTIN=false git stash 2>err &&
+       test_i18ngrep "$expected" err &&
+
+       git -c stash.useBuiltin=true stash 2>err &&
+       test_must_be_empty err &&
+       env GIT_TEST_STASH_USE_BUILTIN=true git stash 2>err &&
+       test_must_be_empty err
+'
+
 test_done
index 4f4b541658a1b604ee27aff76a4e2047fca8111c..0b78573733b30716d1ed4fb8bd2fdeee0c5b9d83 100755 (executable)
@@ -14,7 +14,7 @@ diffc_verify () {
 test_expect_success 'trivial merge - combine-diff empty' '
        for i in $(test_seq 1 9)
        do
-               echo $i >$i.txt &&
+               echo $i >$i.txt &&
                git add $i.txt
        done &&
        git commit -m "init" &&
index cb45271457266daf4d809362cb846dc0f879dd48..bda4586a7951a234b50ffbb28cdd4d7df05e4c56 100755 (executable)
@@ -166,7 +166,7 @@ test_expect_success setup '
        test_tick &&
        git commit -m third &&
 
-       git format-patch --stdout first >patch2 &&
+       git format-patch --stdout first >patch2 &&
 
        git checkout -b lorem &&
        sed -n -e "11,\$p" msg >file &&
index 0f766ba65f598c105bbce6f8f3aa2d4cda1c0de3..5eeb739f3ed4bb44a8951fb223445ead15dc61c8 100755 (executable)
@@ -1627,6 +1627,66 @@ test_expect_success GPG 'log --graph --show-signature for merged tag in shallow
        grep "tag signed_tag_shallow names a non-parent $hash" actual
 '
 
+test_expect_success GPG 'log --graph --show-signature for merged tag with missing key' '
+       test_when_finished "git reset --hard && git checkout master" &&
+       git checkout -b plain-nokey master &&
+       echo aaa >bar &&
+       git add bar &&
+       git commit -m bar_commit &&
+       git checkout -b tagged-nokey master &&
+       echo bbb >baz &&
+       git add baz &&
+       git commit -m baz_commit &&
+       git tag -s -m signed_tag_msg signed_tag_nokey &&
+       git checkout plain-nokey &&
+       git merge --no-ff -m msg signed_tag_nokey &&
+       GNUPGHOME=. git log --graph --show-signature -n1 plain-nokey >actual &&
+       grep "^|\\\  merged tag" actual &&
+       grep "^| | gpg: Signature made" actual &&
+       grep "^| | gpg: Can'"'"'t check signature: \(public key not found\|No public key\)" actual
+'
+
+test_expect_success GPG 'log --graph --show-signature for merged tag with bad signature' '
+       test_when_finished "git reset --hard && git checkout master" &&
+       git checkout -b plain-bad master &&
+       echo aaa >bar &&
+       git add bar &&
+       git commit -m bar_commit &&
+       git checkout -b tagged-bad master &&
+       echo bbb >baz &&
+       git add baz &&
+       git commit -m baz_commit &&
+       git tag -s -m signed_tag_msg signed_tag_bad &&
+       git cat-file tag signed_tag_bad >raw &&
+       sed -e "s/signed_tag_msg/forged/" raw >forged &&
+       git hash-object -w -t tag forged >forged.tag &&
+       git checkout plain-bad &&
+       git merge --no-ff -m msg "$(cat forged.tag)" &&
+       git log --graph --show-signature -n1 plain-bad >actual &&
+       grep "^|\\\  merged tag" actual &&
+       grep "^| | gpg: Signature made" actual &&
+       grep "^| | gpg: BAD signature from" actual
+'
+
+test_expect_success GPG 'log --show-signature for merged tag with GPG failure' '
+       test_when_finished "git reset --hard && git checkout master" &&
+       git checkout -b plain-fail master &&
+       echo aaa >bar &&
+       git add bar &&
+       git commit -m bar_commit &&
+       git checkout -b tagged-fail master &&
+       echo bbb >baz &&
+       git add baz &&
+       git commit -m baz_commit &&
+       git tag -s -m signed_tag_msg signed_tag_fail &&
+       git checkout plain-fail &&
+       git merge --no-ff -m msg signed_tag_fail &&
+       TMPDIR="$(pwd)/bogus" git log --show-signature -n1 plain-fail >actual &&
+       grep "^merged tag" actual &&
+       grep "^No signature" actual &&
+       ! grep "^gpg: Signature made" actual
+'
+
 test_expect_success GPGSM 'log --graph --show-signature for merged tag x509' '
        test_when_finished "git reset --hard && git checkout master" &&
        test_config gpg.format x509 &&
@@ -1648,6 +1708,51 @@ test_expect_success GPGSM 'log --graph --show-signature for merged tag x509' '
        grep "^| | gpgsm: Good signature" actual
 '
 
+test_expect_success GPGSM 'log --graph --show-signature for merged tag x509 missing key' '
+       test_when_finished "git reset --hard && git checkout master" &&
+       test_config gpg.format x509 &&
+       test_config user.signingkey $GIT_COMMITTER_EMAIL &&
+       git checkout -b plain-x509-nokey master &&
+       echo aaa >bar &&
+       git add bar &&
+       git commit -m bar_commit &&
+       git checkout -b tagged-x509-nokey master &&
+       echo bbb >baz &&
+       git add baz &&
+       git commit -m baz_commit &&
+       git tag -s -m signed_tag_msg signed_tag_x509_nokey &&
+       git checkout plain-x509-nokey &&
+       git merge --no-ff -m msg signed_tag_x509_nokey &&
+       GNUPGHOME=. git log --graph --show-signature -n1 plain-x509-nokey >actual &&
+       grep "^|\\\  merged tag" actual &&
+       grep "^| | gpgsm: certificate not found" actual
+'
+
+test_expect_success GPGSM 'log --graph --show-signature for merged tag x509 bad signature' '
+       test_when_finished "git reset --hard && git checkout master" &&
+       test_config gpg.format x509 &&
+       test_config user.signingkey $GIT_COMMITTER_EMAIL &&
+       git checkout -b plain-x509-bad master &&
+       echo aaa >bar &&
+       git add bar &&
+       git commit -m bar_commit &&
+       git checkout -b tagged-x509-bad master &&
+       echo bbb >baz &&
+       git add baz &&
+       git commit -m baz_commit &&
+       git tag -s -m signed_tag_msg signed_tag_x509_bad &&
+       git cat-file tag signed_tag_x509_bad >raw &&
+       sed -e "s/signed_tag_msg/forged/" raw >forged &&
+       git hash-object -w -t tag forged >forged.tag &&
+       git checkout plain-x509-bad &&
+       git merge --no-ff -m msg "$(cat forged.tag)" &&
+       git log --graph --show-signature -n1 plain-x509-bad >actual &&
+       grep "^|\\\  merged tag" actual &&
+       grep "^| | gpgsm: Signature made" actual &&
+       grep "^| | gpgsm: invalid signature" actual
+'
+
+
 test_expect_success GPG '--no-show-signature overrides --show-signature' '
        git log -1 --show-signature --no-show-signature signed >actual &&
        ! grep "^gpg:" actual
index 7124b5581a0e3e31ae9b78b46aec9f059eeb7c1a..a581eaf52936292f28b2ff9542bccd8f2ea5152a 100755 (executable)
@@ -105,14 +105,16 @@ test_expect_success 'non-sparse pack-objects' '
        test_cmp required_objects.txt nonsparse_required_objects.txt
 '
 
+# --sparse is enabled by default by pack.useSparse
 test_expect_success 'sparse pack-objects' '
+       GIT_TEST_PACK_SPARSE=-1 &&
        git rev-parse                   \
                topic1                  \
                topic1^{tree}           \
                topic1:f3               \
                topic1:f3/f4            \
                topic1:f3/f4/data.txt | sort >expect_sparse_objects.txt &&
-       git pack-objects --stdout --revs --sparse <packinput.txt >sparse.pack &&
+       git pack-objects --stdout --revs <packinput.txt >sparse.pack &&
        git index-pack -o sparse.idx sparse.pack &&
        git show-index <sparse.idx | awk "{print \$2}" >sparse_objects.txt &&
        test_cmp expect_sparse_objects.txt sparse_objects.txt
index ccde8ba491e8f119dc48accf5c10f533bf74c2fd..159afa7ac81396e1d9915335f030cd0fcc8b7b3f 100755 (executable)
@@ -11,10 +11,10 @@ test_expect_success 'setup' '
         git commit -m one)
 '
 
-test_expect_success 'git pull -q' '
+test_expect_success 'git pull -q --no-rebase' '
        mkdir clonedq &&
        (cd clonedq && git init &&
-       git pull -q "../parent" >out 2>err &&
+       git pull -q --no-rebase "../parent" >out 2>err &&
        test_must_be_empty err &&
        test_must_be_empty out)
 '
@@ -30,10 +30,10 @@ test_expect_success 'git pull -q --rebase' '
        test_must_be_empty out)
 '
 
-test_expect_success 'git pull' '
+test_expect_success 'git pull --no-rebase' '
        mkdir cloned &&
        (cd cloned && git init &&
-       git pull "../parent" >out 2>err &&
+       git pull --no-rebase "../parent" >out 2>err &&
        test -s err &&
        test_must_be_empty out)
 '
@@ -46,10 +46,10 @@ test_expect_success 'git pull --rebase' '
        test_must_be_empty out)
 '
 
-test_expect_success 'git pull -v' '
+test_expect_success 'git pull -v --no-rebase' '
        mkdir clonedv &&
        (cd clonedv && git init &&
-       git pull -v "../parent" >out 2>err &&
+       git pull -v --no-rebase "../parent" >out 2>err &&
        test -s err &&
        test_must_be_empty out)
 '
@@ -62,25 +62,25 @@ test_expect_success 'git pull -v --rebase' '
        test_must_be_empty out)
 '
 
-test_expect_success 'git pull -v -q' '
+test_expect_success 'git pull -v -q --no-rebase' '
        mkdir clonedvq &&
        (cd clonedvq && git init &&
-       git pull -v -q "../parent" >out 2>err &&
+       git pull -v -q --no-rebase "../parent" >out 2>err &&
        test_must_be_empty out &&
        test_must_be_empty err)
 '
 
-test_expect_success 'git pull -q -v' '
+test_expect_success 'git pull -q -v --no-rebase' '
        mkdir clonedqv &&
        (cd clonedqv && git init &&
-       git pull -q -v "../parent" >out 2>err &&
+       git pull -q -v --no-rebase "../parent" >out 2>err &&
        test_must_be_empty out &&
        test -s err)
 '
 test_expect_success 'git pull --cleanup errors early on invalid argument' '
        mkdir clonedcleanup &&
        (cd clonedcleanup && git init &&
-       test_must_fail git pull --cleanup invalid "../parent" >out 2>err &&
+       test_must_fail git pull --no-rebase --cleanup invalid "../parent" >out 2>err &&
        test_must_be_empty out &&
        test -s err)
 '
index 4f681dbbe11c7f37d47d30bfa33a215c0dd9e189..b57209c84fcad43ce728dd8bfa24674caa64c37c 100755 (executable)
@@ -131,7 +131,7 @@ test_expect_success 'fetch that requires changes in .git/shallow is filtered' '
        git init notshallow &&
        (
        cd notshallow &&
-       git fetch ../shallow/.git refs/heads/*:refs/remotes/shallow/*&&
+       git fetch ../shallow/.git refs/heads/*:refs/remotes/shallow/* &&
        git for-each-ref --format="%(refname)" >actual.refs &&
        cat <<EOF >expect.refs &&
 refs/remotes/shallow/no-shallow
index b811d89cfd6df25fb2e42db6f05a69cf0b7a6c3b..ea2688bde59c9fa0bfe9829e7f8913c280164cde 100755 (executable)
@@ -321,11 +321,17 @@ test_expect_success 'git client does not send an empty Accept-Language' '
 '
 
 test_expect_success 'remote-http complains cleanly about malformed urls' '
-       # do not actually issue "list" or other commands, as we do not
-       # want to rely on what curl would actually do with such a broken
-       # URL. This is just about making sure we do not segfault during
-       # initialization.
-       test_must_fail git remote-http http::/example.com/repo.git
+       test_must_fail git remote-http http::/example.com/repo.git 2>stderr &&
+       test_i18ngrep "url has no scheme" stderr
+'
+
+# NEEDSWORK: Writing commands to git-remote-curl can race against the latter
+# erroring out, producing SIGPIPE. Remove "ok=sigpipe" once transport-helper has
+# learned to handle early remote helper failures more cleanly.
+test_expect_success 'remote-http complains cleanly about empty scheme' '
+       test_must_fail ok=sigpipe git ls-remote \
+               http::${HTTPD_URL#http}/dumb/repo.git 2>stderr &&
+       test_i18ngrep "url has no scheme" stderr
 '
 
 test_expect_success 'redirects can be forbidden/allowed' '
index 0c74b4e21a3ef17499401cdd1f4eeaab5c392137..2f7be23044712ae832428fac09ad0a3cfd6a1fff 100755 (executable)
@@ -175,7 +175,7 @@ test_expect_success 'clone using repo pointed at by gitfile as reference' '
 test_expect_success 'clone and dissociate from reference' '
        git init P &&
        (
-               cd P && test_commit one
+               cd P && test_commit one
        ) &&
        git clone P Q &&
        (
index 34502e3a505443d02a57ccc241e178feeffd1ce5..f822d5d3285040f8a90fab1e7fa4e327196f60f2 100755 (executable)
@@ -129,12 +129,30 @@ test_expect_success 'rename tag A to Q locally' '
        mv .git/refs/tags/A .git/refs/tags/Q
 '
 cat - >err.expect <<EOF
-warning: tag 'A' is really 'Q' here
+warning: tag 'Q' is externally known as 'A'
 EOF
 check_describe A-* HEAD
 test_expect_success 'warning was displayed for Q' '
        test_i18ncmp err.expect err.actual
 '
+test_expect_success 'misnamed annotated tag forces long output' '
+       description=$(git describe --no-long Q^0) &&
+       expr "$description" : "A-0-g[0-9a-f]*$" &&
+       git rev-parse --verify "$description" >actual &&
+       git rev-parse --verify Q^0 >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'abbrev=0 will not break misplaced tag (1)' '
+       description=$(git describe --abbrev=0 Q^0) &&
+       expr "$description" : "A-0-g[0-9a-f]*$"
+'
+
+test_expect_success 'abbrev=0 will not break misplaced tag (2)' '
+       description=$(git describe --abbrev=0 c^0) &&
+       expr "$description" : "A-1-g[0-9a-f]*$"
+'
+
 test_expect_success 'rename tag Q back to A' '
        mv .git/refs/tags/Q .git/refs/tags/A
 '
index 8a72b4c43a4ff5ba2e2aeb99121ba8ba054dc396..b15582a7a2b50306c440f7203e073abd975706df 100755 (executable)
@@ -6,6 +6,7 @@
 test_description='fmt-merge-msg test'
 
 . ./test-lib.sh
+. "$TEST_DIRECTORY/lib-gpg.sh"
 
 test_expect_success setup '
        echo one >one &&
@@ -73,6 +74,10 @@ test_expect_success setup '
        apos="'\''"
 '
 
+test_expect_success GPG 'set up a signed tag' '
+       git tag -s -m signed-tag-msg signed-good-tag left
+'
+
 test_expect_success 'message for merging local branch' '
        echo "Merge branch ${apos}left${apos}" >expected &&
 
@@ -83,6 +88,24 @@ test_expect_success 'message for merging local branch' '
        test_cmp expected actual
 '
 
+test_expect_success GPG 'message for merging local tag signed by good key' '
+       git checkout master &&
+       git fetch . signed-good-tag &&
+       git fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&
+       grep "^Merge tag ${apos}signed-good-tag${apos}" actual &&
+       grep "^# gpg: Signature made" actual &&
+       grep "^# gpg: Good signature from" actual
+'
+
+test_expect_success GPG 'message for merging local tag signed by unknown key' '
+       git checkout master &&
+       git fetch . signed-good-tag &&
+       GNUPGHOME=. git fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&
+       grep "^Merge tag ${apos}signed-good-tag${apos}" actual &&
+       grep "^# gpg: Signature made" actual &&
+       grep "^# gpg: Can${apos}t check signature: \(public key not found\|No public key\)" actual
+'
+
 test_expect_success 'message for merging external branch' '
        echo "Merge branch ${apos}left${apos} of $(pwd)" >expected &&
 
index 9c910ce746733cabaafd34adceeb59cf53dca1f7..b3c1092338a89831206163275e32190a5e162cbd 100755 (executable)
@@ -20,6 +20,10 @@ setdate_and_increment () {
 }
 
 test_expect_success setup '
+       test_oid_cache <<-EOF &&
+       disklen sha1:138
+       disklen sha256:154
+       EOF
        setdate_and_increment &&
        echo "Using $datestamp" > one &&
        git add one &&
@@ -50,6 +54,9 @@ test_atom() {
        "
 }
 
+hexlen=$(test_oid hexsz)
+disklen=$(test_oid disklen)
+
 test_atom head refname refs/heads/master
 test_atom head refname: refs/heads/master
 test_atom head refname:short master
@@ -82,9 +89,9 @@ test_atom head push:rstrip=-1 refs
 test_atom head push:strip=1 remotes/myfork/master
 test_atom head push:strip=-1 master
 test_atom head objecttype commit
-test_atom head objectsize 171
-test_atom head objectsize:disk 138
-test_atom head deltabase 0000000000000000000000000000000000000000
+test_atom head objectsize $((131 + hexlen))
+test_atom head objectsize:disk $disklen
+test_atom head deltabase $ZERO_OID
 test_atom head objectname $(git rev-parse refs/heads/master)
 test_atom head objectname:short $(git rev-parse --short refs/heads/master)
 test_atom head objectname:short=1 $(git rev-parse --short=1 refs/heads/master)
@@ -125,11 +132,11 @@ test_atom tag refname:short testtag
 test_atom tag upstream ''
 test_atom tag push ''
 test_atom tag objecttype tag
-test_atom tag objectsize 154
-test_atom tag objectsize:disk 138
-test_atom tag '*objectsize:disk' 138
-test_atom tag deltabase 0000000000000000000000000000000000000000
-test_atom tag '*deltabase' 0000000000000000000000000000000000000000
+test_atom tag objectsize $((114 + hexlen))
+test_atom tag objectsize:disk $disklen
+test_atom tag '*objectsize:disk' $disklen
+test_atom tag deltabase $ZERO_OID
+test_atom tag '*deltabase' $ZERO_OID
 test_atom tag objectname $(git rev-parse refs/tags/testtag)
 test_atom tag objectname:short $(git rev-parse --short refs/tags/testtag)
 test_atom head objectname:short=1 $(git rev-parse --short=1 refs/heads/master)
@@ -139,7 +146,7 @@ test_atom tag parent ''
 test_atom tag numparent ''
 test_atom tag object $(git rev-parse refs/tags/testtag^0)
 test_atom tag type 'commit'
-test_atom tag '*objectname' 'ea122842f48be4afb2d1fc6a4b96c05885ab7463'
+test_atom tag '*objectname' $(git rev-parse refs/tags/testtag^{})
 test_atom tag '*objecttype' 'commit'
 test_atom tag author ''
 test_atom tag authorname ''
@@ -643,7 +650,7 @@ test_atom refs/tags/signed-long contents "subject line
 body contents
 $sig"
 
-cat >expected <<EOF
+sort >expected <<EOF
 $(git rev-parse refs/tags/bogo) <committer@example.com> refs/tags/bogo
 $(git rev-parse refs/tags/master) <committer@example.com> refs/tags/master
 EOF
index 6db92bd3ba62db4bb4e653480faf90f20d2b0bcb..74b637deb259a9c65e9abcac5ded9bcf5bf76b67 100755 (executable)
@@ -1726,6 +1726,7 @@ test_expect_success 'recursive tagging should give advice' '
        hint: already a tag. If you meant to tag the object that it points to, use:
        hint: |
        hint:   git tag -f nested annotated-v4.0^{}
+       hint: Disable this message with "git config advice.nestedTag false"
        EOF
        git tag -m nested nested annotated-v4.0 2>actual &&
        test_i18ncmp expect actual
index a1cb9ff858e4a2113bbf7fb732b9e6d0cb308445..67346424a53960c33becefe865ad8881c1f1777e 100755 (executable)
@@ -5,7 +5,6 @@ test_description='reset can handle submodules'
 . ./test-lib.sh
 . "$TEST_DIRECTORY"/lib-submodule-update.sh
 
-KNOWN_FAILURE_SUBMODULE_RECURSIVE_NESTED=1
 KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS=1
 KNOWN_FAILURE_SUBMODULE_OVERWRITE_IGNORED_UNTRACKED=1
 
index e3e2aab3b0a106a5a137361c3f1765515c1b1931..956e17abb3d7d5fb480c84c977067837c6baca61 100755 (executable)
@@ -938,7 +938,7 @@ test_expect_success 'submodule add --name allows to replace a submodule with ano
                echo "repo" >expect &&
                test_must_fail git config -f .gitmodules submodule.repo.path &&
                git config -f .gitmodules submodule.repo_new.path >actual &&
-               test_cmp expect actual&&
+               test_cmp expect actual &&
                echo "$submodurl/repo" >expect &&
                test_must_fail git config -f .gitmodules submodule.repo.url &&
                echo "$submodurl/bare.git" >expect &&
@@ -1010,7 +1010,7 @@ test_expect_success 'submodule add with an existing name fails unless forced' '
                test -d repo &&
                echo "repo" >expect &&
                git config -f .gitmodules submodule.repo_new.path >actual &&
-               test_cmp expect actual&&
+               test_cmp expect actual &&
                echo "$submodurl/repo.git" >expect &&
                git config -f .gitmodules submodule.repo_new.url >actual &&
                test_cmp expect actual &&
index 5ba041f537067dd9d16047a3569bb2b5fb3a4d51..eec96e0ba9e371e9603bd47ad5e13f0e547d7b5a 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-test_description='check handling of .gitmodule url with dash'
+test_description='check handling of disallowed .gitmodule urls'
 . ./test-lib.sh
 
 test_expect_success 'create submodule with protected dash in url' '
@@ -60,4 +60,145 @@ test_expect_success 'trailing backslash is handled correctly' '
        test_i18ngrep ! "unknown option" err
 '
 
+test_expect_success 'fsck rejects missing URL scheme' '
+       git checkout --orphan missing-scheme &&
+       cat >.gitmodules <<-\EOF &&
+       [submodule "foo"]
+               url = http::one.example.com/foo.git
+       EOF
+       git add .gitmodules &&
+       test_tick &&
+       git commit -m "gitmodules with missing URL scheme" &&
+       test_when_finished "rm -rf dst" &&
+       git init --bare dst &&
+       git -C dst config transfer.fsckObjects true &&
+       test_must_fail git push dst HEAD 2>err &&
+       grep gitmodulesUrl err
+'
+
+test_expect_success 'fsck rejects relative URL resolving to missing scheme' '
+       git checkout --orphan relative-missing-scheme &&
+       cat >.gitmodules <<-\EOF &&
+       [submodule "foo"]
+               url = "..\\../.\\../:one.example.com/foo.git"
+       EOF
+       git add .gitmodules &&
+       test_tick &&
+       git commit -m "gitmodules with relative URL that strips off scheme" &&
+       test_when_finished "rm -rf dst" &&
+       git init --bare dst &&
+       git -C dst config transfer.fsckObjects true &&
+       test_must_fail git push dst HEAD 2>err &&
+       grep gitmodulesUrl err
+'
+
+test_expect_success 'fsck rejects empty URL scheme' '
+       git checkout --orphan empty-scheme &&
+       cat >.gitmodules <<-\EOF &&
+       [submodule "foo"]
+               url = http::://one.example.com/foo.git
+       EOF
+       git add .gitmodules &&
+       test_tick &&
+       git commit -m "gitmodules with empty URL scheme" &&
+       test_when_finished "rm -rf dst" &&
+       git init --bare dst &&
+       git -C dst config transfer.fsckObjects true &&
+       test_must_fail git push dst HEAD 2>err &&
+       grep gitmodulesUrl err
+'
+
+test_expect_success 'fsck rejects relative URL resolving to empty scheme' '
+       git checkout --orphan relative-empty-scheme &&
+       cat >.gitmodules <<-\EOF &&
+       [submodule "foo"]
+               url = ../../../:://one.example.com/foo.git
+       EOF
+       git add .gitmodules &&
+       test_tick &&
+       git commit -m "relative gitmodules URL resolving to empty scheme" &&
+       test_when_finished "rm -rf dst" &&
+       git init --bare dst &&
+       git -C dst config transfer.fsckObjects true &&
+       test_must_fail git push dst HEAD 2>err &&
+       grep gitmodulesUrl err
+'
+
+test_expect_success 'fsck rejects empty hostname' '
+       git checkout --orphan empty-host &&
+       cat >.gitmodules <<-\EOF &&
+       [submodule "foo"]
+               url = http:///one.example.com/foo.git
+       EOF
+       git add .gitmodules &&
+       test_tick &&
+       git commit -m "gitmodules with extra slashes" &&
+       test_when_finished "rm -rf dst" &&
+       git init --bare dst &&
+       git -C dst config transfer.fsckObjects true &&
+       test_must_fail git push dst HEAD 2>err &&
+       grep gitmodulesUrl err
+'
+
+test_expect_success 'fsck rejects relative url that produced empty hostname' '
+       git checkout --orphan messy-relative &&
+       cat >.gitmodules <<-\EOF &&
+       [submodule "foo"]
+               url = ../../..//one.example.com/foo.git
+       EOF
+       git add .gitmodules &&
+       test_tick &&
+       git commit -m "gitmodules abusing relative_path" &&
+       test_when_finished "rm -rf dst" &&
+       git init --bare dst &&
+       git -C dst config transfer.fsckObjects true &&
+       test_must_fail git push dst HEAD 2>err &&
+       grep gitmodulesUrl err
+'
+
+test_expect_success 'fsck permits embedded newline with unrecognized scheme' '
+       git checkout --orphan newscheme &&
+       cat >.gitmodules <<-\EOF &&
+       [submodule "foo"]
+               url = "data://acjbkd%0akajfdickajkd"
+       EOF
+       git add .gitmodules &&
+       git commit -m "gitmodules with unrecognized scheme" &&
+       test_when_finished "rm -rf dst" &&
+       git init --bare dst &&
+       git -C dst config transfer.fsckObjects true &&
+       git push dst HEAD
+'
+
+test_expect_success 'fsck rejects embedded newline in url' '
+       # create an orphan branch to avoid existing .gitmodules objects
+       git checkout --orphan newline &&
+       cat >.gitmodules <<-\EOF &&
+       [submodule "foo"]
+       url = "https://one.example.com?%0ahost=two.example.com/foo.git"
+       EOF
+       git add .gitmodules &&
+       git commit -m "gitmodules with newline" &&
+       test_when_finished "rm -rf dst" &&
+       git init --bare dst &&
+       git -C dst config transfer.fsckObjects true &&
+       test_must_fail git push dst HEAD 2>err &&
+       grep gitmodulesUrl err
+'
+
+test_expect_success 'fsck rejects embedded newline in relative url' '
+       git checkout --orphan relative-newline &&
+       cat >.gitmodules <<-\EOF &&
+       [submodule "foo"]
+               url = "./%0ahost=two.example.com/foo.git"
+       EOF
+       git add .gitmodules &&
+       git commit -m "relative url with newline" &&
+       test_when_finished "rm -rf dst" &&
+       git init --bare dst &&
+       git -C dst config transfer.fsckObjects true &&
+       test_must_fail git push dst HEAD 2>err &&
+       grep gitmodulesUrl err
+'
+
 test_done
index 0c06d22a0079a61f04cd83dad29562dc4d017218..6baaa1ad91d4e51364afb12e62be6a1b48f2ea74 100755 (executable)
@@ -6,6 +6,11 @@ GNUPGHOME_NOT_USED=$GNUPGHOME
 . "$TEST_DIRECTORY/lib-gpg.sh"
 
 test_expect_success GPG 'create signed commits' '
+       test_oid_cache <<-\EOF &&
+       header sha1:gpgsig
+       header sha256:gpgsig-sha256
+       EOF
+
        test_when_finished "test_unconfig commit.gpgsign" &&
 
        echo 1 >file && git add file &&
@@ -155,6 +160,11 @@ test_expect_success GPG 'verify signatures with --raw' '
        )
 '
 
+test_expect_success GPG 'proper header is used for hash algorithm' '
+       git cat-file commit fourth-signed >output &&
+       grep "^$(test_oid header) -----BEGIN PGP SIGNATURE-----" output
+'
+
 test_expect_success GPG 'show signed commit with signature' '
        git show -s initial >commit &&
        git show -s --show-signature initial >show &&
@@ -162,7 +172,7 @@ test_expect_success GPG 'show signed commit with signature' '
        git cat-file commit initial >cat &&
        grep -v -e "gpg: " -e "Warning: " show >show.commit &&
        grep -e "gpg: " -e "Warning: " show >show.gpg &&
-       grep -v "^ " cat | grep -v "^gpgsig " >cat.commit &&
+       grep -v "^ " cat | grep -v "^$(test_oid header) " >cat.commit &&
        test_cmp show.commit commit &&
        test_cmp show.gpg verify.2 &&
        test_cmp cat.commit verify.1
@@ -299,10 +309,10 @@ test_expect_success GPG 'check config gpg.format values' '
 test_expect_success GPG 'detect fudged commit with double signature' '
        sed -e "/gpgsig/,/END PGP/d" forged1 >double-base &&
        sed -n -e "/gpgsig/,/END PGP/p" forged1 | \
-               sed -e "s/^gpgsig//;s/^ //" | gpg --dearmor >double-sig1.sig &&
+               sed -e "s/^$(test_oid header)//;s/^ //" | gpg --dearmor >double-sig1.sig &&
        gpg -o double-sig2.sig -u 29472784 --detach-sign double-base &&
        cat double-sig1.sig double-sig2.sig | gpg --enarmor >double-combined.asc &&
-       sed -e "s/^\(-.*\)ARMORED FILE/\1SIGNATURE/;1s/^/gpgsig /;2,\$s/^/ /" \
+       sed -e "s/^\(-.*\)ARMORED FILE/\1SIGNATURE/;1s/^/$(test_oid header) /;2,\$s/^/ /" \
                double-combined.asc > double-gpgsig &&
        sed -e "/committer/r double-gpgsig" double-base >double-commit &&
        git hash-object -w -t commit double-commit >double-commit.commit &&
index c6c44ec570dac66ca9a800ac687280cd3597e886..0f97828cd0bb8e6c3f1c39332010f5fbcfdb6512 100755 (executable)
@@ -27,6 +27,44 @@ test_expect_success 'setup' '
        git tag c3
 '
 
+test_expect_success 'pull.rebase not set' '
+       git reset --hard c0 &&
+       git pull . c1 2>err &&
+       test_i18ngrep "Pulling without specifying how to reconcile" err
+'
+
+test_expect_success 'pull.rebase not set and pull.ff=false' '
+       git reset --hard c0 &&
+       test_config pull.ff false &&
+       git pull . c1 2>err &&
+       test_i18ngrep "Pulling without specifying how to reconcile" err
+'
+
+test_expect_success 'pull.rebase not set and pull.ff=only' '
+       git reset --hard c0 &&
+       test_config pull.ff only &&
+       git pull . c1 2>err &&
+       test_i18ngrep ! "Pulling without specifying how to reconcile" err
+'
+
+test_expect_success 'pull.rebase not set and --rebase given' '
+       git reset --hard c0 &&
+       git pull --rebase . c1 2>err &&
+       test_i18ngrep ! "Pulling without specifying how to reconcile" err
+'
+
+test_expect_success 'pull.rebase not set and --no-rebase given' '
+       git reset --hard c0 &&
+       git pull --no-rebase . c1 2>err &&
+       test_i18ngrep ! "Pulling without specifying how to reconcile" err
+'
+
+test_expect_success 'pull.rebase not set and --ff-only given' '
+       git reset --hard c0 &&
+       git pull --ff-only . c1 2>err &&
+       test_i18ngrep ! "Pulling without specifying how to reconcile" err
+'
+
 test_expect_success 'merge c1 with c2' '
        git reset --hard c1 &&
        test -f c0.c &&
index e90413204ee32cafc3380244a5cf013eadcd72c1..5c8894d94ff107b6ea9ca63226075fadf9501e55 100755 (executable)
@@ -126,7 +126,7 @@ test_expect_success 'not_uptodate_dir porcelain checkout error' '
        git rm rep2 -r &&
        >rep &&
        >rep2 &&
-       git add rep rep2&&
+       git add rep rep2 &&
        git commit -m "added test as a file" &&
        git checkout master &&
        >rep/untracked-file &&
index c90fdc5c8940164c6bf0eacdf9f7ac2e3b0e02ec..83f8f5cacb59658bab98fde2da87f6cb5f7a9fca 100755 (executable)
@@ -486,7 +486,7 @@ test_expect_success 'NUL in property value' '
        {
                properties \
                        unimportant "something with a NUL (Q)" \
-                       svn:log "commit message"&&
+                       svn:log "commit message" &&
                echo PROPS-END
        } |
        q_to_nul >props &&
index 3e41c58a13689b65bf5a9ed6b2fc6466b303af81..768257b29e0cf0be6df93e01d529b50269d72465 100755 (executable)
@@ -3381,4 +3381,113 @@ test_expect_success 'X: handling encoding' '
        git log -1 --format=%B encoding | grep $(printf "\317\200")
 '
 
+###
+### series Y (submodules and hash algorithms)
+###
+
+cat >Y-sub-input <<\Y_INPUT_END
+blob
+mark :1
+data 4
+foo
+
+reset refs/heads/master
+commit refs/heads/master
+mark :2
+author Full Name <user@company.tld> 1000000000 +0100
+committer Full Name <user@company.tld> 1000000000 +0100
+data 24
+Test submodule commit 1
+M 100644 :1 file
+
+blob
+mark :3
+data 8
+foo
+bar
+
+commit refs/heads/master
+mark :4
+author Full Name <user@company.tld> 1000000001 +0100
+committer Full Name <user@company.tld> 1000000001 +0100
+data 24
+Test submodule commit 2
+from :2
+M 100644 :3 file
+Y_INPUT_END
+
+# Note that the submodule object IDs are intentionally not translated.
+cat >Y-main-input <<\Y_INPUT_END
+blob
+mark :1
+data 4
+foo
+
+reset refs/heads/master
+commit refs/heads/master
+mark :2
+author Full Name <user@company.tld> 2000000000 +0100
+committer Full Name <user@company.tld> 2000000000 +0100
+data 14
+Test commit 1
+M 100644 :1 file
+
+blob
+mark :3
+data 73
+[submodule "sub1"]
+       path = sub1
+       url = https://void.example.com/main.git
+
+commit refs/heads/master
+mark :4
+author Full Name <user@company.tld> 2000000001 +0100
+committer Full Name <user@company.tld> 2000000001 +0100
+data 14
+Test commit 2
+from :2
+M 100644 :3 .gitmodules
+M 160000 0712c5be7cf681388e355ef47525aaf23aee1a6d sub1
+
+blob
+mark :5
+data 8
+foo
+bar
+
+commit refs/heads/master
+mark :6
+author Full Name <user@company.tld> 2000000002 +0100
+committer Full Name <user@company.tld> 2000000002 +0100
+data 14
+Test commit 3
+from :4
+M 100644 :5 file
+M 160000 ff729f5e62f72c0c3978207d9a80e5f3a65f14d7 sub1
+Y_INPUT_END
+
+cat >Y-marks <<\Y_INPUT_END
+:2 0712c5be7cf681388e355ef47525aaf23aee1a6d
+:4 ff729f5e62f72c0c3978207d9a80e5f3a65f14d7
+Y_INPUT_END
+
+test_expect_success 'Y: setup' '
+       test_oid_cache <<-EOF
+       Ymaster sha1:9afed2f9161ddf416c0a1863b8b0725b00070504
+       Ymaster sha256:c0a1010da1df187b2e287654793df01b464bd6f8e3f17fc1481a7dadf84caee3
+       EOF
+'
+
+test_expect_success 'Y: rewrite submodules' '
+       git init main1 &&
+       (
+               cd main1 &&
+               git init sub2 &&
+               git -C sub2 fast-import --export-marks=../sub2-marks <../Y-sub-input &&
+               git fast-import --rewrite-submodules-from=sub:../Y-marks \
+                       --rewrite-submodules-to=sub:sub2-marks <../Y-main-input &&
+               test "$(git rev-parse master)" = "$(test_oid Ymaster)"
+       )
+'
+
 test_done
index cc8d463e01ac74a78fa08b638318a88b8ea98bff..267ddc997d028926a81562fe6fe8eab9157da819 100755 (executable)
@@ -53,7 +53,7 @@ test_expect_success \
 
 test_expect_success \
        'Make initial commit' \
-       'echo "Not an empty file." > file &&
+       'echo "Not an empty file." >file &&
         git add file &&
         git commit -a -m "Initial commit." &&
         git branch b'
@@ -139,7 +139,7 @@ test_expect_success \
 
 test_expect_success \
        'commitdiff(0): file added' \
-       'echo "New file" > new_file &&
+       'echo "New file" >new_file &&
         git add new_file &&
         git commit -a -m "File added." &&
         gitweb_run "p=.git;a=commitdiff"'
@@ -179,7 +179,7 @@ test_expect_success \
 
 test_expect_success \
        'commitdiff(0): mode change and modified' \
-       'echo "New line" >> file2 &&
+       'echo "New line" >>file2 &&
         test_chmod +x file2 &&
         git commit -a -m "Mode change and modification." &&
         gitweb_run "p=.git;a=commitdiff"'
@@ -197,14 +197,14 @@ propter nomen suum.
 EOF
         git commit -a -m "File added." &&
         git mv file2 file3 &&
-        echo "Propter nomen suum." >> file3 &&
+        echo "Propter nomen suum." >>file3 &&
         git commit -a -m "File rename and modification." &&
         gitweb_run "p=.git;a=commitdiff"'
 
 test_expect_success \
        'commitdiff(0): renamed, mode change and modified' \
        'git mv file3 file2 &&
-        echo "Propter nomen suum." >> file2 &&
+        echo "Propter nomen suum." >>file2 &&
         test_chmod +x file2 &&
         git commit -a -m "File rename, mode change and modification." &&
         gitweb_run "p=.git;a=commitdiff"'
@@ -213,8 +213,8 @@ test_expect_success \
 # commitdiff testing (taken from t4114-apply-typechange.sh)
 
 test_expect_success 'setup typechange commits' '
-       echo "hello world" > foo &&
-       echo "hi planet" > bar &&
+       echo "hello world" >foo &&
+       echo "hi planet" >bar &&
        git update-index --add foo bar &&
        git commit -m initial &&
        git branch initial &&
@@ -223,18 +223,18 @@ test_expect_success 'setup typechange commits' '
        git commit -m "foo symlinked to bar" &&
        git branch foo-symlinked-to-bar &&
        rm -f foo &&
-       echo "how far is the sun?" > foo &&
+       echo "how far is the sun?" >foo &&
        git update-index foo &&
        git commit -m "foo back to file" &&
        git branch foo-back-to-file &&
        rm -f foo &&
        git update-index --remove foo &&
        mkdir foo &&
-       echo "if only I knew" > foo/baz &&
+       echo "if only I knew" >foo/baz &&
        git update-index --add foo/baz &&
        git commit -m "foo becomes a directory" &&
        git branch "foo-becomes-a-directory" &&
-       echo "hello world" > foo/baz &&
+       echo "hello world" >foo/baz &&
        git update-index foo/baz &&
        git commit -m "foo/baz is the original foo" &&
        git branch foo-baz-renamed-from-foo
@@ -324,7 +324,7 @@ test_expect_success 'commitdiff(1): removal of incomplete line' '
 test_expect_success \
        'Create a merge' \
        'git checkout b &&
-        echo "Branch" >> b &&
+        echo "Branch" >>b &&
         git add b &&
         git commit -a -m "On branch" &&
         git checkout master &&
@@ -342,26 +342,26 @@ test_expect_success \
 test_expect_success \
        'Prepare large commit' \
        'git checkout b &&
-        echo "To be changed" > 01-change &&
-        echo "To be renamed" > 02-pure-rename-from &&
-        echo "To be deleted" > 03-delete &&
-        echo "To be renamed and changed" > 04-rename-from &&
-        echo "To have mode changed" > 05-mode-change &&
-        echo "File to symlink" > 06-file-or-symlink &&
-        echo "To be changed and have mode changed" > 07-change-mode-change     &&
+        echo "To be changed" >01-change &&
+        echo "To be renamed" >02-pure-rename-from &&
+        echo "To be deleted" >03-delete &&
+        echo "To be renamed and changed" >04-rename-from &&
+        echo "To have mode changed" >05-mode-change &&
+        echo "File to symlink" >06-file-or-symlink &&
+        echo "To be changed and have mode changed" >07-change-mode-change &&
         git add 0* &&
         git commit -a -m "Prepare large commit" &&
-        echo "Changed" > 01-change &&
+        echo "Changed" >01-change &&
         git mv 02-pure-rename-from 02-pure-rename-to &&
         git rm 03-delete && rm -f 03-delete &&
-        echo "A new file" > 03-new &&
+        echo "A new file" >03-new &&
         git add 03-new &&
         git mv 04-rename-from 04-rename-to &&
-        echo "Changed" >> 04-rename-to &&
+        echo "Changed" >>04-rename-to &&
         test_chmod +x 05-mode-change &&
         rm -f 06-file-or-symlink &&
         test_ln_s_add 01-change 06-file-or-symlink &&
-        echo "Changed and have mode changed" > 07-change-mode-change   &&
+        echo "Changed and have mode changed" >07-change-mode-change &&
         test_chmod +x 07-change-mode-change &&
         git commit -a -m "Large commit" &&
         git checkout master'
@@ -444,7 +444,7 @@ test_expect_success \
 test_expect_success \
        'logs: history (implicit HEAD, deleted file)' \
        'git checkout master &&
-        echo "to be deleted" > deleted_file &&
+        echo "to be deleted" >deleted_file &&
         git add deleted_file &&
         git commit -m "Add file to be deleted" &&
         git rm deleted_file &&
@@ -522,7 +522,7 @@ test_expect_success \
        '. "$TEST_DIRECTORY"/t3901/utf8.txt &&
         test_when_finished "GIT_AUTHOR_NAME=\"A U Thor\"" &&
         test_when_finished "GIT_COMMITTER_NAME=\"C O Mitter\"" &&
-        echo "UTF-8" >> file &&
+        echo "UTF-8" >>file &&
         git add file &&
         git commit -F "$TEST_DIRECTORY"/t3900/1-UTF-8.txt &&
         gitweb_run "p=.git;a=commit"'
@@ -532,7 +532,7 @@ test_expect_success \
        '. "$TEST_DIRECTORY"/t3901/8859-1.txt &&
         test_when_finished "GIT_AUTHOR_NAME=\"A U Thor\"" &&
         test_when_finished "GIT_COMMITTER_NAME=\"C O Mitter\"" &&
-        echo "ISO-8859-1" >> file &&
+        echo "ISO-8859-1" >>file &&
         git add file &&
         test_config i18n.commitencoding ISO-8859-1 &&
         git commit -F "$TEST_DIRECTORY"/t3900/ISO8859-1.txt &&
@@ -675,8 +675,8 @@ test_expect_success \
 
 test_expect_success \
        'README.html with non-ASCII characters (utf-8)' \
-       'echo "<b>UTF-8 example:</b><br />" > .git/README.html &&
-        cat "$TEST_DIRECTORY"/t3900/1-UTF-8.txt >> .git/README.html &&
+       'echo "<b>UTF-8 example:</b><br />" >.git/README.html &&
+        cat "$TEST_DIRECTORY"/t3900/1-UTF-8.txt >>.git/README.html &&
         gitweb_run "p=.git;a=summary"'
 
 # ----------------------------------------------------------------------
@@ -704,7 +704,7 @@ test_expect_success HIGHLIGHT \
 test_expect_success HIGHLIGHT \
        'syntax highlighting (highlighted, shell script)' \
        'git config gitweb.highlight yes &&
-        echo "#!/usr/bin/sh" > test.sh &&
+        echo "#!/usr/bin/sh" >test.sh &&
         git add test.sh &&
         git commit -m "Add test.sh" &&
         gitweb_run "p=.git;a=blob;f=test.sh"'
@@ -712,7 +712,7 @@ test_expect_success HIGHLIGHT \
 test_expect_success HIGHLIGHT \
        'syntax highlighting (highlighter language autodetection)' \
        'git config gitweb.highlight yes &&
-        echo "#!/usr/bin/perl" > test &&
+        echo "#!/usr/bin/perl" >test &&
         git add test &&
         git commit -m "Add test" &&
         gitweb_run "p=.git;a=blob;f=test"'
@@ -729,11 +729,11 @@ test_expect_success \
        'git init --bare foo.git &&
         git --git-dir=foo.git --work-tree=. add file &&
         git --git-dir=foo.git --work-tree=. commit -m "Initial commit" &&
-        echo "foo" > foo.git/description &&
+        echo "foo" >foo.git/description &&
         mkdir -p foo &&
         (cd foo &&
          git clone --shared --bare ../foo.git foo-forked.git &&
-         echo "fork of foo" > foo-forked.git/description)'
+         echo "fork of foo" >foo-forked.git/description)'
 
 test_expect_success \
        'forks: projects list' \
@@ -754,8 +754,8 @@ EOF
 test_expect_success \
        'ctags: tag cloud in projects list' \
        'mkdir .git/ctags &&
-        echo "2" > .git/ctags/foo &&
-        echo "1" > .git/ctags/bar &&
+        echo "2" >.git/ctags/foo &&
+        echo "1" >.git/ctags/bar &&
        gitweb_run'
 
 test_expect_success \
@@ -769,8 +769,8 @@ test_expect_success \
 test_expect_success \
        'ctags: malformed tag weights' \
        'mkdir -p .git/ctags &&
-        echo "not-a-number" > .git/ctags/nan &&
-        echo "not-a-number-2" > .git/ctags/nan2 &&
+        echo "not-a-number" >.git/ctags/nan &&
+        echo "not-a-number-2" >.git/ctags/nan2 &&
         echo "0.1" >.git/ctags/floating-point &&
         gitweb_run'
 
index d743ca33ee6ab29f97d328756663803824f105f5..ff6c0352e688246e9b97e063679889c2583c459e 100755 (executable)
@@ -58,7 +58,7 @@ test_expect_success 'import with extra info lines from verbose p4 trigger' '
        (
                cd "$git" &&
                git p4 sync
-       )&&
+       ) &&
        (
                p4 triggers -i <<-EOF
                Triggers:
index a6178e9fac9499c55a1f0c274c49c1615b87ef88..0bb1105ec3791eb6ccf254c34e4e25b8eb0aecda 100644 (file)
@@ -527,21 +527,6 @@ case $(echo $GIT_TRACE |tr "[A-Z]" "[a-z]") in
        ;;
 esac
 
-# Convenience
-#
-# A regexp to match 5, 35 and 40 hexdigits
-_x05='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
-_x35="$_x05$_x05$_x05$_x05$_x05$_x05$_x05"
-_x40="$_x35$_x05"
-
-# Zero SHA-1
-_z40=0000000000000000000000000000000000000000
-
-OID_REGEX="$_x40"
-ZERO_OID=$_z40
-EMPTY_TREE=4b825dc642cb6eb9a060e54bf8d69288fbee4904
-EMPTY_BLOB=e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
-
 # Line feed
 LF='
 '
@@ -915,6 +900,7 @@ maybe_setup_valgrind () {
        fi
 }
 
+trace_level_=0
 want_trace () {
        test "$trace" = t && {
                test "$verbose" = t || test "$verbose_log" = t
@@ -928,7 +914,7 @@ want_trace () {
 test_eval_inner_ () {
        # Do not add anything extra (including LF) after '$*'
        eval "
-               want_trace && set -x
+               want_trace && trace_level_=$(($trace_level_+1)) && set -x
                $*"
 }
 
@@ -959,7 +945,8 @@ test_eval_ () {
                test_eval_ret_=$?
                if want_trace
                then
-                       set +x
+                       test 1 = $trace_level_ && set +x
+                       trace_level_=$(($trace_level_-1))
                fi
        } 2>/dev/null 4>&2
 
@@ -1118,6 +1105,7 @@ finalize_junit_xml () {
                junit_time=$(test-tool date getnanos $junit_suite_start)
                sed -e "s/\(<testsuite.*\) time=\"[^\"]*\"/\1/" \
                        -e "s/<testsuite [^>]*/& time=\"$junit_time\"/" \
+                       -e '/^ *<\/testsuite/d' \
                        <"$junit_xml_path" >"$junit_xml_path.new"
                mv "$junit_xml_path.new" "$junit_xml_path"
 
@@ -1416,6 +1404,20 @@ then
        fi
 fi
 
+# Convenience
+# A regexp to match 5, 35 and 40 hexdigits
+_x05='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
+_x35="$_x05$_x05$_x05$_x05$_x05$_x05$_x05"
+_x40="$_x35$_x05"
+
+test_oid_init
+
+ZERO_OID=$(test_oid zero)
+OID_REGEX=$(echo $ZERO_OID | sed -e 's/0/[0-9a-f]/g')
+EMPTY_TREE=$(test_oid empty_tree)
+EMPTY_BLOB=$(test_oid empty_blob)
+_z40=$ZERO_OID
+
 # Provide an implementation of the 'yes' utility; the upper bound
 # limit is there to help Windows that cannot stop this loop from
 # wasting cycles when the downstream stops reading, so do not be
index 1ecdab330408a1cca7f703b6f2dc8d9b11ae0261..f618a644efa0f942619d0ed71c8ff4d68f11a4de 100644 (file)
@@ -371,6 +371,7 @@ static int check_updates(struct unpack_trees_options *o)
        state.quiet = 1;
        state.refresh_cache = 1;
        state.istate = index;
+       clone_checkout_metadata(&state.meta, &o->meta, NULL);
 
        if (!o->update || o->dry_run) {
                remove_marked_cache_entries(index, 0);
@@ -1815,9 +1816,6 @@ static void invalidate_ce_path(const struct cache_entry *ce,
 /*
  * Check that checking out ce->sha1 in subdir ce->name is not
  * going to overwrite any working files.
- *
- * Currently, git does not checkout subprojects during a superproject
- * checkout, so it is not going to overwrite anything.
  */
 static int verify_clean_submodule(const char *old_sha1,
                                  const struct cache_entry *ce,
@@ -2067,7 +2065,7 @@ static int merged_entry(const struct cache_entry *ce,
                }
                invalidate_ce_path(merge, o);
 
-               if (submodule_from_ce(ce)) {
+               if (submodule_from_ce(ce) && file_exists(ce->name)) {
                        int ret = check_submodule_move_head(ce, NULL,
                                                            oid_to_hex(&ce->oid),
                                                            o);
@@ -2096,7 +2094,7 @@ static int merged_entry(const struct cache_entry *ce,
                        invalidate_ce_path(old, o);
                }
 
-               if (submodule_from_ce(ce)) {
+               if (submodule_from_ce(ce) && file_exists(ce->name)) {
                        int ret = check_submodule_move_head(ce, oid_to_hex(&old->oid),
                                                            oid_to_hex(&ce->oid),
                                                            o);
index ae1557fb8046c957dfa1feef6349a0117617f389..ad41b45a7139aacad7ded1ddcf6774d5b031e7a7 100644 (file)
@@ -85,6 +85,7 @@ struct unpack_trees_options {
        struct index_state result;
 
        struct pattern_list *pl; /* for internal use */
+       struct checkout_metadata meta;
 };
 
 int unpack_trees(unsigned n, struct tree_desc *t,
index eba4fd3a03812f046dadc1baf9d7644ba8dfcce9..ee82235f260c21748ec987ada46a4422980d9c8b 100644 (file)
@@ -226,17 +226,20 @@ struct worktree *find_worktree(struct worktree **list,
 
 struct worktree *find_worktree_by_path(struct worktree **list, const char *p)
 {
+       struct strbuf wt_path = STRBUF_INIT;
        char *path = real_pathdup(p, 0);
 
        if (!path)
                return NULL;
        for (; *list; list++) {
-               const char *wt_path = real_path_if_valid((*list)->path);
+               if (!strbuf_realpath(&wt_path, (*list)->path, 0))
+                       continue;
 
-               if (wt_path && !fspathcmp(path, wt_path))
+               if (!fspathcmp(path, wt_path.buf))
                        break;
        }
        free(path);
+       strbuf_release(&wt_path);
        return *list;
 }
 
@@ -285,6 +288,7 @@ int validate_worktree(const struct worktree *wt, struct strbuf *errmsg,
                      unsigned flags)
 {
        struct strbuf wt_path = STRBUF_INIT;
+       struct strbuf realpath = STRBUF_INIT;
        char *path = NULL;
        int err, ret = -1;
 
@@ -336,7 +340,8 @@ int validate_worktree(const struct worktree *wt, struct strbuf *errmsg,
                goto done;
        }
 
-       ret = fspathcmp(path, real_path(git_common_path("worktrees/%s", wt->id)));
+       strbuf_realpath(&realpath, git_common_path("worktrees/%s", wt->id), 1);
+       ret = fspathcmp(path, realpath.buf);
 
        if (ret)
                strbuf_addf_gently(errmsg, _("'%s' does not point back to '%s'"),
@@ -344,6 +349,7 @@ int validate_worktree(const struct worktree *wt, struct strbuf *errmsg,
 done:
        free(path);
        strbuf_release(&wt_path);
+       strbuf_release(&realpath);
        return ret;
 }
 
@@ -450,7 +456,7 @@ const struct worktree *find_shared_symref(const char *symref,
 int submodule_uses_worktrees(const char *path)
 {
        char *submodule_gitdir;
-       struct strbuf sb = STRBUF_INIT;
+       struct strbuf sb = STRBUF_INIT, err = STRBUF_INIT;
        DIR *dir;
        struct dirent *d;
        int ret = 0;
@@ -464,18 +470,16 @@ int submodule_uses_worktrees(const char *path)
        get_common_dir_noenv(&sb, submodule_gitdir);
        free(submodule_gitdir);
 
-       /*
-        * The check below is only known to be good for repository format
-        * version 0 at the time of writing this code.
-        */
        strbuf_addstr(&sb, "/config");
        read_repository_format(&format, sb.buf);
-       if (format.version != 0) {
+       if (verify_repository_format(&format, &err)) {
+               strbuf_release(&err);
                strbuf_release(&sb);
                clear_repository_format(&format);
                return 1;
        }
        clear_repository_format(&format);
+       strbuf_release(&err);
 
        /* Replace config by worktrees. */
        strbuf_setlen(&sb, sb.len - strlen("config"));
index 71c3f25f43deffd56cb6cc73cb76b3cbfaa66290..73ab5d4da1c0b4766cb63501b9db06c0a7fea934 100644 (file)
@@ -38,9 +38,22 @@ enum show_ignored_type {
 enum commit_whence {
        FROM_COMMIT,     /* normal */
        FROM_MERGE,      /* commit came from merge */
-       FROM_CHERRY_PICK /* commit came from cherry-pick */
+       FROM_CHERRY_PICK_SINGLE, /* commit came from cherry-pick */
+       FROM_CHERRY_PICK_MULTI, /* commit came from a sequence of cherry-picks */
+       FROM_REBASE_PICK /* commit came from a pick/reword/edit */
 };
 
+static inline int is_from_cherry_pick(enum commit_whence whence)
+{
+       return whence == FROM_CHERRY_PICK_SINGLE ||
+               whence == FROM_CHERRY_PICK_MULTI;
+}
+
+static inline int is_from_rebase(enum commit_whence whence)
+{
+       return whence == FROM_REBASE_PICK;
+}
+
 struct wt_status_change_data {
        int worktree_status;
        int index_status;