]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'js/stash-p-fix'
authorJunio C Hamano <gitster@pobox.com>
Tue, 28 Apr 2020 22:50:06 +0000 (15:50 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 28 Apr 2020 22:50:06 +0000 (15:50 -0700)
Allowing the user to split a patch hunk while "git stash -p" does
not work well; a band-aid has been added to make this (partially)
work better.

* js/stash-p-fix:
  stash -p: (partially) fix bug concerning split hunks
  t3904: fix incorrect demonstration of a bug

323 files changed:
.gitignore
Documentation/CodingGuidelines
Documentation/Makefile
Documentation/MyFirstObjectWalk.txt
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/asciidoc.conf
Documentation/config.txt
Documentation/config/feature.txt
Documentation/config/fetch.txt
Documentation/config/format.txt
Documentation/config/http.txt
Documentation/config/pack.txt
Documentation/config/push.txt
Documentation/config/stash.txt
Documentation/config/submodule.txt
Documentation/config/tag.txt
Documentation/config/tar.txt [new file with mode: 0644]
Documentation/config/trace2.txt
Documentation/fetch-options.txt
Documentation/git-am.txt
Documentation/git-checkout.txt
Documentation/git-cherry-pick.txt
Documentation/git-clone.txt
Documentation/git-commit-tree.txt
Documentation/git-commit.txt
Documentation/git-fast-import.txt
Documentation/git-format-patch.txt
Documentation/git-grep.txt
Documentation/git-init.txt
Documentation/git-log.txt
Documentation/git-ls-files.txt
Documentation/git-p4.txt
Documentation/git-pack-objects.txt
Documentation/git-pull.txt
Documentation/git-read-tree.txt
Documentation/git-rebase.txt
Documentation/git-reset.txt
Documentation/git-restore.txt
Documentation/git-revert.txt
Documentation/git-switch.txt
Documentation/git.txt
Documentation/gitfaq.txt [new file with mode: 0644]
Documentation/githooks.txt
Documentation/gitsubmodules.txt
Documentation/howto/maintain-git.txt
Documentation/manpage-1.72.xsl [deleted file]
Documentation/manpage-base.xsl [deleted file]
Documentation/manpage-bold-literal.xsl
Documentation/manpage-normal.xsl
Documentation/manpage-suppress-sp.xsl [deleted file]
Documentation/merge-options.txt
Documentation/pull-fetch-param.txt
Documentation/rev-list-options.txt
Documentation/technical/api-trace2.txt
Documentation/user-manual.conf
GIT-VERSION-GEN
INSTALL
Makefile
RelNotes
abspath.c
add-interactive.c
add-patch.c
advice.c
advice.h
apply.c
archive-tar.c
archive.c
archive.h
bisect.c
builtin.h
builtin/cat-file.c
builtin/checkout.c
builtin/clean.c
builtin/clone.c
builtin/commit-graph.c
builtin/commit.c
builtin/describe.c
builtin/diff.c
builtin/fetch-pack.c
builtin/fetch.c
builtin/fmt-merge-msg.c
builtin/help.c
builtin/index-pack.c
builtin/init-db.c
builtin/log.c
builtin/merge-base.c
builtin/merge.c
builtin/pack-objects.c
builtin/prune-packed.c
builtin/prune.c
builtin/pull.c
builtin/rebase.c
builtin/receive-pack.c
builtin/reflog.c
builtin/repack.c
builtin/reset.c
builtin/rev-parse.c
builtin/send-pack.c
builtin/stash.c
builtin/submodule--helper.c
builtin/tag.c
builtin/worktree.c
cache.h
ci/lib.sh
combine-diff.c
commit-graph.c
commit-slab.h
commit.c
compat/mingw.c
compat/win32/path-utils.h
config.c
config.h
config.mak.dev
connect.c
connected.c
connected.h
contrib/fast-import/import-tars.perl
contrib/subtree/Makefile
convert.c
convert.h
credential.c
credential.h
csum-file.c
delta-islands.c
diff.c
diffcore-break.c
diffcore-rename.c
diffcore.h
editor.c
entry.c
environment.c
fast-import.c
fetch-pack.c
fmt-merge-msg.c [new file with mode: 0644]
fmt-merge-msg.h
fsck.c
git-compat-util.h
git-legacy-stash.sh [deleted file]
git-p4.py
git-submodule.sh
git.c
gitweb/gitweb.perl
gpg-interface.c
gpg-interface.h
hash.h
hex.c
http.c
line-log.c
ll-merge.c
log-tree.c
ls-refs.c
merge-recursive.c
merge.c
midx.c
object-store.h
object.h
oid-array.c [moved from sha1-array.c with 93% similarity]
oid-array.h [moved from sha1-array.h with 97% similarity]
oidset.h
parse-options-cb.c
parse-options.c
path.c
pretty.c
pretty.h
promisor-remote.c
promisor-remote.h
prompt.c
prompt.h
prune-packed.c [new file with mode: 0644]
prune-packed.h [new file with mode: 0644]
ref-filter.c
ref-filter.h
refs.c
remote-curl.c
remote.c
repo-settings.c
repository.c
repository.h
revision.c
revision.h
run-command.c
send-pack.c
sequencer.c
sequencer.h
setup.c
sha1-file.c
sha1-name.c
sha256/gcrypt.h
shallow.c
shell.c
submodule-config.c
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-oid-array.c [moved from t/helper/test-sha1-array.c with 83% similarity]
t/helper/test-path-utils.c
t/helper/test-pkt-line.c
t/helper/test-repository.c
t/helper/test-tool.c
t/helper/test-tool.h
t/lib-credential.sh [changed mode: 0755->0644]
t/lib-gpg.sh [changed mode: 0755->0644]
t/lib-log-graph.sh [changed mode: 0755->0644]
t/lib-submodule-update.sh [changed mode: 0755->0644]
t/perf/p5310-pack-bitmaps.sh
t/perf/p9300-fast-import-export.sh [new file with mode: 0755]
t/t0000-basic.sh
t/t0001-init.sh
t/t0007-git-var.sh
t/t0018-advice.sh [new file with mode: 0755]
t/t0021-conversion.sh
t/t0021/rot13-filter.pl
t/t0040-parse-options.sh
t/t0060-path-utils.sh
t/t0064-sha1-array.sh
t/t0212-trace2-event.sh
t/t0300-credentials.sh
t/t1450-fsck.sh
t/t2402-worktree-list.sh
t/t3402-rebase-merge.sh
t/t3403-rebase-skip.sh
t/t3404-rebase-interactive.sh
t/t3406-rebase-message.sh
t/t3417-rebase-whitespace-fix.sh
t/t3419-rebase-patch-id.sh
t/t3421-rebase-topology-linear.sh
t/t3424-rebase-empty.sh
t/t3431-rebase-fork-point.sh
t/t3432-rebase-fast-forward.sh
t/t3435-rebase-gpg-sign.sh [new file with mode: 0755]
t/t3507-cherry-pick-conflict.sh
t/t3510-cherry-pick-sequence.sh
t/t3514-cherry-pick-revert-gpg.sh [new file with mode: 0755]
t/t3700-add.sh
t/t3701-add-interactive.sh
t/t3903-stash.sh
t/t4014-format-patch.sh
t/t4057-diff-combined-paths.sh
t/t4061-diff-indent.sh
t/t4067-diff-partial-clone.sh
t/t4124-apply-ws-rule.sh
t/t4150-am.sh
t/t4202-log.sh
t/t4208-log-magic-pathspec.sh
t/t5003-archive-zip.sh
t/t5319-multi-pack-index.sh
t/t5319/no-objects.midx [new file with mode: 0644]
t/t5322-pack-objects-sparse.sh
t/t5324-split-commit-graph.sh
t/t5504-fetch-receive-strict.sh
t/t5512-ls-remote.sh
t/t5516-fetch-push.sh
t/t5521-pull-options.sh
t/t5537-fetch-shallow.sh
t/t5541-http-push-smart.sh
t/t5543-atomic-push.sh
t/t5548-push-porcelain.sh [new file with mode: 0755]
t/t5550-http-fetch-dumb.sh
t/t5562-http-backend-content-length.sh
t/t5604-clone-reference.sh
t/t5607-clone-bundle.sh
t/t5611-clone-config.sh
t/t5612-clone-refspec.sh
t/t5616-partial-clone.sh
t/t5703-upload-pack-ref-in-want.sh
t/t5704-protocol-violations.sh [new file with mode: 0755]
t/t5801-remote-helpers.sh
t/t6012-rev-list-simplify.sh
t/t6120-describe.sh
t/t6200-fmt-merge-msg.sh
t/t6300-for-each-ref.sh
t/t7004-tag.sh
t/t7063-status-untracked-cache.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-functions.sh
t/test-lib.sh
trace2.c
trace2.h
trace2/tr2_cfg.c
trace2/tr2_cfg.h
trace2/tr2_sysenv.c
trace2/tr2_sysenv.h
transport-helper.c
transport.c
transport.h
unpack-trees.c
unpack-trees.h
upload-pack.c
userdiff.c
worktree.c
wrapper.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
index ed4e443a3cd99f8f86b88951e42509f49b14f638..390ceece523ed9ec2221563d991f8ec0bf83b461 100644 (file)
@@ -91,8 +91,6 @@ For shell scripts specifically (not exhaustive):
 
    - No shell arrays.
 
-   - No strlen ${#parameter}.
-
    - No pattern replacement ${parameter/pattern/string}.
 
  - We use Arithmetic Expansion $(( ... )).
index 8fe829cc1b83945f25ae59b427d36d11e024091f..15d9d04f3164b939ec91695f4ec1c3b2fd6c00b3 100644 (file)
@@ -30,6 +30,7 @@ MAN7_TXT += gitcredentials.txt
 MAN7_TXT += gitcvs-migration.txt
 MAN7_TXT += gitdiffcore.txt
 MAN7_TXT += giteveryday.txt
+MAN7_TXT += gitfaq.txt
 MAN7_TXT += gitglossary.txt
 MAN7_TXT += gitnamespaces.txt
 MAN7_TXT += gitremote-helpers.txt
@@ -149,32 +150,9 @@ endif
 -include ../config.mak.autogen
 -include ../config.mak
 
-#
-# For docbook-xsl ...
-#      -1.68.1,        no extra settings are needed?
-#      1.69.0,         set ASCIIDOC_ROFF?
-#      1.69.1-1.71.0,  set DOCBOOK_SUPPRESS_SP?
-#      1.71.1,         set ASCIIDOC_ROFF?
-#      1.72.0,         set DOCBOOK_XSL_172.
-#      1.73.0-,        no extra settings are needed
-#
-
-ifdef DOCBOOK_XSL_172
-ASCIIDOC_EXTRA += -a git-asciidoc-no-roff
-MANPAGE_XSL = manpage-1.72.xsl
-else
-       ifndef ASCIIDOC_ROFF
-       # docbook-xsl after 1.72 needs the regular XSL, but will not
-       # pass-thru raw roff codes from asciidoc.conf, so turn them off.
-       ASCIIDOC_EXTRA += -a git-asciidoc-no-roff
-       endif
-endif
 ifndef NO_MAN_BOLD_LITERAL
 XMLTO_EXTRA += -m manpage-bold-literal.xsl
 endif
-ifdef DOCBOOK_SUPPRESS_SP
-XMLTO_EXTRA += -m manpage-suppress-sp.xsl
-endif
 
 # Newer DocBook stylesheet emits warning cruft in the output when
 # this is not set, and if set it shows an absolute link.  Older
index aa828dfdc44a856c8bdd8b0826defeb398113bcc..c3f2d1a831e3b1d9c3e7cd5d555248e01bffbd73 100644 (file)
@@ -357,9 +357,6 @@ static void walken_commit_walk(struct rev_info *rev)
        ...
 
        while ((commit = get_revision(rev))) {
-               if (!commit)
-                       continue;
-
                strbuf_reset(&prettybuf);
                pp_commit_easy(CMIT_FMT_ONELINE, commit, &prettybuf);
                puts(prettybuf.buf);
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..0bd2dc7
--- /dev/null
@@ -0,0 +1,217 @@
+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).
+
+ * "git p4" learned four new hooks and also "--no-verify" option to
+   bypass them (and the existing "p4-pre-submit" hook).
+
+ * "git pull" shares many options with underlying "git fetch", but
+   some of them were not documented and some of those that would make
+   sense to pass down were not passed down.
+
+ * "git rebase" learned the "--no-gpg-sign" option to countermand
+   commit.gpgSign the user may have.
+
+ * The output from "git format-patch" uses RFC 2047 encoding for
+   non-ASCII letters on From: and Subject: headers, so that it can
+   directly be fed to e-mail programs.  A new option has been added
+   to produce these headers in raw.
+
+ * "git log" learned "--show-pulls" that helps pathspec limited
+   history views; a merge commit that takes the whole change from a
+   side branch, which is normally omitted from the output, is shown
+   in addition to the commits that introduce real changes.
+
+ * The interactive input from various codepaths are consolidated and
+   any prompt possibly issued earlier are fflush()ed before we read.
+
+ * Allow "git rebase" to reapply all local commits, even if the may be
+   already in the upstream, without checking first.
+
+ * The 'pack.useSparse' configuration variable now defaults to 'true',
+   enabling an optimization that has been experimental since Git 2.21.
+
+
+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.
+
+ * Enable tests that require GnuPG on Windows.
+
+ * Minor test usability improvement.
+
+ * Trace2 enhancement to allow logging of the environment variables.
+
+ * Test clean-up continues.
+
+ * Perf-test update.
+
+ * A Windows-specific test element has been made more robust against
+   misuse from both user's environment and programmer's errors.
+
+
+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).
+
+ * The import-tars importer (in contrib/fast-import/) used to create
+   phony files at the top-level of the repository when the archive
+   contains global PAX headers, which made its own logic to detect and
+   omit the common leading directory ineffective, which has been
+   corrected.
+   (merge c839fcff65 js/import-tars-do-not-make-phony-files-from-pax-headers later to maint).
+
+ * Simplify the commit ancestry connectedness check in a partial clone
+   repository in which "promised" objects are assumed to be obtainable
+   lazily on-demand from promisor remote repositories.
+   (merge 2b98478c6f jt/connectivity-check-optim-in-partial-clone later to maint).
+
+ * The server-end of the v2 protocol to serve "git clone" and "git
+   fetch" was not prepared to see a delim packets at unexpected
+   places, which led to a crash.
+   (merge cacae4329f jk/harden-protocol-v2-delim-handling later to maint).
+
+ * When fed a midx that records no objects, some codepaths tried to
+   loop from 0 through (num_objects-1), which, due to integer
+   arithmetic wrapping around, made it nonsense operation with out of
+   bounds array accesses.  The code has been corrected to reject such
+   an midx file.
+   (merge 796d61cdc0 dr/midx-avoid-int-underflow later to maint).
+
+ * Utitiles run via the run_command() API were not spawned correctly
+   on Cygwin, when the paths to them are given as a full path with
+   backslashes.
+   (merge 05ac8582bc ak/run-command-on-cygwin-fix later to maint).
+
+ * "git pull --rebase" tried to run a rebase even after noticing that
+   the pull results in a fast-forward and no rebase is needed nor
+   sensible, for the past few years due to a mistake nobody noticed.
+   (merge fbae70ddc6 en/pull-do-not-rebase-after-fast-forwarding later to maint).
+
+ * "git rebase" with the merge backend did not work well when the
+   rebase.abbreviateCommands configuration was set.
+   (merge de9f1d3ef4 ag/rebase-merge-allow-ff-under-abbrev-command later to maint).
+
+ * The logic to auto-follow tags by "git clone --single-branch" was
+   not careful to avoid lazy-fetching unnecessary tags, which has been
+   corrected.
+   (merge 167a575e2d jk/use-quick-lookup-in-clone-for-tag-following later to maint).
+
+ * "git rebase -i" did not leave the reflog entries correctly.
+   (merge 1f6965f994 en/sequencer-reflog-action later to maint).
+
+ * The more aggressive updates to remote-tracking branches we had for
+   the past 7 years or so were not reflected in the documentation,
+   which has been corrected.
+   (merge a44088435c pb/pull-fetch-doc later to maint).
+
+ * We've left the command line parsing of "git log :/a/b/" broken for
+   about a full year without anybody noticing, which has been
+   corrected.
+   (merge 0220461071 jc/missing-ref-store-fix later to maint).
+
+ * Misc fixes for Windows.
+   (merge 3efc128cd5 js/mingw-fixes later to maint).
+
+ * "git rebase" (again) learns to honor "--no-keep-empty", which lets
+   the user to discard commits that are empty from the beginning (as
+   opposed to the ones that become empty because of rebasing).  The
+   interactive rebase also marks commits that are empty in the todo.
+   (merge 50ed76148a en/rebase-no-keep-empty later to maint).
+
+ * Parsing the host part out of URL for the credential helper has been corrected.
+   (merge 4c5971e18a jk/credential-parsing-end-of-host-in-URL 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).
+   (merge 64d1022e14 ar/test-style-fixes later to maint).
+   (merge 4a465443a6 ds/doc-clone-filter later to maint).
+   (merge bb2dbe301b jk/t3419-drop-expensive-tests later to maint).
+   (merge d3507cc712 js/test-junit-finalization-fix later to maint).
+   (merge 2149b6748f bc/faq later to maint).
+   (merge 12dc0879f1 jk/test-cleanup later to maint).
+   (merge 344420bf0f pb/rebase-doc-typofix later to maint).
+   (merge 7cd54d37dc dl/wrapper-fix-indentation later to maint).
+   (merge 78725ebda9 jc/allow-strlen-substitution-in-shell-scripts later to maint).
+   (merge 2ecfcdecc6 jm/gitweb-fastcgi-utf8 later to maint).
+   (merge 0740d0a5d3 jk/oid-array-cleanups later to maint).
+   (merge a1aba0c95c js/t0007-typofix later to maint).
+   (merge 76ba7fa225 ma/config-doc-fix later to maint).
index 8fc4b67081a649a6bd54a27f8cee77d41be79327..3e4c13971b4a7c02a769e87685331822c844e238 100644 (file)
@@ -31,24 +31,6 @@ ifdef::backend-docbook[]
 endif::backend-docbook[]
 
 ifdef::backend-docbook[]
-ifndef::git-asciidoc-no-roff[]
-# "unbreak" docbook-xsl v1.68 for manpages. v1.69 works with or without this.
-# v1.72 breaks with this because it replaces dots not in roff requests.
-[listingblock]
-<example><title>{title}</title>
-<literallayout class="monospaced">
-ifdef::doctype-manpage[]
-&#10;.ft C&#10;
-endif::doctype-manpage[]
-|
-ifdef::doctype-manpage[]
-&#10;.ft&#10;
-endif::doctype-manpage[]
-</literallayout>
-{title#}</example>
-endif::git-asciidoc-no-roff[]
-
-ifdef::git-asciidoc-no-roff[]
 ifdef::doctype-manpage[]
 # The following two small workarounds insert a simple paragraph after screen
 [listingblock]
@@ -67,7 +49,6 @@ ifdef::doctype-manpage[]
 {title#}</para></formalpara>
 {title%}<simpara></simpara>
 endif::doctype-manpage[]
-endif::git-asciidoc-no-roff[]
 endif::backend-docbook[]
 
 ifdef::doctype-manpage[]
index 08b13ba72be53bf36e05f7095964c5206d795945..74009d5402cf486005d7ee580c74c96171c012f1 100644 (file)
@@ -220,12 +220,12 @@ Example
 ; affected by the condition
 [includeIf "gitdir:/path/to/group/"]
        path = foo.inc
-----
 
-       ; include only if we are in a worktree where foo-branch is
-       ; currently checked out
-       [includeIf "onbranch:foo-branch"]
-               path = foo.inc
+; include only if we are in a worktree where foo-branch is
+; currently checked out
+[includeIf "onbranch:foo-branch"]
+       path = foo.inc
+----
 
 Values
 ~~~~~~
@@ -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 f11940280fe303d53794b1f6de50c63e3a2829be..b1a9b1461d30f4e79bd194d968cebbb8617b3181 100644 (file)
@@ -1,11 +1,14 @@
 fetch.recurseSubmodules::
-       This option can be either set to a boolean value or to 'on-demand'.
+       This option controls whether `git fetch` (and the underlying fetch
+       in `git pull`) will recursively fetch into populated submodules.
+       This option can be set either to a boolean value or to 'on-demand'.
        Setting it to a boolean changes the behavior of fetch and pull to
-       unconditionally recurse into submodules when set to true or to not
-       recurse at all when set to false. When set to 'on-demand' (the default
-       value), fetch and pull will only recurse into a populated submodule
-       when its superproject retrieves a commit that updates the submodule's
+       recurse unconditionally into submodules when set to true or to not
+       recurse at all when set to false. When set to 'on-demand', fetch and
+       pull will only recurse into a populated submodule when its
+       superproject retrieves a commit that updates the submodule's
        reference.
+       Defaults to 'on-demand', or to the value of 'submodule.recurse' if set.
 
 fetch.fsckObjects::
        If it is set to true, git-fetch-pack will check all fetched
index 45c7bd5a8ff8723ec3478a33fbca4216a5b465fa..564e8091ba5ce6788ad97c2109b14afd6d235c83 100644 (file)
@@ -57,6 +57,11 @@ format.suffix::
        `.patch`. Use this variable to change that suffix (make sure to
        include the dot if you want it).
 
+format.encodeEmailHeaders::
+       Encode email headers that have non-ASCII characters with
+       "Q-encoding" (described in RFC 2047) for email transmission.
+       Defaults to true.
+
 format.pretty::
        The default pretty format for log/show/whatchanged command,
        See linkgit:git-log[1], linkgit:git-show[1],
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 0a7aa322a9be695cd9863a519bf3fae5b789a7df..f5e5b38c6889e92d5366e7be1140db57e8f708c4 100644 (file)
@@ -112,3 +112,5 @@ push.recurseSubmodules::
        is 'no' then default behavior of ignoring submodules when pushing
        is retained. You may override this configuration at time of push by
        specifying '--recurse-submodules=check|on-demand|no'.
+       If not set, 'no' is used by default, unless 'submodule.recurse' is
+       set (in which case a 'true' value means 'on-demand').
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 b33177151c5fe745652fc64a6fe4ec9858fc18c2..d7a63c8c12bbc24c637dfe5c4c767fdcfb1876ad 100644 (file)
@@ -59,9 +59,17 @@ submodule.active::
 
 submodule.recurse::
        Specifies if commands recurse into submodules by default. This
-       applies to all commands that have a `--recurse-submodules` option,
-       except `clone`.
+       applies to all commands that have a `--recurse-submodules` option
+       (`checkout`, `fetch`, `grep`, `pull`, `push`, `read-tree`, `reset`,
+       `restore` and `switch`) except `clone` and `ls-files`.
        Defaults to false.
+       When set to true, it can be deactivated via the
+       `--no-recurse-submodules` option. Note that some Git commands
+       lacking this option may call some of the above commands affected by
+       `submodule.recurse`; for instance `git remote update` will call
+       `git fetch` but does not have a `--no-recurse-submodules` option.
+       For these commands a workaround is to temporarily change the
+       configuration value by using `git -c submodule.recurse=0`.
 
 submodule.fetchJobs::
        Specifies how many submodules are fetched/cloned at the same time.
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 4ce0b9a6d17fdd47d6b3d6cc36ea3ee4ea74cc0f..01d3afd8a8b38d3e6a722018be2fc4544907bf16 100644 (file)
@@ -48,6 +48,15 @@ trace2.configParams::
        May be overridden by the `GIT_TRACE2_CONFIG_PARAMS` environment
        variable.  Unset by default.
 
+trace2.envVars::
+       A comma-separated list of "important" environment variables that should
+       be recorded in the trace2 output.  For example,
+       `GIT_HTTP_USER_AGENT,GIT_CONFIG` would cause the trace2 output to
+       contain events listing the overrides for HTTP user agent and the
+       location of the Git configuration file (assuming any are set).  May be
+       overriden by the `GIT_TRACE2_ENV_VARS` environment variable.  Unset by
+       default.
+
 trace2.destinationDebug::
        Boolean.  When true Git will print error messages when a
        trace target destination cannot be opened for writing.
index a115a1ae0e973dc0375e82cbc1de5eff182aa9a1..6e2a160a47cb8c07a72600d2fc7ad404bf2bc1c1 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
@@ -163,7 +163,9 @@ ifndef::git-pull[]
        value. Use 'on-demand' to only recurse into a populated submodule
        when the superproject retrieves a commit that updates the submodule's
        reference to a commit that isn't already in the local submodule
-       clone.
+       clone. By default, 'on-demand' is used, unless
+       `fetch.recurseSubmodules` is set (see linkgit:git-config[1]).
+endif::git-pull[]
 
 -j::
 --jobs=<n>::
@@ -177,9 +179,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 +192,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
@@ -200,7 +205,6 @@ default fetches are performed sequentially, not in parallel.
        recursion (such as settings in linkgit:gitmodules[5] and
        linkgit:git-config[1]) override this option, as does
        specifying --[no-]recurse-submodules directly.
-endif::git-pull[]
 
 -u::
 --update-head-ok::
@@ -210,6 +214,7 @@ endif::git-pull[]
        to communicate with 'git fetch', and unless you are
        implementing your own Porcelain you are not supposed to
        use it.
+endif::git-pull[]
 
 --upload-pack <upload-pack>::
        When given, and the repository to fetch from is handled
index ab5754e05d16d70b79cf90437bc999628aa99151..38c0852139c17077aacb6bc43fcf8102cc6570f4 100644 (file)
@@ -148,9 +148,12 @@ default.   You can use `--no-utf8` to override this.
 
 -S[<keyid>]::
 --gpg-sign[=<keyid>]::
+--no-gpg-sign::
        GPG-sign commits. The `keyid` argument is optional and
        defaults to the committer identity; if specified, it must be
-       stuck to the option without a space.
+       stuck to the option without a space. `--no-gpg-sign` is useful to
+       countermand both `commit.gpgSign` configuration variable, and
+       earlier `--gpg-sign`.
 
 --continue::
 -r::
index c8fb995fa74ee9262ddd71514aeb250376d30a1b..5b697eee1b7632a046b48d488b3c3af75ad7fa45 100644 (file)
@@ -292,11 +292,11 @@ Note that this option uses the no overlay mode by default (see also
 
 --recurse-submodules::
 --no-recurse-submodules::
-       Using `--recurse-submodules` will update the content of all initialized
+       Using `--recurse-submodules` will update the content of all active
        submodules according to the commit recorded in the superproject. If
        local modifications in a submodule would be overwritten the checkout
        will fail unless `-f` is used. If nothing (or `--no-recurse-submodules`)
-       is used, the work trees of submodules will not be updated.
+       is used, submodules working trees will not be updated.
        Just like linkgit:git-submodule[1], this will detach `HEAD` of the
        submodule.
 
index 83ce51aedfea54fd5150ef142aca24f8c1df95c9..75feeef08a0e64ac3f6bfc35290e92984c1a1580 100644 (file)
@@ -109,9 +109,12 @@ effect to your index in a row.
 
 -S[<keyid>]::
 --gpg-sign[=<keyid>]::
+--no-gpg-sign::
        GPG-sign commits. The `keyid` argument is optional and
        defaults to the committer identity; if specified, it must be
-       stuck to the option without a space.
+       stuck to the option without a space. `--no-gpg-sign` is useful to
+       countermand both `commit.gpgSign` configuration variable, and
+       earlier `--gpg-sign`.
 
 --ff::
        If the current HEAD is the same as the parent of the
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 ec15ee8d6fad83d5dfe7d019cbb5984303552d9e..2e2c5810983a89a22e3280a276b5e8fd1509c670 100644 (file)
@@ -61,13 +61,11 @@ OPTIONS
 
 -S[<keyid>]::
 --gpg-sign[=<keyid>]::
+--no-gpg-sign::
        GPG-sign commits. The `keyid` argument is optional and
        defaults to the committer identity; if specified, it must be
-       stuck to the option without a space.
-
---no-gpg-sign::
-       Do not GPG-sign commit, to countermand a `--gpg-sign` option
-       given earlier on the command line.
+       stuck to the option without a space. `--no-gpg-sign` is useful to
+       countermand a `--gpg-sign` option given earlier on the command line.
 
 Commit Information
 ------------------
index 13f653989f32f1231030d8e89a70cb6a96c9955f..a3baea32aedddfa00077adc5f2a2b36e38a5ea26 100644 (file)
@@ -348,13 +348,12 @@ changes to tracked files.
 
 -S[<keyid>]::
 --gpg-sign[=<keyid>]::
+--no-gpg-sign::
        GPG-sign commits. The `keyid` argument is optional and
        defaults to the committer identity; if specified, it must be
-       stuck to the option without a space.
-
---no-gpg-sign::
-       Countermand `commit.gpgSign` configuration variable that is
-       set to force each and every commit to be signed.
+       stuck to the option without a space. `--no-gpg-sign` is useful to
+       countermand both `commit.gpgSign` configuration variable, and
+       earlier `--gpg-sign`.
 
 \--::
        Do not interpret any more arguments as options.
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 0d4f8951bbb1d1df0ebba15d2619d5a39568469d..0f81d0437bb65bd0ee68ef63edf7bb48d9ea87a6 100644 (file)
@@ -24,6 +24,7 @@ SYNOPSIS
                   [(--reroll-count|-v) <n>]
                   [--to=<email>] [--cc=<email>]
                   [--[no-]cover-letter] [--quiet]
+                  [--[no-]encode-email-headers]
                   [--no-notes | --notes[=<ref>]]
                   [--interdiff=<previous>]
                   [--range-diff=<previous> [--creation-factor=<percent>]]
@@ -253,6 +254,13 @@ feeding the result to `git send-email`.
        containing the branch description, shortlog and the overall diffstat.  You can
        fill in a description in the file before sending it out.
 
+--encode-email-headers::
+--no-encode-email-headers::
+       Encode email headers that have non-ASCII characters with
+       "Q-encoding" (described in RFC 2047), instead of outputting the
+       headers verbatim. Defaults to the value of the
+       `format.encodeEmailHeaders` configuration variable.
+
 --interdiff=<previous>::
        As a reviewer aid, insert an interdiff into the cover letter,
        or as commentary of the lone patch of a 1-patch series, showing
index ddb6acc0257ef25c415de9e63e4fb9bacd1728ea..cdf8e26b479ef644696c67cd384e9802efd834c8 100644 (file)
@@ -93,7 +93,7 @@ OPTIONS
        with `--no-index`.
 
 --recurse-submodules::
-       Recursively search in each submodule that has been initialized and
+       Recursively search in each submodule that is active and
        checked out in the repository.  When used in combination with the
        <tree> option the prefix of all submodule output will be the name of
        the parent project's <tree> object. This option has no effect
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 bed09bb09e52c6ddc0923e6173f6f5b5dceceb4a..619577f23b0460453aa55caa32cd1605610ed5f3 100644 (file)
@@ -49,6 +49,7 @@ OPTIONS
        Print out the ref name given on the command line by which each
        commit was reached.
 
+--[no-]mailmap::
 --[no-]use-mailmap::
        Use mailmap file to map author and committer names and email
        addresses to canonical real names and email addresses. See
index 8461c0e83e9d535b25755be1d5045cdc30145cb4..3cb2ebb4380e28e8e4b4e1bb5e3f4ae511960b7a 100644 (file)
@@ -148,7 +148,7 @@ a space) at the start of each line:
        top directory.
 
 --recurse-submodules::
-       Recursively calls ls-files on each submodule in the repository.
+       Recursively calls ls-files on each active submodule in the repository.
        Currently there is only support for the --cached mode.
 
 --abbrev[=<n>]::
index 3494a1db3ebf4991ed05011a8f4035807db7a9f6..dab9609013fd5c4fb1947bc5f8cd9cd4e2b5063c 100644 (file)
@@ -374,14 +374,55 @@ These options can be used to modify 'git p4 submit' behavior.
     been submitted. Implies --disable-rebase. Can also be set with
     git-p4.disableP4Sync. Sync with origin/master still goes ahead if possible.
 
-Hook for submit
-~~~~~~~~~~~~~~~
+Hooks for submit
+----------------
+
+p4-pre-submit
+~~~~~~~~~~~~~
+
 The `p4-pre-submit` hook is executed if it exists and is executable.
 The hook takes no parameters and nothing from standard input. Exiting with
 non-zero status from this script prevents `git-p4 submit` from launching.
+It can be bypassed with the `--no-verify` command line option.
 
 One usage scenario is to run unit tests in the hook.
 
+p4-prepare-changelist
+~~~~~~~~~~~~~~~~~~~~~
+
+The `p4-prepare-changelist` hook is executed right after preparing
+the default changelist message and before the editor is started.
+It takes one parameter, the name of the file that contains the
+changelist text. Exiting with a non-zero status from the script
+will abort the process.
+
+The purpose of the hook is to edit the message file in place,
+and it is not supressed by the `--no-verify` option. This hook
+is called even if `--prepare-p4-only` is set.
+
+p4-changelist
+~~~~~~~~~~~~~
+
+The `p4-changelist` hook is executed after the changelist
+message has been edited by the user. It can be bypassed with the
+`--no-verify` option. It takes a single parameter, the name
+of the file that holds the proposed changelist text. Exiting
+with a non-zero status causes the command to abort.
+
+The hook is allowed to edit the changelist file and can be used
+to normalize the text into some project standard format. It can
+also be used to refuse the Submit after inspect the message file.
+
+p4-post-changelist
+~~~~~~~~~~~~~~~~~~
+
+The `p4-post-changelist` hook is invoked after the submit has
+successfully occured in P4. It takes no parameters and is meant
+primarily for notification and cannot affect the outcome of the
+git p4 submit action.
+
+
+
 Rebase options
 ~~~~~~~~~~~~~~
 These options can be used to modify 'git p4 rebase' behavior.
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 dfb901f8b8358ea309e6403079e3c87fb82fee6f..9c7ab2d48f1a91a7e3be27e5eb6828df8dad3b67 100644 (file)
@@ -85,8 +85,9 @@ OPTIONS
        Pass --verbose to git-fetch and git-merge.
 
 --[no-]recurse-submodules[=yes|on-demand|no]::
-       This option controls if new commits of all populated submodules should
-       be fetched and updated, too (see linkgit:git-config[1] and
+       This option controls if new commits of populated submodules should
+       be fetched, and if the working trees of active submodules should be
+       updated, too (see linkgit:git-fetch[1], linkgit:git-config[1] and
        linkgit:gitmodules[5]).
 +
 If the checkout is done via rebase, local submodule commits are rebased as well.
@@ -229,9 +230,9 @@ branch.<name>.merge options; see linkgit:git-config[1] for details.
 $ git pull origin next
 ------------------------------------------------
 +
-This leaves a copy of `next` temporarily in FETCH_HEAD, but
-does not update any remote-tracking branches. Using remote-tracking
-branches, the same can be done by invoking fetch and merge:
+This leaves a copy of `next` temporarily in FETCH_HEAD, and
+updates the remote-tracking branch `origin/next`.
+The same can be done by invoking fetch and merge:
 +
 ------------------------------------------------
 $ git fetch origin
index da33f84f33d2c5869ce730169ff0f0f23edd42d1..5fa8bab64c2d0bb80f88ace023b5eafc898b05f1 100644 (file)
@@ -116,9 +116,9 @@ OPTIONS
        located in.
 
 --[no-]recurse-submodules::
-       Using --recurse-submodules will update the content of all initialized
+       Using --recurse-submodules will update the content of all active
        submodules according to the commit recorded in the superproject by
-       calling read-tree recursively, also setting the submodules HEAD to be
+       calling read-tree recursively, also setting the submodules' HEAD to be
        detached at that commit.
 
 --no-sparse-checkout::
index f7a6033607fa558dfd5a0437d7b4327ff1c44c78..2de86206836fbdf88527309a46afdc2afd47df79 100644 (file)
@@ -277,20 +277,51 @@ See also INCOMPATIBLE OPTIONS below.
        Other options, like --exec, will use the default of drop unless
        -i/--interactive is explicitly specified.
 +
-Note that commits which start empty are kept, and commits which are
-clean cherry-picks (as determined by `git log --cherry-mark ...`) are
-always dropped.
+Note that commits which start empty are kept (unless --no-keep-empty
+is specified), and commits which are clean cherry-picks (as determined
+by `git log --cherry-mark ...`) are detected and dropped as a
+preliminary step (unless --reapply-cherry-picks is passed).
 +
 See also INCOMPATIBLE OPTIONS below.
 
+--no-keep-empty::
 --keep-empty::
-       No-op.  Rebasing commits that started empty (had no change
-       relative to their parent) used to fail and this option would
-       override that behavior, allowing commits with empty changes to
-       be rebased.  Now commits with no changes do not cause rebasing
-       to halt.
+       Do not keep commits that start empty before the rebase
+       (i.e. that do not change anything from its parent) in the
+       result.  The default is to keep commits which start empty,
+       since creating such commits requires passing the --allow-empty
+       override flag to `git commit`, signifying that a user is very
+       intentionally creating such a commit and thus wants to keep
+       it.
 +
-See also BEHAVIORAL DIFFERENCES and INCOMPATIBLE OPTIONS below.
+Usage of this flag will probably be rare, since you can get rid of
+commits that start empty by just firing up an interactive rebase and
+removing the lines corresponding to the commits you don't want.  This
+flag exists as a convenient shortcut, such as for cases where external
+tools generate many empty commits and you want them all removed.
++
+For commits which do not start empty but become empty after rebasing,
+see the --empty flag.
++
+See also INCOMPATIBLE OPTIONS below.
+
+--reapply-cherry-picks::
+--no-reapply-cherry-picks::
+       Reapply all clean cherry-picks of any upstream commit instead
+       of preemptively dropping them. (If these commits then become
+       empty after rebasing, because they contain a subset of already
+       upstream changes, the behavior towards them is controlled by
+       the `--empty` flag.)
++
+By default (or if `--no-reapply-cherry-picks` is given), these commits
+will be automatically dropped.  Because this necessitates reading all
+upstream commits, this can be expensive in repos with a large number
+of upstream commits that need to be read.
++
+`--reapply-cherry-picks` allows rebase to forgo reading all upstream
+commits, potentially improving performance.
++
+See also INCOMPATIBLE OPTIONS below.
 
 --allow-empty-message::
        No-op.  Rebasing commits with an empty message used to fail
@@ -354,9 +385,12 @@ See also INCOMPATIBLE OPTIONS below.
 
 -S[<keyid>]::
 --gpg-sign[=<keyid>]::
+--no-gpg-sign::
        GPG-sign commits. The `keyid` argument is optional and
        defaults to the committer identity; if specified, it must be
-       stuck to the option without a space.
+       stuck to the option without a space. `--no-gpg-sign` is useful to
+       countermand both `commit.gpgSign` configuration variable, and
+       earlier `--gpg-sign`.
 
 -q::
 --quiet::
@@ -587,8 +621,9 @@ are incompatible with the following options:
  * --preserve-merges
  * --interactive
  * --exec
- * --keep-empty
+ * --no-keep-empty
  * --empty=
+ * --reapply-cherry-picks
  * --edit-todo
  * --root when used in combination with --onto
 
@@ -605,7 +640,7 @@ BEHAVIORAL DIFFERENCES
 -----------------------
 
 git rebase has two primary backends: apply and merge.  (The apply
-backend used to known as the 'am' backend, but the name led to
+backend used to be known as the 'am' backend, but the name led to
 confusion as it looks like a verb instead of a noun.  Also, the merge
 backend used to be known as the interactive backend, but it is now
 used for non-interactive cases as well.  Both were renamed based on
@@ -620,12 +655,15 @@ commits that started empty, though these are rare in practice.  It
 also drops commits that become empty and has no option for controlling
 this behavior.
 
-The merge backend keeps intentionally empty commits.  Similar to the
-apply backend, by default the merge backend drops commits that become
-empty unless -i/--interactive is specified (in which case it stops and
-asks the user what to do).  The merge backend also has an
---empty={drop,keep,ask} option for changing the behavior of handling
-commits that become empty.
+The merge backend keeps intentionally empty commits by default (though
+with -i they are marked as empty in the todo list editor, or they can
+be dropped automatically with --no-keep-empty).
+
+Similar to the apply backend, by default the merge backend drops
+commits that become empty unless -i/--interactive is specified (in
+which case it stops and asks the user what to do).  The merge backend
+also has an --empty={drop,keep,ask} option for changing the behavior
+of handling commits that become empty.
 
 Directory rename detection
 ~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -684,9 +722,17 @@ Hooks
 ~~~~~
 
 The apply backend has not traditionally called the post-commit hook,
-while the merge backend has.  However, this was by accident of
-implementation rather than by design.  Both backends should have the
-same behavior, though it is not clear which one is correct.
+while the merge backend has.  Both have called the post-checkout hook,
+though the merge backend has squelched its output.  Further, both
+backends only call the post-checkout hook with the starting point
+commit of the rebase, not the intermediate commits nor the final
+commit.  In each case, the calling of these hooks was by accident of
+implementation rather than by design (both backends were originally
+implemented as shell scripts and happened to invoke other commands
+like 'git checkout' or 'git commit' that would call the hooks).  Both
+backends should have the same behavior, though it is not entirely
+clear which, if any, is correct.  We will likely make rebase stop
+calling either of these hooks in the future.
 
 Interruptability
 ~~~~~~~~~~~~~~~~
@@ -1002,7 +1048,8 @@ Only works if the changes (patch IDs based on the diff contents) on
 'subsystem' did.
 
 In that case, the fix is easy because 'git rebase' knows to skip
-changes that are already present in the new upstream.  So if you say
+changes that are already present in the new upstream (unless
+`--reapply-cherry-picks` is given). So if you say
 (assuming you're on 'topic')
 ------------
     $ git rebase subsystem
index 932080c55d2c232236c630a26c9968874882b1f3..252e2d4e47d10404226cce6abfc0132a4d2e9b74 100644 (file)
@@ -87,6 +87,12 @@ but carries forward unmerged index entries.
        different between `<commit>` and `HEAD`.
        If a file that is different between `<commit>` and `HEAD` has local
        changes, reset is aborted.
+
+--[no-]recurse-submodules::
+       When the working tree is updated, using --recurse-submodules will
+       also recursively reset the working tree of all active submodules
+       according to the commit recorded in the superproject, also setting
+       the submodules' HEAD to be detached at that commit.
 --
 
 See "Reset, restore and revert" in linkgit:git[1] for the differences
index 5bf60d49434109918fedbc6dd9051a2f65d24316..8e3b33980281336765ac1749cfe6add3ce9cda4e 100644 (file)
@@ -107,6 +107,17 @@ in linkgit:git-checkout[1] for details.
        patterns and unconditionally restores any files in
        `<pathspec>`.
 
+--recurse-submodules::
+--no-recurse-submodules::
+       If `<pathspec>` names an active submodule and the restore location
+       includes the working tree, the submodule will only be updated if
+       this option is given, in which case its working tree will be
+       restored to the commit recorded in the superproject, and any local
+       modifications overwritten. If nothing (or
+       `--no-recurse-submodules`) is used, submodules working trees will
+       not be updated. Just like linkgit:git-checkout[1], this will detach
+       `HEAD` of the submodule.
+
 --overlay::
 --no-overlay::
        In overlay mode, the command never removes files when
index 9d22270757c9b5d402f680a3f5933989678cb6f0..044276e9da62af44af9a8d897df0dd536556610b 100644 (file)
@@ -90,9 +90,12 @@ effect to your index in a row.
 
 -S[<keyid>]::
 --gpg-sign[=<keyid>]::
+--no-gpg-sign::
        GPG-sign commits. The `keyid` argument is optional and
        defaults to the committer identity; if specified, it must be
-       stuck to the option without a space.
+       stuck to the option without a space. `--no-gpg-sign` is useful to
+       countermand both `commit.gpgSign` configuration variable, and
+       earlier `--gpg-sign`.
 
 -s::
 --signoff::
index 197900363b0b7daa273fc2b39f7bed6961f08e09..3759c3a265b5b9761a9df4d8e6b9d733d29aee04 100644 (file)
@@ -181,9 +181,9 @@ name, the guessing is aborted.  You can explicitly give a name with
 --recurse-submodules::
 --no-recurse-submodules::
        Using `--recurse-submodules` will update the content of all
-       initialized submodules according to the commit recorded in the
+       active submodules according to the commit recorded in the
        superproject. If nothing (or `--no-recurse-submodules`) is
-       used, the work trees of submodules will not be updated. Just
+       used, submodules working trees will not be updated. Just
        like linkgit:git-submodule[1], this will detach `HEAD` of the
        submodules.
 
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`::
diff --git a/Documentation/gitfaq.txt b/Documentation/gitfaq.txt
new file mode 100644 (file)
index 0000000..1cf83df
--- /dev/null
@@ -0,0 +1,337 @@
+gitfaq(7)
+=========
+
+NAME
+----
+gitfaq - Frequently asked questions about using Git
+
+SYNOPSIS
+--------
+gitfaq
+
+DESCRIPTION
+-----------
+
+The examples in this FAQ assume a standard POSIX shell, like `bash` or `dash`,
+and a user, A U Thor, who has the account `author` on the hosting provider
+`git.example.org`.
+
+Configuration
+-------------
+
+[[user-name]]
+What should I put in `user.name`?::
+       You should put your personal name, generally a form using a given name
+       and family name.  For example, the current maintainer of Git uses "Junio
+       C Hamano".  This will be the name portion that is stored in every commit
+       you make.
++
+This configuration doesn't have any effect on authenticating to remote services;
+for that, see `credential.username` in linkgit:git-config[1].
+
+[[http-postbuffer]]
+What does `http.postBuffer` really do?::
+       This option changes the size of the buffer that Git uses when pushing
+       data to a remote over HTTP or HTTPS.  If the data is larger than this
+       size, libcurl, which handles the HTTP support for Git, will use chunked
+       transfer encoding since it isn't known ahead of time what the size of
+       the pushed data will be.
++
+Leaving this value at the default size is fine unless you know that either the
+remote server or a proxy in the middle doesn't support HTTP/1.1 (which
+introduced the chunked transfer encoding) or is known to be broken with chunked
+data.  This is often (erroneously) suggested as a solution for generic push
+problems, but since almost every server and proxy supports at least HTTP/1.1,
+raising this value usually doesn't solve most push problems.  A server or proxy
+that didn't correctly support HTTP/1.1 and chunked transfer encoding wouldn't be
+that useful on the Internet today, since it would break lots of traffic.
++
+Note that increasing this value will increase the memory used on every relevant
+push that Git does over HTTP or HTTPS, since the entire buffer is allocated
+regardless of whether or not it is all used.  Thus, it's best to leave it at the
+default unless you are sure you need a different value.
+
+[[configure-editor]]
+How do I configure a different editor?::
+       If you haven't specified an editor specifically for Git, it will by default
+       use the editor you've configured using the `VISUAL` or `EDITOR` environment
+       variables, or if neither is specified, the system default (which is usually
+       `vi`).  Since some people find `vi` difficult to use or prefer a different
+       editor, it may be desirable to change the editor used.
++
+If you want to configure a general editor for most programs which need one, you
+can edit your shell configuration (e.g., `~/.bashrc` or `~/.zshenv`) to contain
+a line setting the `EDITOR` or `VISUAL` environment variable to an appropriate
+value.  For example, if you prefer the editor `nano`, then you could write the
+following:
++
+----
+export VISUAL=nano
+----
++
+If you want to configure an editor specifically for Git, you can either set the
+`core.editor` configuration value or the `GIT_EDITOR` environment variable.  You
+can see linkgit:git-var[1] for details on the order in which these options are
+consulted.
++
+Note that in all cases, the editor value will be passed to the shell, so any
+arguments containing spaces should be appropriately quoted.  Additionally, if
+your editor normally detaches from the terminal when invoked, you should specify
+it with an argument that makes it not do that, or else Git will not see any
+changes.  An example of a configuration addressing both of these issues on
+Windows would be the configuration `"C:\Program Files\Vim\gvim.exe" --nofork`,
+which quotes the filename with spaces and specifies the `--nofork` option to
+avoid backgrounding the process.
+
+Credentials
+-----------
+
+[[http-credentials]]
+How do I specify my credentials when pushing over HTTP?::
+       The easiest way to do this is to use a credential helper via the
+       `credential.helper` configuration.  Most systems provide a standard
+       choice to integrate with the system credential manager.  For example,
+       Git for Windows provides the `wincred` credential manager, macOS has the
+       `osxkeychain` credential manager, and Unix systems with a standard
+       desktop environment can use the `libsecret` credential manager.  All of
+       these store credentials in an encrypted store to keep your passwords or
+       tokens secure.
++
+In addition, you can use the `store` credential manager which stores in a file
+in your home directory, or the `cache` credential manager, which does not
+permanently store your credentials, but does prevent you from being prompted for
+them for a certain period of time.
++
+You can also just enter your password when prompted.  While it is possible to
+place the password (which must be percent-encoded) in the URL, this is not
+particularly secure and can lead to accidental exposure of credentials, so it is
+not recommended.
+
+[[http-credentials-environment]]
+How do I read a password or token from an environment variable?::
+       The `credential.helper` configuration option can also take an arbitrary
+       shell command that produces the credential protocol on standard output.
+       This is useful when passing credentials into a container, for example.
++
+Such a shell command can be specified by starting the option value with an
+exclamation point.  If your password or token were stored in the `GIT_TOKEN`,
+you could run the following command to set your credential helper:
++
+----
+$ git config credential.helper \
+       '!f() { echo username=author; echo "password=$GIT_TOKEN"; };f'
+----
+
+[[http-reset-credentials]]
+How do I change the password or token I've saved in my credential manager?::
+       Usually, if the password or token is invalid, Git will erase it and
+       prompt for a new one.  However, there are times when this doesn't always
+       happen.  To change the password or token, you can erase the existing
+       credentials and then Git will prompt for new ones.  To erase
+       credentials, use a syntax like the following (substituting your username
+       and the hostname):
++
+----
+$ echo url=https://author@git.example.org | git credential reject
+----
+
+[[multiple-accounts-http]]
+How do I use multiple accounts with the same hosting provider using HTTP?::
+       Usually the easiest way to distinguish between these accounts is to use
+       the username in the URL.  For example, if you have the accounts `author`
+       and `committer` on `git.example.org`, you can use the URLs
+       https://author@git.example.org/org1/project1.git and
+       https://committer@git.example.org/org2/project2.git.  This way, when you
+       use a credential helper, it will automatically try to look up the
+       correct credentials for your account.  If you already have a remote set
+       up, you can change the URL with something like `git remote set-url
+       origin https://author@git.example.org/org1/project1.git` (see
+       linkgit:git-remote[1] for details).
+
+[[multiple-accounts-ssh]]
+How do I use multiple accounts with the same hosting provider using SSH?::
+       With most hosting providers that support SSH, a single key pair uniquely
+       identifies a user.  Therefore, to use multiple accounts, it's necessary
+       to create a key pair for each account.  If you're using a reasonably
+       modern OpenSSH version, you can create a new key pair with something
+       like `ssh-keygen -t ed25519 -f ~/.ssh/id_committer`.  You can then
+       register the public key (in this case, `~/.ssh/id_committer.pub`; note
+       the `.pub`) with the hosting provider.
++
+Most hosting providers use a single SSH account for pushing; that is, all users
+push to the `git` account (e.g., `git@git.example.org`).  If that's the case for
+your provider, you can set up multiple aliases in SSH to make it clear which key
+pair to use.  For example, you could write something like the following in
+`~/.ssh/config`, substituting the proper private key file:
++
+----
+# This is the account for author on git.example.org.
+Host example_author
+       HostName git.example.org
+       User git
+       # This is the key pair registered for author with git.example.org.
+       IdentityFile ~/.ssh/id_author
+       IdentitiesOnly yes
+# This is the account for committer on git.example.org.
+Host example_committer
+       HostName git.example.org
+       User git
+       # This is the key pair registered for committer with git.example.org.
+       IdentityFile ~/.ssh/id_committer
+       IdentitiesOnly yes
+----
++
+Then, you can adjust your push URL to use `git@example_author` or
+`git@example_committer` instead of `git@example.org` (e.g., `git remote set-url
+git@example_author:org1/project1.git`).
+
+Common Issues
+-------------
+
+[[last-commit-amend]]
+I've made a mistake in the last commit.  How do I change it?::
+       You can make the appropriate change to your working tree, run `git add
+       <file>` or `git rm <file>`, as appropriate, to stage it, and then `git
+       commit --amend`.  Your change will be included in the commit, and you'll
+       be prompted to edit the commit message again; if you wish to use the
+       original message verbatim, you can use the `--no-edit` option to `git
+       commit` in addition, or just save and quit when your editor opens.
+
+[[undo-previous-change]]
+I've made a change with a bug and it's been included in the main branch.  How should I undo it?::
+       The usual way to deal with this is to use `git revert`.  This preserves
+       the history that the original change was made and was a valuable
+       contribution, but also introduces a new commit that undoes those changes
+       because the original had a problem.  The commit message of the revert
+       indicates the commit which was reverted and is usually edited to include
+       an explanation as to why the revert was made.
+
+[[ignore-tracked-files]]
+How do I ignore changes to a tracked file?::
+       Git doesn't provide a way to do this.  The reason is that if Git needs
+       to overwrite this file, such as during a checkout, it doesn't know
+       whether the changes to the file are precious and should be kept, or
+       whether they are irrelevant and can safely be destroyed.  Therefore, it
+       has to take the safe route and always preserve them.
++
+It's tempting to try to use certain features of `git update-index`, namely the
+assume-unchanged and skip-worktree bits, but these don't work properly for this
+purpose and shouldn't be used this way.
++
+If your goal is to modify a configuration file, it can often be helpful to have
+a file checked into the repository which is a template or set of defaults which
+can then be copied alongside and modified as appropriate.  This second, modified
+file is usually ignored to prevent accidentally committing it.
+
+Hooks
+-----
+
+[[restrict-with-hooks]]
+How do I use hooks to prevent users from making certain changes?::
+       The only safe place to make these changes is on the remote repository
+       (i.e., the Git server), usually in the `pre-receive` hook or in a
+       continuous integration (CI) system.  These are the locations in which
+       policy can be enforced effectively.
++
+It's common to try to use `pre-commit` hooks (or, for commit messages,
+`commit-msg` hooks) to check these things, which is great if you're working as a
+solo developer and want the tooling to help you.  However, using hooks on a
+developer machine is not effective as a policy control because a user can bypass
+these hooks with `--no-verify` without being noticed (among various other ways).
+Git assumes that the user is in control of their local repositories and doesn't
+try to prevent this or tattle on the user.
++
+In addition, some advanced users find `pre-commit` hooks to be an impediment to
+workflows that use temporary commits to stage work in progress or that create
+fixup commits, so it's better to push these kinds of checks to the server
+anyway.
+
+Cross-Platform Issues
+---------------------
+
+[[windows-text-binary]]
+I'm on Windows and my text files are detected as binary.::
+       Git works best when you store text files as UTF-8.  Many programs on
+       Windows support UTF-8, but some do not and only use the little-endian
+       UTF-16 format, which Git detects as binary.  If you can't use UTF-8 with
+       your programs, you can specify a working tree encoding that indicates
+       which encoding your files should be checked out with, while still
+       storing these files as UTF-8 in the repository.  This allows tools like
+       linkgit:git-diff[1] to work as expected, while still allowing your tools
+       to work.
++
+To do so, you can specify a linkgit:gitattributes[5] pattern with the
+`working-tree-encoding` attribute.  For example, the following pattern sets all
+C files to use UTF-16LE-BOM, which is a common encoding on Windows:
++
+----
+*.c    working-tree-encoding=UTF-16LE-BOM
+----
++
+You will need to run `git add --renormalize` to have this take effect.  Note
+that if you are making these changes on a project that is used across platforms,
+you'll probably want to make it in a per-user configuration file or in the one
+in `$GIT_DIR/info/attributes`, since making it in a `.gitattributes` file in the
+repository will apply to all users of the repository.
++
+See the following entry for information about normalizing line endings as well,
+and see linkgit:gitattributes[5] for more information about attribute files.
+
+[[windows-diff-control-m]]
+I'm on Windows and git diff shows my files as having a `^M` at the end.::
+       By default, Git expects files to be stored with Unix line endings.  As such,
+       the carriage return (`^M`) that is part of a Windows line ending is shown
+       because it is considered to be trailing whitespace.  Git defaults to showing
+       trailing whitespace only on new lines, not existing ones.
++
+You can store the files in the repository with Unix line endings and convert
+them automatically to your platform's line endings.  To do that, set the
+configuration option `core.eol` to `native` and see the following entry for
+information about how to configure files as text or binary.
++
+You can also control this behavior with the `core.whitespace` setting if you
+don't wish to remove the carriage returns from your line endings.
+
+[[recommended-storage-settings]]
+What's the recommended way to store files in Git?::
+       While Git can store and handle any file of any type, there are some
+       settings that work better than others.  In general, we recommend that
+       text files be stored in UTF-8 without a byte-order mark (BOM) with LF
+       (Unix-style) endings.  We also recommend the use of UTF-8 (again,
+       without BOM) in commit messages.  These are the settings that work best
+       across platforms and with tools such as `git diff` and `git merge`.
++
+Additionally, if you have a choice between storage formats that are text based
+or non-text based, we recommend storing files in the text format and, if
+necessary, transforming them into the other format.  For example, a text-based
+SQL dump with one record per line will work much better for diffing and merging
+than an actual database file.  Similarly, text-based formats such as Markdown
+and AsciiDoc will work better than binary formats such as Microsoft Word and
+PDF.
++
+Similarly, storing binary dependencies (e.g., shared libraries or JAR files) or
+build products in the repository is generally not recommended.  Dependencies and
+build products are best stored on an artifact or package server with only
+references, URLs, and hashes stored in the repository.
++
+We also recommend setting a linkgit:gitattributes[5] file to explicitly mark
+which files are text and which are binary.  If you want Git to guess, you can
+set the attribute `text=auto`.  For example, the following might be appropriate
+in some projects:
++
+----
+# By default, guess.
+*      text=auto
+# Mark all C files as text.
+*.c    text
+# Mark all JPEG files as binary.
+*.jpg  binary
+----
++
+These settings help tools pick the right format for output such as patches and
+result in files being checked out in the appropriate line ending for the
+platform.
+
+GIT
+---
+Part of the linkgit:git[1] suite
index 3dccab53758717d6e7822d5cdfefd002864e9276..81f2a87e88ba5fee51419b615a60352a8dd3c999 100644 (file)
@@ -522,12 +522,61 @@ The exit status determines whether git will use the data from the
 hook to limit its search.  On error, it will fall back to verifying
 all files and folders.
 
+p4-changelist
+~~~~~~~~~~~~~
+
+This hook is invoked by `git-p4 submit`.
+
+The `p4-changelist` hook is executed after the changelist
+message has been edited by the user. It can be bypassed with the
+`--no-verify` option. It takes a single parameter, the name
+of the file that holds the proposed changelist text. Exiting
+with a non-zero status causes the command to abort.
+
+The hook is allowed to edit the changelist file and can be used
+to normalize the text into some project standard format. It can
+also be used to refuse the Submit after inspect the message file.
+
+Run `git-p4 submit --help` for details.
+
+p4-prepare-changelist
+~~~~~~~~~~~~~~~~~~~~~
+
+This hook is invoked by `git-p4 submit`.
+
+The `p4-prepare-changelist` hook is executed right after preparing
+the default changelist message and before the editor is started.
+It takes one parameter, the name of the file that contains the
+changelist text. Exiting with a non-zero status from the script
+will abort the process.
+
+The purpose of the hook is to edit the message file in place,
+and it is not supressed by the `--no-verify` option. This hook
+is called even if `--prepare-p4-only` is set.
+
+Run `git-p4 submit --help` for details.
+
+p4-post-changelist
+~~~~~~~~~~~~~~~~~~
+
+This hook is invoked by `git-p4 submit`.
+
+The `p4-post-changelist` hook is invoked after the submit has
+successfully occured in P4. It takes no parameters and is meant
+primarily for notification and cannot affect the outcome of the
+git p4 submit action.
+
+Run `git-p4 submit --help` for details.
+
 p4-pre-submit
 ~~~~~~~~~~~~~
 
 This hook is invoked by `git-p4 submit`. It takes no parameters and nothing
 from standard input. Exiting with non-zero status from this script prevent
-`git-p4 submit` from launching. Run `git-p4 submit --help` for details.
+`git-p4 submit` from launching. It can be bypassed with the `--no-verify`
+command line option. Run `git-p4 submit --help` for details.
+
+
 
 post-index-change
 ~~~~~~~~~~~~~~~~~
index c476f891b5cefdd74076f29acea25f270f3d2243..f9f4e65c9e7a3f2444411e98cbd42c61b689d47a 100644 (file)
@@ -271,7 +271,8 @@ will not be checked out by default; You can instruct 'clone' to recurse
 into submodules. The 'init' and 'update' subcommands of 'git submodule'
 will maintain submodules checked out and at an appropriate revision in
 your working tree. Alternatively you can set 'submodule.recurse' to have
-'checkout' recursing into submodules.
+'checkout' recursing into submodules (note that 'submodule.recurse' also
+affects other git commands, see linkgit:git-config[1] for a complete list).
 
 
 SEE ALSO
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
 --------
diff --git a/Documentation/manpage-1.72.xsl b/Documentation/manpage-1.72.xsl
deleted file mode 100644 (file)
index b4d315c..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-<!-- manpage-1.72.xsl:
-     special settings for manpages rendered from asciidoc+docbook
-     handles peculiarities in docbook-xsl 1.72.0 -->
-<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
-               version="1.0">
-
-<xsl:import href="manpage-base.xsl"/>
-
-<!-- these are the special values for the roff control characters
-     needed for docbook-xsl 1.72.0 -->
-<xsl:param name="git.docbook.backslash">&#x2593;</xsl:param>
-<xsl:param name="git.docbook.dot"      >&#x2302;</xsl:param>
-
-</xsl:stylesheet>
diff --git a/Documentation/manpage-base.xsl b/Documentation/manpage-base.xsl
deleted file mode 100644 (file)
index a264fa6..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-<!-- manpage-base.xsl:
-     special formatting for manpages rendered from asciidoc+docbook -->
-<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
-               version="1.0">
-
-<!-- these params silence some output from xmlto -->
-<xsl:param name="man.output.quietly" select="1"/>
-<xsl:param name="refentry.meta.get.quietly" select="1"/>
-
-<!-- convert asciidoc callouts to man page format;
-     git.docbook.backslash and git.docbook.dot params
-     must be supplied by another XSL file or other means -->
-<xsl:template match="co">
-       <xsl:value-of select="concat(
-                             $git.docbook.backslash,'fB(',
-                             substring-after(@id,'-'),')',
-                             $git.docbook.backslash,'fR')"/>
-</xsl:template>
-<xsl:template match="calloutlist">
-       <xsl:value-of select="$git.docbook.dot"/>
-       <xsl:text>sp&#10;</xsl:text>
-       <xsl:apply-templates/>
-       <xsl:text>&#10;</xsl:text>
-</xsl:template>
-<xsl:template match="callout">
-       <xsl:value-of select="concat(
-                             $git.docbook.backslash,'fB',
-                             substring-after(@arearefs,'-'),
-                             '. ',$git.docbook.backslash,'fR')"/>
-       <xsl:apply-templates/>
-       <xsl:value-of select="$git.docbook.dot"/>
-       <xsl:text>br&#10;</xsl:text>
-</xsl:template>
-
-</xsl:stylesheet>
index 94d6c1b54565f88c56c489d604150ed18b8a49b3..e13db856932567d62cf1cb87ab36a7278581fa0d 100644 (file)
@@ -8,11 +8,9 @@
      this makes literal text easier to distinguish in manpages
      viewed on a tty -->
 <xsl:template match="literal|d:literal">
-       <xsl:value-of select="$git.docbook.backslash"/>
-       <xsl:text>fB</xsl:text>
+       <xsl:text>\fB</xsl:text>
        <xsl:apply-templates/>
-       <xsl:value-of select="$git.docbook.backslash"/>
-       <xsl:text>fR</xsl:text>
+       <xsl:text>\fR</xsl:text>
 </xsl:template>
 
 </xsl:stylesheet>
index a48f5b11f3dcc9227131d3a5caf1de4f857f0b28..a9c7ec69f46d8c631e86449aafdf327d8e51d435 100644 (file)
@@ -1,13 +1,26 @@
 <!-- manpage-normal.xsl:
-     special settings for manpages rendered from asciidoc+docbook
-     handles anything we want to keep away from docbook-xsl 1.72.0 -->
+     special settings for manpages rendered from asciidoc+docbook -->
 <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                version="1.0">
 
-<xsl:import href="manpage-base.xsl"/>
 
-<!-- these are the normal values for the roff control characters -->
-<xsl:param name="git.docbook.backslash">\</xsl:param>
-<xsl:param name="git.docbook.dot"      >.</xsl:param>
+<!-- these params silence some output from xmlto -->
+<xsl:param name="man.output.quietly" select="1"/>
+<xsl:param name="refentry.meta.get.quietly" select="1"/>
+
+<!-- convert asciidoc callouts to man page format -->
+<xsl:template match="co">
+       <xsl:value-of select="concat('\fB(',substring-after(@id,'-'),')\fR')"/>
+</xsl:template>
+<xsl:template match="calloutlist">
+       <xsl:text>.sp&#10;</xsl:text>
+       <xsl:apply-templates/>
+       <xsl:text>&#10;</xsl:text>
+</xsl:template>
+<xsl:template match="callout">
+       <xsl:value-of select="concat('\fB',substring-after(@arearefs,'-'),'. \fR')"/>
+       <xsl:apply-templates/>
+       <xsl:text>.br&#10;</xsl:text>
+</xsl:template>
 
 </xsl:stylesheet>
diff --git a/Documentation/manpage-suppress-sp.xsl b/Documentation/manpage-suppress-sp.xsl
deleted file mode 100644 (file)
index a63c763..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-<!-- manpage-suppress-sp.xsl:
-     special settings for manpages rendered from asciidoc+docbook
-     handles erroneous, inline .sp in manpage output of some
-     versions of docbook-xsl -->
-<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
-               version="1.0">
-
-<!-- attempt to work around spurious .sp at the tail of the line
-     that some versions of docbook stylesheets seem to add -->
-<xsl:template match="simpara">
-  <xsl:variable name="content">
-    <xsl:apply-templates/>
-  </xsl:variable>
-  <xsl:value-of select="normalize-space($content)"/>
-  <xsl:if test="not(ancestor::authorblurb) and
-                not(ancestor::personblurb)">
-    <xsl:text>&#10;&#10;</xsl:text>
-  </xsl:if>
-</xsl:template>
-
-</xsl:stylesheet>
index 40dc4f5e8c6e2d5a59cfbbeea1df7254068a6ed2..fb3a6e8d429acceab689dc22374e13ca0d7e314c 100644 (file)
@@ -61,9 +61,12 @@ When not possible, refuse to merge and exit with a non-zero status.
 
 -S[<keyid>]::
 --gpg-sign[=<keyid>]::
+--no-gpg-sign::
        GPG-sign the resulting merge commit. The `keyid` argument is
        optional and defaults to the committer identity; if specified,
-       it must be stuck to the option without a space.
+       it must be stuck to the option without a space. `--no-gpg-sign`
+       is useful to countermand both `commit.gpgSign` configuration variable,
+       and earlier `--gpg-sign`.
 
 --log[=<n>]::
 --no-log::
index 7d3a60f5b9361cd46adc4f005694a52ed06fd1c1..95ea84990298bf2c0e0dc4b03ee53e3563018a6c 100644 (file)
@@ -19,7 +19,8 @@ ifndef::git-pull[]
        (see <<CRTB,CONFIGURED REMOTE-TRACKING BRANCHES>> below).
 endif::git-pull[]
 ifdef::git-pull[]
-       (see linkgit:git-fetch[1]).
+       (see the section "CONFIGURED REMOTE-TRACKING BRANCHES"
+       in linkgit:git-fetch[1]).
 endif::git-pull[]
 +
 The format of a <refspec> parameter is an optional plus
index bfd02ade99161b64ba89978bfbf9693d7340a962..04ad7dd36ebd311cbb7acb33a94dcc631f393f0a 100644 (file)
@@ -342,6 +342,12 @@ Default mode::
        branches if the end result is the same (i.e. merging branches
        with the same content)
 
+--show-pulls::
+       Include all commits from the default mode, but also any merge
+       commits that are not TREESAME to the first parent but are
+       TREESAME to a later parent. This mode is helpful for showing
+       the merge commits that "first introduced" a change to a branch.
+
 --full-history::
        Same as the default mode, but does not prune some history.
 
@@ -534,7 +540,7 @@ Note the major differences in `N`, `P`, and `Q` over `--full-history`:
   parent and is TREESAME.
 --
 
-Finally, there is a fifth simplification mode available:
+There is another simplification mode available:
 
 --ancestry-path::
        Limit the displayed commits to those directly on the ancestry
@@ -573,6 +579,132 @@ option does. Applied to the 'D..M' range, it results in:
                                L--M
 -----------------------------------------------------------------------
 
+Before discussing another option, `--show-pulls`, we need to
+create a new example history.
++
+A common problem users face when looking at simplified history is that a
+commit they know changed a file somehow does not appear in the file's
+simplified history. Let's demonstrate a new example and show how options
+such as `--full-history` and `--simplify-merges` works in that case:
++
+-----------------------------------------------------------------------
+         .-A---M-----C--N---O---P
+        /     / \  \  \/   /   /
+       I     B   \  R-'`-Z'   /
+        \   /     \/         /
+         \ /      /\        /
+          `---X--'  `---Y--'
+-----------------------------------------------------------------------
++
+For this example, suppose `I` created `file.txt` which was modified by
+`A`, `B`, and `X` in different ways. The single-parent commits `C`, `Z`,
+and `Y` do not change `file.txt`. The merge commit `M` was created by
+resolving the merge conflict to include both changes from `A` and `B`
+and hence is not TREESAME to either. The merge commit `R`, however, was
+created by ignoring the contents of `file.txt` at `M` and taking only
+the contents of `file.txt` at `X`. Hence, `R` is TREESAME to `X` but not
+`M`. Finally, the natural merge resolution to create `N` is to take the
+contents of `file.txt` at `R`, so `N` is TREESAME to `R` but not `C`.
+The merge commits `O` and `P` are TREESAME to their first parents, but
+not to their second parents, `Z` and `Y` respectively.
++
+When using the default mode, `N` and `R` both have a TREESAME parent, so
+those edges are walked and the others are ignored. The resulting history
+graph is:
++
+-----------------------------------------------------------------------
+       I---X
+-----------------------------------------------------------------------
++
+When using `--full-history`, Git walks every edge. This will discover
+the commits `A` and `B` and the merge `M`, but also will reveal the
+merge commits `O` and `P`. With parent rewriting, the resulting graph is:
++
+-----------------------------------------------------------------------
+         .-A---M--------N---O---P
+        /     / \  \  \/   /   /
+       I     B   \  R-'`--'   /
+        \   /     \/         /
+         \ /      /\        /
+          `---X--'  `------'
+-----------------------------------------------------------------------
++
+Here, the merge commits `O` and `P` contribute extra noise, as they did
+not actually contribute a change to `file.txt`. They only merged a topic
+that was based on an older version of `file.txt`. This is a common
+issue in repositories using a workflow where many contributors work in
+parallel and merge their topic branches along a single trunk: manu
+unrelated merges appear in the `--full-history` results.
++
+When using the `--simplify-merges` option, the commits `O` and `P`
+disappear from the results. This is because the rewritten second parents
+of `O` and `P` are reachable from their first parents. Those edges are
+removed and then the commits look like single-parent commits that are
+TREESAME to their parent. This also happens to the commit `N`, resulting
+in a history view as follows:
++
+-----------------------------------------------------------------------
+         .-A---M--.
+        /     /    \
+       I     B      R
+        \   /      /
+         \ /      /
+          `---X--'
+-----------------------------------------------------------------------
++
+In this view, we see all of the important single-parent changes from
+`A`, `B`, and `X`. We also see the carefully-resolved merge `M` and the
+not-so-carefully-resolved merge `R`. This is usually enough information
+to determine why the commits `A` and `B` "disappeared" from history in
+the default view. However, there are a few issues with this approach.
++
+The first issue is performance. Unlike any previous option, the
+`--simplify-merges` option requires walking the entire commit history
+before returning a single result. This can make the option difficult to
+use for very large repositories.
++
+The second issue is one of auditing. When many contributors are working
+on the same repository, it is important which merge commits introduced
+a change into an important branch. The problematic merge `R` above is
+not likely to be the merge commit that was used to merge into an
+important branch. Instead, the merge `N` was used to merge `R` and `X`
+into the important branch. This commit may have information about why
+the change `X` came to override the changes from `A` and `B` in its
+commit message.
++
+The `--show-pulls` option helps with both of these issues by adding more
+merge commits to the history results. If a merge is not TREESAME to its
+first parent but is TREESAME to a later parent, then that merge is
+treated as if it "pulled" the change from another branch. When using
+`--show-pulls` on this example (and no other options) the resulting
+graph is:
++
+-----------------------------------------------------------------------
+       I---X---R---N
+-----------------------------------------------------------------------
++
+Here, the merge commits `R` and `N` are included because they pulled
+the commits `X` and `R` into the base branch, respectively. These
+merges are the reason the commits `A` and `B` do not appear in the
+default history.
++
+When `--show-pulls` is paired with `--simplify-merges`, the
+graph includes all of the necessary information:
++
+-----------------------------------------------------------------------
+         .-A---M--.   N
+        /     /    \ /
+       I     B      R
+        \   /      /
+         \ /      /
+          `---X--'
+-----------------------------------------------------------------------
++
+Notice that since `M` is reachable from `R`, the edge from `N` to `M`
+was simplified away. However, `N` still appears in the history as an
+important commit because it "pulled" the change `R` into the main
+branch.
+
 The `--simplify-by-decoration` option allows you to view only the
 big picture of the topology of the history, by omitting commits
 that are not referenced by tags.  Commits are marked as !TREESAME
index 4f07ceadcb3fb5f8cc3479d0cd9fbeb0adec18b2..6b6085585d5659ea6427247c18c96c84fdef5ade 100644 (file)
@@ -656,7 +656,8 @@ The "exec_id" field is a command-unique id and is only useful if the
 ------------
 
 `"def_param"`::
-       This event is generated to log a global parameter.
+       This event is generated to log a global parameter, such as a config
+       setting, command-line flag, or environment variable.
 +
 ------------
 {
index d87294de2f54c354c6215fb6029a182c966d6b2e..0148f126dcdf6aca15a5560fb5b122b85b022461 100644 (file)
@@ -9,13 +9,3 @@ tilde=&#126;
 
 [linkgit-inlinemacro]
 <ulink url="{target}.html">{target}{0?({0})}</ulink>
-
-ifdef::backend-docbook[]
-# "unbreak" docbook-xsl v1.68 for manpages. v1.69 works with or without this.
-[listingblock]
-<example><title>{title}</title>
-<literallayout class="monospaced">
-|
-</literallayout>
-{title#}</example>
-endif::backend-docbook[]
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='
 '
diff --git a/INSTALL b/INSTALL
index 22c364f34f573c615d968a85374ba2c429b7fabd..9ba33e6a141a3906eb707dd11d1af4b0f8191a55 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -206,9 +206,7 @@ Issues of note:
    clone two separate git-htmldocs and git-manpages repositories next
    to the clone of git itself.
 
-   It has been reported that docbook-xsl version 1.72 and 1.73 are
-   buggy; 1.72 misformats manual pages for callouts, and 1.73 needs
-   the patch in contrib/patches/docbook-xsl-manpages-charmap.patch
+   The minimum supported version of docbook-xsl is 1.74.
 
    Users attempting to build the documentation on Cygwin may need to ensure
    that the /etc/xml/catalog file looks something like this:
index 9804a0758b2458f0ba3d7130c83d7d22d9570879..6e3b3c22a82519ff85683d013a5746382c7c0bc2 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -282,12 +282,6 @@ all::
 # Define NO_ST_BLOCKS_IN_STRUCT_STAT if your platform does not have st_blocks
 # field that counts the on-disk footprint in 512-byte blocks.
 #
-# Define DOCBOOK_XSL_172 if you want to format man pages with DocBook XSL v1.72
-# (not v1.73 or v1.71).
-#
-# Define ASCIIDOC_ROFF if your DocBook XSL does not escape raw roff directives
-# (versions 1.68.1 through v1.72).
-#
 # Define GNU_ROFF if your target system uses GNU groff.  This forces
 # apostrophes to be ASCII so that cut&pasting examples to the shell
 # will work.
@@ -609,7 +603,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 +688,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
@@ -738,7 +732,7 @@ TEST_BUILTINS_OBJS += test-run-command.o
 TEST_BUILTINS_OBJS += test-scrap-cache-tree.o
 TEST_BUILTINS_OBJS += test-serve-v2.o
 TEST_BUILTINS_OBJS += test-sha1.o
-TEST_BUILTINS_OBJS += test-sha1-array.o
+TEST_BUILTINS_OBJS += test-oid-array.o
 TEST_BUILTINS_OBJS += test-sha256.o
 TEST_BUILTINS_OBJS += test-sigchain.o
 TEST_BUILTINS_OBJS += test-strcmp-offset.o
@@ -886,6 +880,7 @@ LIB_OBJS += ewah/ewah_rlw.o
 LIB_OBJS += exec-cmd.o
 LIB_OBJS += fetch-negotiator.o
 LIB_OBJS += fetch-pack.o
+LIB_OBJS += fmt-merge-msg.o
 LIB_OBJS += fsck.o
 LIB_OBJS += fsmonitor.o
 LIB_OBJS += gettext.o
@@ -929,6 +924,7 @@ LIB_OBJS += notes-utils.o
 LIB_OBJS += object.o
 LIB_OBJS += oidmap.o
 LIB_OBJS += oidset.o
+LIB_OBJS += oid-array.o
 LIB_OBJS += packfile.o
 LIB_OBJS += pack-bitmap.o
 LIB_OBJS += pack-bitmap-write.o
@@ -951,6 +947,7 @@ LIB_OBJS += progress.o
 LIB_OBJS += promisor-remote.o
 LIB_OBJS += prompt.o
 LIB_OBJS += protocol.o
+LIB_OBJS += prune-packed.o
 LIB_OBJS += quote.o
 LIB_OBJS += range-diff.o
 LIB_OBJS += reachable.o
@@ -978,7 +975,6 @@ LIB_OBJS += sequencer.o
 LIB_OBJS += serve.o
 LIB_OBJS += server-info.o
 LIB_OBJS += setup.o
-LIB_OBJS += sha1-array.o
 LIB_OBJS += sha1-lookup.o
 LIB_OBJS += sha1-file.o
 LIB_OBJS += sha1-name.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 4a9bf85cac033b0cf22665918fccee561678eb3d..29cd2fe02014b32c21b69059e5bbf93018a77223 100644 (file)
@@ -9,6 +9,7 @@
 #include "lockfile.h"
 #include "dir.h"
 #include "run-command.h"
+#include "prompt.h"
 
 static void init_color(struct repository *r, struct add_i_state *s,
                       const char *slot_name, char *dst,
@@ -289,13 +290,12 @@ static ssize_t list_and_choose(struct add_i_state *s,
                fputs(singleton ? "> " : ">> ", stdout);
                fflush(stdout);
 
-               if (strbuf_getline(&input, stdin) == EOF) {
+               if (git_read_line_interactively(&input) == EOF) {
                        putchar('\n');
                        if (immediate)
                                res = LIST_AND_CHOOSE_QUIT;
                        break;
                }
-               strbuf_trim(&input);
 
                if (!input.len)
                        break;
index d8dafa8168dc8389468d8c2c3cd6221220826605..d8bfe379be4d50893b8b977efff9c79de54b38af 100644 (file)
@@ -7,6 +7,7 @@
 #include "color.h"
 #include "diff.h"
 #include "compat/terminal.h"
+#include "prompt.h"
 
 enum prompt_mode_type {
        PROMPT_MODE_CHANGE = 0, PROMPT_DELETION, PROMPT_HUNK,
@@ -1158,9 +1159,8 @@ static int read_single_character(struct add_p_state *s)
                return res;
        }
 
-       if (strbuf_getline(&s->answer, stdin) == EOF)
+       if (git_read_line_interactively(&s->answer) == EOF)
                return EOF;
-       strbuf_trim_trailing_newline(&s->answer);
        return 0;
 }
 
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 5a77701a15c2068ce91dcffb835335203a5860aa..5ceec3684be910c44216d38e11f240fa4cc22ed3 100644 (file)
@@ -364,7 +364,7 @@ static struct archiver **tar_filters;
 static int nr_tar_filters;
 static int alloc_tar_filters;
 
-static struct archiver *find_tar_filter(const char *name, int len)
+static struct archiver *find_tar_filter(const char *name, size_t len)
 {
        int i;
        for (i = 0; i < nr_tar_filters; i++) {
@@ -380,7 +380,7 @@ static int tar_filter_config(const char *var, const char *value, void *data)
        struct archiver *ar;
        const char *name;
        const char *type;
-       int namelen;
+       size_t namelen;
 
        if (parse_config_key(var, "tar", &name, &namelen, &type) < 0 || !name)
                return 0;
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 9154f810f76af3cdbf82b8bb509f47aa392b0e01..d5e830410f5949d1e8c24e990cf0a70b980656ea 100644 (file)
--- a/bisect.c
+++ b/bisect.c
@@ -10,7 +10,7 @@
 #include "run-command.h"
 #include "log-tree.h"
 #include "bisect.h"
-#include "sha1-array.h"
+#include "oid-array.h"
 #include "argv-array.h"
 #include "commit-slab.h"
 #include "commit-reach.h"
@@ -473,7 +473,7 @@ static void read_bisect_paths(struct argv_array *array)
        fclose(fp);
 }
 
-static char *join_sha1_array_hex(struct oid_array *array, char delim)
+static char *join_oid_array_hex(struct oid_array *array, char delim)
 {
        struct strbuf joined_hexs = STRBUF_INIT;
        int i;
@@ -765,7 +765,7 @@ static enum bisect_error handle_bad_merge_base(void)
 {
        if (is_expected_rev(current_bad_oid)) {
                char *bad_hex = oid_to_hex(current_bad_oid);
-               char *good_hex = join_sha1_array_hex(&good_revs, ' ');
+               char *good_hex = join_oid_array_hex(&good_revs, ' ');
                if (!strcmp(term_bad, "bad") && !strcmp(term_good, "good")) {
                        fprintf(stderr, _("The merge base %s is bad.\n"
                                "This means the bug has been fixed "
@@ -796,7 +796,7 @@ static void handle_skipped_merge_base(const struct object_id *mb)
 {
        char *mb_hex = oid_to_hex(mb);
        char *bad_hex = oid_to_hex(current_bad_oid);
-       char *good_hex = join_sha1_array_hex(&good_revs, ' ');
+       char *good_hex = join_oid_array_hex(&good_revs, ' ');
 
        warning(_("the merge base between %s and [%s] "
                "must be skipped.\n"
index 2b25a80cde37b401cdb8e7b5d2487c8989b95dd7..a5ae15bfe54b652465b0164872923c8386d03ae9 100644 (file)
--- a/builtin.h
+++ b/builtin.h
  * command.
  */
 
-#define DEFAULT_MERGE_LOG_LEN 20
-
 extern const char git_usage_string[];
 extern const char git_more_info_string[];
 
-#define PRUNE_PACKED_DRY_RUN 01
-#define PRUNE_PACKED_VERBOSE 02
-
-void prune_packed_objects(int);
-
-struct fmt_merge_msg_opts {
-       unsigned add_title:1,
-               credit_people:1;
-       int shortlog_len;
-};
-
-int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
-                 struct fmt_merge_msg_opts *);
-
 /**
  * If a built-in has DELAY_PAGER_CONFIG set, the built-in should call this early
  * when it wishes to respect the `pager.foo`-config. The `cmd` is the name of
index 272f9fc6d7cb540c89c3dca13969398afd3c1ee1..0d03fdac6e1c5e848d5b1981f3b7d4c8ea6221c1 100644 (file)
@@ -12,7 +12,7 @@
 #include "userdiff.h"
 #include "streaming.h"
 #include "tree-walk.h"
-#include "sha1-array.h"
+#include "oid-array.h"
 #include "packfile.h"
 #include "object-store.h"
 #include "promisor-remote.h"
@@ -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 5abf087e7c495153b36b927b5b615f80a68d414c..c8c011d2ddfa3e2653535c6d4b36675a20a90031 100644 (file)
@@ -18,6 +18,7 @@
 #include "color.h"
 #include "pathspec.h"
 #include "help.h"
+#include "prompt.h"
 
 static int force = -1; /* unset */
 static int interactive;
@@ -420,7 +421,6 @@ static int find_unique(const char *choice, struct menu_stuff *menu_stuff)
        return found;
 }
 
-
 /*
  * Parse user input, and return choice(s) for menu (menu_stuff).
  *
@@ -580,9 +580,7 @@ static int *list_and_choose(struct menu_opts *opts, struct menu_stuff *stuff)
                               clean_get_color(CLEAN_COLOR_RESET));
                }
 
-               if (strbuf_getline_lf(&choice, stdin) != EOF) {
-                       strbuf_trim(&choice);
-               } else {
+               if (git_read_line_interactively(&choice) == EOF) {
                        eof = 1;
                        break;
                }
@@ -662,9 +660,7 @@ static int filter_by_patterns_cmd(void)
                clean_print_color(CLEAN_COLOR_PROMPT);
                printf(_("Input ignore patterns>> "));
                clean_print_color(CLEAN_COLOR_RESET);
-               if (strbuf_getline_lf(&confirm, stdin) != EOF)
-                       strbuf_trim(&confirm);
-               else
+               if (git_read_line_interactively(&confirm) == EOF)
                        putchar('\n');
 
                /* quit filter_by_pattern mode if press ENTER or Ctrl-D */
@@ -760,9 +756,7 @@ static int ask_each_cmd(void)
                        qname = quote_path_relative(item->string, NULL, &buf);
                        /* TRANSLATORS: Make sure to keep [y/N] as is */
                        printf(_("Remove %s [y/N]? "), qname);
-                       if (strbuf_getline_lf(&confirm, stdin) != EOF) {
-                               strbuf_trim(&confirm);
-                       } else {
+                       if (git_read_line_interactively(&confirm) == EOF) {
                                putchar('\n');
                                eof = 1;
                        }
index 1ad26f4d8c81b3e26bca8c3701162ab0e8120e87..cb48a291caf9a364f21208e762f6b6e07a6ca7f6 100644 (file)
@@ -102,10 +102,10 @@ static struct option builtin_clone_options[] = {
                    N_("don't use local hardlinks, always copy")),
        OPT_BOOL('s', "shared", &option_shared,
                    N_("setup as shared repository")),
-       OPT_ALIAS(0, "recursive", "recurse-submodules"),
        { OPTION_CALLBACK, 0, "recurse-submodules", &option_recurse_submodules,
          N_("pathspec"), N_("initialize submodules in the clone"),
          PARSE_OPT_OPTARG, recurse_submodules_cb, (intptr_t)"." },
+       OPT_ALIAS(0, "recursive", "recurse-submodules"),
        OPT_INTEGER('j', "jobs", &max_jobs,
                    N_("number of submodules cloned in parallel")),
        OPT_STRING(0, "template", &option_template, N_("template-directory"),
@@ -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)
@@ -639,7 +643,9 @@ static void write_followtags(const struct ref *refs, const char *msg)
                        continue;
                if (ends_with(ref->name, "^{}"))
                        continue;
-               if (!has_object_file(&ref->old_oid))
+               if (!has_object_file_with_flags(&ref->old_oid,
+                                               OBJECT_INFO_QUICK |
+                                               OBJECT_INFO_SKIP_FETCH_OBJECT))
                        continue;
                update_ref(msg, ref->name, &ref->old_oid, NULL, 0,
                           UPDATE_REFS_DIE_ON_ERR);
@@ -672,8 +678,7 @@ static void update_remote_refs(const struct ref *refs,
                               const char *branch_top,
                               const char *msg,
                               struct transport *transport,
-                              int check_connectivity,
-                              int check_refs_are_promisor_objects_only)
+                              int check_connectivity)
 {
        const struct ref *rm = mapped_refs;
 
@@ -682,8 +687,6 @@ static void update_remote_refs(const struct ref *refs,
 
                opt.transport = transport;
                opt.progress = transport->progress;
-               opt.check_refs_are_promisor_objects_only =
-                       !!check_refs_are_promisor_objects_only;
 
                if (check_connected(iterate_ref_map, &rm, &opt))
                        die(_("remote did not send all necessary objects"));
@@ -780,11 +783,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 +802,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 +810,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 +1108,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;
@@ -1275,7 +1281,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 
        update_remote_refs(refs, mapped_refs, remote_head_points_at,
                           branch_top.buf, reflog_msg.buf, transport,
-                          !is_local, filter_options.choice);
+                          !is_local);
 
        update_head(our_head_points_at, remote_head, reflog_msg.buf);
 
index 4a70b33fb5f1359397742fcf8601c4c1d49109fe..03a8331e2b6fe75172e7953507b94a10aef9be1b 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);
@@ -140,7 +143,7 @@ static int graph_write(int argc, const char **argv)
                OPT_INTEGER(0, "size-multiple", &split_opts.size_multiple,
                        N_("maximum ratio between two levels of a split commit-graph")),
                OPT_EXPIRY_DATE(0, "expire-time", &split_opts.expire_time,
-                       N_("maximum number of commits in a non-base split commit-graph")),
+                       N_("only expire files older than a given date-time")),
                OPT_END(),
        };
 
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 42ac803091e6a0c7e22203b097d1f0001c9f5302..8537b17bd5e42aeb52a676f39a2d9a5d48e65aef 100644 (file)
@@ -17,7 +17,7 @@
 #include "log-tree.h"
 #include "builtin.h"
 #include "submodule.h"
-#include "sha1-array.h"
+#include "oid-array.h"
 
 #define DIFF_NO_INDEX_EXPLICIT 1
 #define DIFF_NO_INDEX_IMPLICIT 2
index dc1485c8aa1bf2b623aaa18ccd710644a34e0153..47711000725b4fb7dc504c62cc2d4ac384f7d46b 100644 (file)
@@ -3,7 +3,7 @@
 #include "fetch-pack.h"
 #include "remote.h"
 #include "connect.h"
-#include "sha1-array.h"
+#include "oid-array.h"
 #include "protocol.h"
 
 static const char fetch_pack_usage[] =
index bf6bab80fab915242f412de7f0ced4a92d95f930..1097e1e512bb3cd0798d07b1ff08df3af25adac7 100644 (file)
@@ -908,13 +908,6 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
        if (!connectivity_checked) {
                struct check_connected_options opt = CHECK_CONNECTED_INIT;
 
-               if (filter_options.choice)
-                       /*
-                        * Since a filter is specified, objects indirectly
-                        * referenced by refs are allowed to be absent.
-                        */
-                       opt.check_refs_are_promisor_objects_only = 1;
-
                rm = ref_map;
                if (check_connected(iterate_ref_map, &rm, &opt)) {
                        rc = error(_("%s did not send all necessary objects\n"), url);
index 736f666f644c7cf78b816888ba226693464fc331..48a8699de728a950053b182fae23e73b14fba6a6 100644 (file)
 #include "builtin.h"
-#include "cache.h"
 #include "config.h"
-#include "refs.h"
-#include "object-store.h"
-#include "commit.h"
-#include "diff.h"
-#include "revision.h"
-#include "tag.h"
-#include "string-list.h"
-#include "branch.h"
 #include "fmt-merge-msg.h"
-#include "gpg-interface.h"
-#include "repository.h"
-#include "commit-reach.h"
+#include "parse-options.h"
 
 static const char * const fmt_merge_msg_usage[] = {
        N_("git fmt-merge-msg [-m <message>] [--log[=<n>] | --no-log] [--file <file>]"),
        NULL
 };
 
-static int use_branch_desc;
-
-int fmt_merge_msg_config(const char *key, const char *value, void *cb)
-{
-       if (!strcmp(key, "merge.log") || !strcmp(key, "merge.summary")) {
-               int is_bool;
-               merge_log_config = git_config_bool_or_int(key, value, &is_bool);
-               if (!is_bool && merge_log_config < 0)
-                       return error("%s: negative length %s", key, value);
-               if (is_bool && merge_log_config)
-                       merge_log_config = DEFAULT_MERGE_LOG_LEN;
-       } else if (!strcmp(key, "merge.branchdesc")) {
-               use_branch_desc = git_config_bool(key, value);
-       } else {
-               return git_default_config(key, value, cb);
-       }
-       return 0;
-}
-
-/* merge data per repository where the merged tips came from */
-struct src_data {
-       struct string_list branch, tag, r_branch, generic;
-       int head_status;
-};
-
-struct origin_data {
-       struct object_id oid;
-       unsigned is_local_branch:1;
-};
-
-static void init_src_data(struct src_data *data)
-{
-       data->branch.strdup_strings = 1;
-       data->tag.strdup_strings = 1;
-       data->r_branch.strdup_strings = 1;
-       data->generic.strdup_strings = 1;
-}
-
-static struct string_list srcs = STRING_LIST_INIT_DUP;
-static struct string_list origins = STRING_LIST_INIT_DUP;
-
-struct merge_parents {
-       int alloc, nr;
-       struct merge_parent {
-               struct object_id given;
-               struct object_id commit;
-               unsigned char used;
-       } *item;
-};
-
-/*
- * I know, I know, this is inefficient, but you won't be pulling and merging
- * hundreds of heads at a time anyway.
- */
-static struct merge_parent *find_merge_parent(struct merge_parents *table,
-                                             struct object_id *given,
-                                             struct object_id *commit)
-{
-       int i;
-       for (i = 0; i < table->nr; i++) {
-               if (given && !oideq(&table->item[i].given, given))
-                       continue;
-               if (commit && !oideq(&table->item[i].commit, commit))
-                       continue;
-               return &table->item[i];
-       }
-       return NULL;
-}
-
-static void add_merge_parent(struct merge_parents *table,
-                            struct object_id *given,
-                            struct object_id *commit)
-{
-       if (table->nr && find_merge_parent(table, given, commit))
-               return;
-       ALLOC_GROW(table->item, table->nr + 1, table->alloc);
-       oidcpy(&table->item[table->nr].given, given);
-       oidcpy(&table->item[table->nr].commit, commit);
-       table->item[table->nr].used = 0;
-       table->nr++;
-}
-
-static int handle_line(char *line, struct merge_parents *merge_parents)
-{
-       int i, len = strlen(line);
-       struct origin_data *origin_data;
-       char *src;
-       const char *origin, *tag_name;
-       struct src_data *src_data;
-       struct string_list_item *item;
-       int pulling_head = 0;
-       struct object_id oid;
-       const unsigned hexsz = the_hash_algo->hexsz;
-
-       if (len < hexsz + 3 || line[hexsz] != '\t')
-               return 1;
-
-       if (starts_with(line + hexsz + 1, "not-for-merge"))
-               return 0;
-
-       if (line[hexsz + 1] != '\t')
-               return 2;
-
-       i = get_oid_hex(line, &oid);
-       if (i)
-               return 3;
-
-       if (!find_merge_parent(merge_parents, &oid, NULL))
-               return 0; /* subsumed by other parents */
-
-       origin_data = xcalloc(1, sizeof(struct origin_data));
-       oidcpy(&origin_data->oid, &oid);
-
-       if (line[len - 1] == '\n')
-               line[len - 1] = 0;
-       line += hexsz + 2;
-
-       /*
-        * At this point, line points at the beginning of comment e.g.
-        * "branch 'frotz' of git://that/repository.git".
-        * Find the repository name and point it with src.
-        */
-       src = strstr(line, " of ");
-       if (src) {
-               *src = 0;
-               src += 4;
-               pulling_head = 0;
-       } else {
-               src = line;
-               pulling_head = 1;
-       }
-
-       item = unsorted_string_list_lookup(&srcs, src);
-       if (!item) {
-               item = string_list_append(&srcs, src);
-               item->util = xcalloc(1, sizeof(struct src_data));
-               init_src_data(item->util);
-       }
-       src_data = item->util;
-
-       if (pulling_head) {
-               origin = src;
-               src_data->head_status |= 1;
-       } else if (skip_prefix(line, "branch ", &origin)) {
-               origin_data->is_local_branch = 1;
-               string_list_append(&src_data->branch, origin);
-               src_data->head_status |= 2;
-       } else if (skip_prefix(line, "tag ", &tag_name)) {
-               origin = line;
-               string_list_append(&src_data->tag, tag_name);
-               src_data->head_status |= 2;
-       } else if (skip_prefix(line, "remote-tracking branch ", &origin)) {
-               string_list_append(&src_data->r_branch, origin);
-               src_data->head_status |= 2;
-       } else {
-               origin = src;
-               string_list_append(&src_data->generic, line);
-               src_data->head_status |= 2;
-       }
-
-       if (!strcmp(".", src) || !strcmp(src, origin)) {
-               int len = strlen(origin);
-               if (origin[0] == '\'' && origin[len - 1] == '\'')
-                       origin = xmemdupz(origin + 1, len - 2);
-       } else
-               origin = xstrfmt("%s of %s", origin, src);
-       if (strcmp(".", src))
-               origin_data->is_local_branch = 0;
-       string_list_append(&origins, origin)->util = origin_data;
-       return 0;
-}
-
-static void print_joined(const char *singular, const char *plural,
-               struct string_list *list, struct strbuf *out)
-{
-       if (list->nr == 0)
-               return;
-       if (list->nr == 1) {
-               strbuf_addf(out, "%s%s", singular, list->items[0].string);
-       } else {
-               int i;
-               strbuf_addstr(out, plural);
-               for (i = 0; i < list->nr - 1; i++)
-                       strbuf_addf(out, "%s%s", i > 0 ? ", " : "",
-                                   list->items[i].string);
-               strbuf_addf(out, " and %s", list->items[list->nr - 1].string);
-       }
-}
-
-static void add_branch_desc(struct strbuf *out, const char *name)
-{
-       struct strbuf desc = STRBUF_INIT;
-
-       if (!read_branch_desc(&desc, name)) {
-               const char *bp = desc.buf;
-               while (*bp) {
-                       const char *ep = strchrnul(bp, '\n');
-                       if (*ep)
-                               ep++;
-                       strbuf_addf(out, "  : %.*s", (int)(ep - bp), bp);
-                       bp = ep;
-               }
-               strbuf_complete_line(out);
-       }
-       strbuf_release(&desc);
-}
-
-#define util_as_integral(elem) ((intptr_t)((elem)->util))
-
-static void record_person_from_buf(int which, struct string_list *people,
-                                  const char *buffer)
-{
-       char *name_buf, *name, *name_end;
-       struct string_list_item *elem;
-       const char *field;
-
-       field = (which == 'a') ? "\nauthor " : "\ncommitter ";
-       name = strstr(buffer, field);
-       if (!name)
-               return;
-       name += strlen(field);
-       name_end = strchrnul(name, '<');
-       if (*name_end)
-               name_end--;
-       while (isspace(*name_end) && name <= name_end)
-               name_end--;
-       if (name_end < name)
-               return;
-       name_buf = xmemdupz(name, name_end - name + 1);
-
-       elem = string_list_lookup(people, name_buf);
-       if (!elem) {
-               elem = string_list_insert(people, name_buf);
-               elem->util = (void *)0;
-       }
-       elem->util = (void*)(util_as_integral(elem) + 1);
-       free(name_buf);
-}
-
-
-static void record_person(int which, struct string_list *people,
-                         struct commit *commit)
-{
-       const char *buffer = get_commit_buffer(commit, NULL);
-       record_person_from_buf(which, people, buffer);
-       unuse_commit_buffer(commit, buffer);
-}
-
-static int cmp_string_list_util_as_integral(const void *a_, const void *b_)
-{
-       const struct string_list_item *a = a_, *b = b_;
-       return util_as_integral(b) - util_as_integral(a);
-}
-
-static void add_people_count(struct strbuf *out, struct string_list *people)
-{
-       if (people->nr == 1)
-               strbuf_addstr(out, people->items[0].string);
-       else if (people->nr == 2)
-               strbuf_addf(out, "%s (%d) and %s (%d)",
-                           people->items[0].string,
-                           (int)util_as_integral(&people->items[0]),
-                           people->items[1].string,
-                           (int)util_as_integral(&people->items[1]));
-       else if (people->nr)
-               strbuf_addf(out, "%s (%d) and others",
-                           people->items[0].string,
-                           (int)util_as_integral(&people->items[0]));
-}
-
-static void credit_people(struct strbuf *out,
-                         struct string_list *them,
-                         int kind)
-{
-       const char *label;
-       const char *me;
-
-       if (kind == 'a') {
-               label = "By";
-               me = git_author_info(IDENT_NO_DATE);
-       } else {
-               label = "Via";
-               me = git_committer_info(IDENT_NO_DATE);
-       }
-
-       if (!them->nr ||
-           (them->nr == 1 &&
-            me &&
-            skip_prefix(me, them->items->string, &me) &&
-            starts_with(me, " <")))
-               return;
-       strbuf_addf(out, "\n%c %s ", comment_line_char, label);
-       add_people_count(out, them);
-}
-
-static void add_people_info(struct strbuf *out,
-                           struct string_list *authors,
-                           struct string_list *committers)
-{
-       QSORT(authors->items, authors->nr,
-             cmp_string_list_util_as_integral);
-       QSORT(committers->items, committers->nr,
-             cmp_string_list_util_as_integral);
-
-       credit_people(out, authors, 'a');
-       credit_people(out, committers, 'c');
-}
-
-static void shortlog(const char *name,
-                    struct origin_data *origin_data,
-                    struct commit *head,
-                    struct rev_info *rev,
-                    struct fmt_merge_msg_opts *opts,
-                    struct strbuf *out)
-{
-       int i, count = 0;
-       struct commit *commit;
-       struct object *branch;
-       struct string_list subjects = STRING_LIST_INIT_DUP;
-       struct string_list authors = STRING_LIST_INIT_DUP;
-       struct string_list committers = STRING_LIST_INIT_DUP;
-       int flags = UNINTERESTING | TREESAME | SEEN | SHOWN | ADDED;
-       struct strbuf sb = STRBUF_INIT;
-       const struct object_id *oid = &origin_data->oid;
-       int limit = opts->shortlog_len;
-
-       branch = deref_tag(the_repository, parse_object(the_repository, oid),
-                          oid_to_hex(oid),
-                          the_hash_algo->hexsz);
-       if (!branch || branch->type != OBJ_COMMIT)
-               return;
-
-       setup_revisions(0, NULL, rev, NULL);
-       add_pending_object(rev, branch, name);
-       add_pending_object(rev, &head->object, "^HEAD");
-       head->object.flags |= UNINTERESTING;
-       if (prepare_revision_walk(rev))
-               die("revision walk setup failed");
-       while ((commit = get_revision(rev)) != NULL) {
-               struct pretty_print_context ctx = {0};
-
-               if (commit->parents && commit->parents->next) {
-                       /* do not list a merge but count committer */
-                       if (opts->credit_people)
-                               record_person('c', &committers, commit);
-                       continue;
-               }
-               if (!count && opts->credit_people)
-                       /* the 'tip' committer */
-                       record_person('c', &committers, commit);
-               if (opts->credit_people)
-                       record_person('a', &authors, commit);
-               count++;
-               if (subjects.nr > limit)
-                       continue;
-
-               format_commit_message(commit, "%s", &sb, &ctx);
-               strbuf_ltrim(&sb);
-
-               if (!sb.len)
-                       string_list_append(&subjects,
-                                          oid_to_hex(&commit->object.oid));
-               else
-                       string_list_append_nodup(&subjects,
-                                                strbuf_detach(&sb, NULL));
-       }
-
-       if (opts->credit_people)
-               add_people_info(out, &authors, &committers);
-       if (count > limit)
-               strbuf_addf(out, "\n* %s: (%d commits)\n", name, count);
-       else
-               strbuf_addf(out, "\n* %s:\n", name);
-
-       if (origin_data->is_local_branch && use_branch_desc)
-               add_branch_desc(out, name);
-
-       for (i = 0; i < subjects.nr; i++)
-               if (i >= limit)
-                       strbuf_addstr(out, "  ...\n");
-               else
-                       strbuf_addf(out, "  %s\n", subjects.items[i].string);
-
-       clear_commit_marks((struct commit *)branch, flags);
-       clear_commit_marks(head, flags);
-       free_commit_list(rev->commits);
-       rev->commits = NULL;
-       rev->pending.nr = 0;
-
-       string_list_clear(&authors, 0);
-       string_list_clear(&committers, 0);
-       string_list_clear(&subjects, 0);
-}
-
-static void fmt_merge_msg_title(struct strbuf *out,
-                               const char *current_branch)
-{
-       int i = 0;
-       char *sep = "";
-
-       strbuf_addstr(out, "Merge ");
-       for (i = 0; i < srcs.nr; i++) {
-               struct src_data *src_data = srcs.items[i].util;
-               const char *subsep = "";
-
-               strbuf_addstr(out, sep);
-               sep = "; ";
-
-               if (src_data->head_status == 1) {
-                       strbuf_addstr(out, srcs.items[i].string);
-                       continue;
-               }
-               if (src_data->head_status == 3) {
-                       subsep = ", ";
-                       strbuf_addstr(out, "HEAD");
-               }
-               if (src_data->branch.nr) {
-                       strbuf_addstr(out, subsep);
-                       subsep = ", ";
-                       print_joined("branch ", "branches ", &src_data->branch,
-                                       out);
-               }
-               if (src_data->r_branch.nr) {
-                       strbuf_addstr(out, subsep);
-                       subsep = ", ";
-                       print_joined("remote-tracking branch ", "remote-tracking branches ",
-                                       &src_data->r_branch, out);
-               }
-               if (src_data->tag.nr) {
-                       strbuf_addstr(out, subsep);
-                       subsep = ", ";
-                       print_joined("tag ", "tags ", &src_data->tag, out);
-               }
-               if (src_data->generic.nr) {
-                       strbuf_addstr(out, subsep);
-                       print_joined("commit ", "commits ", &src_data->generic,
-                                       out);
-               }
-               if (strcmp(".", srcs.items[i].string))
-                       strbuf_addf(out, " of %s", srcs.items[i].string);
-       }
-
-       if (!strcmp("master", current_branch))
-               strbuf_addch(out, '\n');
-       else
-               strbuf_addf(out, " into %s\n", current_branch);
-}
-
-static void fmt_tag_signature(struct strbuf *tagbuf,
-                             struct strbuf *sig,
-                             const char *buf,
-                             unsigned long len)
-{
-       const char *tag_body = strstr(buf, "\n\n");
-       if (tag_body) {
-               tag_body += 2;
-               strbuf_add(tagbuf, tag_body, buf + len - tag_body);
-       }
-       strbuf_complete_line(tagbuf);
-       if (sig->len) {
-               strbuf_addch(tagbuf, '\n');
-               strbuf_add_commented_lines(tagbuf, sig->buf, sig->len);
-       }
-}
-
-static void fmt_merge_msg_sigs(struct strbuf *out)
-{
-       int i, tag_number = 0, first_tag = 0;
-       struct strbuf tagbuf = STRBUF_INIT;
-
-       for (i = 0; i < origins.nr; i++) {
-               struct object_id *oid = origins.items[i].util;
-               enum object_type type;
-               unsigned long size, len;
-               char *buf = read_object_file(oid, &type, &size);
-               struct strbuf sig = STRBUF_INIT;
-
-               if (!buf || type != OBJ_TAG)
-                       goto next;
-               len = parse_signature(buf, size);
-
-               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");
-               }
-
-               if (!tag_number++) {
-                       fmt_tag_signature(&tagbuf, &sig, buf, len);
-                       first_tag = i;
-               } else {
-                       if (tag_number == 2) {
-                               struct strbuf tagline = STRBUF_INIT;
-                               strbuf_addch(&tagline, '\n');
-                               strbuf_add_commented_lines(&tagline,
-                                               origins.items[first_tag].string,
-                                               strlen(origins.items[first_tag].string));
-                               strbuf_insert(&tagbuf, 0, tagline.buf,
-                                             tagline.len);
-                               strbuf_release(&tagline);
-                       }
-                       strbuf_addch(&tagbuf, '\n');
-                       strbuf_add_commented_lines(&tagbuf,
-                                       origins.items[i].string,
-                                       strlen(origins.items[i].string));
-                       fmt_tag_signature(&tagbuf, &sig, buf, len);
-               }
-               strbuf_release(&sig);
-       next:
-               free(buf);
-       }
-       if (tagbuf.len) {
-               strbuf_addch(out, '\n');
-               strbuf_addbuf(out, &tagbuf);
-       }
-       strbuf_release(&tagbuf);
-}
-
-static void find_merge_parents(struct merge_parents *result,
-                              struct strbuf *in, struct object_id *head)
-{
-       struct commit_list *parents;
-       struct commit *head_commit;
-       int pos = 0, i, j;
-
-       parents = NULL;
-       while (pos < in->len) {
-               int len;
-               char *p = in->buf + pos;
-               char *newline = strchr(p, '\n');
-               const char *q;
-               struct object_id oid;
-               struct commit *parent;
-               struct object *obj;
-
-               len = newline ? newline - p : strlen(p);
-               pos += len + !!newline;
-
-               if (parse_oid_hex(p, &oid, &q) ||
-                   q[0] != '\t' ||
-                   q[1] != '\t')
-                       continue; /* skip not-for-merge */
-               /*
-                * Do not use get_merge_parent() here; we do not have
-                * "name" here and we do not want to contaminate its
-                * util field yet.
-                */
-               obj = parse_object(the_repository, &oid);
-               parent = (struct commit *)peel_to_type(NULL, 0, obj, OBJ_COMMIT);
-               if (!parent)
-                       continue;
-               commit_list_insert(parent, &parents);
-               add_merge_parent(result, &obj->oid, &parent->object.oid);
-       }
-       head_commit = lookup_commit(the_repository, head);
-       if (head_commit)
-               commit_list_insert(head_commit, &parents);
-       reduce_heads_replace(&parents);
-
-       while (parents) {
-               struct commit *cmit = pop_commit(&parents);
-               for (i = 0; i < result->nr; i++)
-                       if (oideq(&result->item[i].commit, &cmit->object.oid))
-                               result->item[i].used = 1;
-       }
-
-       for (i = j = 0; i < result->nr; i++) {
-               if (result->item[i].used) {
-                       if (i != j)
-                               result->item[j] = result->item[i];
-                       j++;
-               }
-       }
-       result->nr = j;
-}
-
-int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
-                 struct fmt_merge_msg_opts *opts)
-{
-       int i = 0, pos = 0;
-       struct object_id head_oid;
-       const char *current_branch;
-       void *current_branch_to_free;
-       struct merge_parents merge_parents;
-
-       memset(&merge_parents, 0, sizeof(merge_parents));
-
-       /* get current branch */
-       current_branch = current_branch_to_free =
-               resolve_refdup("HEAD", RESOLVE_REF_READING, &head_oid, NULL);
-       if (!current_branch)
-               die("No current branch");
-       if (starts_with(current_branch, "refs/heads/"))
-               current_branch += 11;
-
-       find_merge_parents(&merge_parents, in, &head_oid);
-
-       /* get a line */
-       while (pos < in->len) {
-               int len;
-               char *newline, *p = in->buf + pos;
-
-               newline = strchr(p, '\n');
-               len = newline ? newline - p : strlen(p);
-               pos += len + !!newline;
-               i++;
-               p[len] = 0;
-               if (handle_line(p, &merge_parents))
-                       die("error in line %d: %.*s", i, len, p);
-       }
-
-       if (opts->add_title && srcs.nr)
-               fmt_merge_msg_title(out, current_branch);
-
-       if (origins.nr)
-               fmt_merge_msg_sigs(out);
-
-       if (opts->shortlog_len) {
-               struct commit *head;
-               struct rev_info rev;
-
-               head = lookup_commit_or_die(&head_oid, "HEAD");
-               repo_init_revisions(the_repository, &rev, NULL);
-               rev.commit_format = CMIT_FMT_ONELINE;
-               rev.ignore_merges = 1;
-               rev.limited = 1;
-
-               strbuf_complete_line(out);
-
-               for (i = 0; i < origins.nr; i++)
-                       shortlog(origins.items[i].string,
-                                origins.items[i].util,
-                                head, &rev, opts, out);
-       }
-
-       strbuf_complete_line(out);
-       free(current_branch_to_free);
-       free(merge_parents.item);
-       return 0;
-}
-
 int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
 {
        const char *inpath = NULL;
index e5590d7787c50af121db0ba7e6221e89c6785df1..c024110531f6c4502bfeed0a91d971b2be47c90b 100644 (file)
@@ -242,7 +242,7 @@ static int add_man_viewer_cmd(const char *name,
 static int add_man_viewer_info(const char *var, const char *value)
 {
        const char *name, *subkey;
-       int namelen;
+       size_t namelen;
 
        if (parse_config_key(var, "man", &name, &namelen, &subkey) < 0 || !name)
                return 0;
index d967d188a307fe8763b5fdff08b570999e2a853d..f176dd28c870d5e417b373f2bb0d39b4fa1a0c29 100644 (file)
@@ -1368,9 +1368,8 @@ static void fix_unresolved_deltas(struct hashfile *f)
                                continue;
                        oid_array_append(&to_fetch, &d->oid);
                }
-               if (to_fetch.nr)
-                       promisor_remote_get_direct(the_repository,
-                                                  to_fetch.oid, to_fetch.nr);
+               promisor_remote_get_direct(the_repository,
+                                          to_fetch.oid, to_fetch.nr);
                oid_array_clear(&to_fetch);
        }
 
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 83a4a6188e221caefc5028e59cb7c95c2f1d1e0c..390b6ca2ce56c9bc7b098a2bd032b9cf606766e6 100644 (file)
@@ -46,6 +46,7 @@ static int default_abbrev_commit;
 static int default_show_root = 1;
 static int default_follow;
 static int default_show_signature;
+static int default_encode_email_headers = 1;
 static int decoration_style;
 static int decoration_given;
 static int use_mailmap_config = 1;
@@ -151,6 +152,7 @@ static void cmd_log_init_defaults(struct rev_info *rev)
        rev->show_root_diff = default_show_root;
        rev->subject_prefix = fmt_patch_subject_prefix;
        rev->show_signature = default_show_signature;
+       rev->encode_email_headers = default_encode_email_headers;
        rev->diffopt.flags.allow_textconv = 1;
 
        if (default_date_mode)
@@ -173,6 +175,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
                OPT__QUIET(&quiet, N_("suppress diff output")),
                OPT_BOOL(0, "source", &source, N_("show source")),
                OPT_BOOL(0, "use-mailmap", &mailmap, N_("Use mail map file")),
+               OPT_ALIAS(0, "mailmap", "use-mailmap"),
                OPT_STRING_LIST(0, "decorate-refs", &decorate_refs_include,
                                N_("pattern"), N_("only decorate refs that match <pattern>")),
                OPT_STRING_LIST(0, "decorate-refs-exclude", &decorate_refs_exclude,
@@ -438,6 +441,10 @@ static int git_log_config(const char *var, const char *value, void *cb)
                return git_config_string(&fmt_pretty, var, value);
        if (!strcmp(var, "format.subjectprefix"))
                return git_config_string(&fmt_patch_subject_prefix, var, value);
+       if (!strcmp(var, "format.encodeemailheaders")) {
+               default_encode_email_headers = git_config_bool(var, value);
+               return 0;
+       }
        if (!strcmp(var, "log.abbrevcommit")) {
                default_abbrev_commit = git_config_bool(var, value);
                return 0;
@@ -1719,6 +1726,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
        rev.show_notes = show_notes;
        memcpy(&rev.notes_opt, &notes_opt, sizeof(notes_opt));
        rev.commit_format = CMIT_FMT_EMAIL;
+       rev.encode_email_headers = default_encode_email_headers;
        rev.expand_tabs_in_log_default = 0;
        rev.verbose_header = 1;
        rev.diff = 1;
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 d127d2225f897f111124fb55b12cd1aae7db7a8e..df83ba2a807a88fa5904a2acbe64a7caa7188224 100644 (file)
@@ -597,10 +597,12 @@ static void parse_branch_merge_options(char *bmo)
 static int git_merge_config(const char *k, const char *v, void *cb)
 {
        int status;
+       const char *str;
 
-       if (branch && starts_with(k, "branch.") &&
-               starts_with(k + 7, branch) &&
-               !strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
+       if (branch &&
+           skip_prefix(k, "branch.", &str) &&
+           skip_prefix(str, branch, &str) &&
+           !strcmp(str, ".mergeoptions")) {
                free(branch_mergeoptions);
                branch_mergeoptions = xstrdup(v);
                return 0;
index 02aa6ee4808a96f264a861bc49789341179056be..fdd18c7ccb76de5988554667f50f51c94e474857 100644 (file)
@@ -26,7 +26,7 @@
 #include "pack-bitmap.h"
 #include "delta-islands.h"
 #include "reachable.h"
-#include "sha1-array.h"
+#include "oid-array.h"
 #include "argv-array.h"
 #include "list.h"
 #include "packfile.h"
@@ -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 48c5e78e339dbd32db255382f729ca2e65b514c5..b7b9281a8cea2c009ef9a4487094613f032f6aba 100644 (file)
@@ -1,54 +1,12 @@
 #include "builtin.h"
-#include "cache.h"
-#include "progress.h"
 #include "parse-options.h"
-#include "packfile.h"
-#include "object-store.h"
+#include "prune-packed.h"
 
 static const char * const prune_packed_usage[] = {
        N_("git prune-packed [-n | --dry-run] [-q | --quiet]"),
        NULL
 };
 
-static struct progress *progress;
-
-static int prune_subdir(unsigned int nr, const char *path, void *data)
-{
-       int *opts = data;
-       display_progress(progress, nr + 1);
-       if (!(*opts & PRUNE_PACKED_DRY_RUN))
-               rmdir(path);
-       return 0;
-}
-
-static int prune_object(const struct object_id *oid, const char *path,
-                        void *data)
-{
-       int *opts = data;
-
-       if (!has_object_pack(oid))
-               return 0;
-
-       if (*opts & PRUNE_PACKED_DRY_RUN)
-               printf("rm -f %s\n", path);
-       else
-               unlink_or_warn(path);
-       return 0;
-}
-
-void prune_packed_objects(int opts)
-{
-       if (opts & PRUNE_PACKED_VERBOSE)
-               progress = start_delayed_progress(_("Removing duplicate objects"), 256);
-
-       for_each_loose_file_in_objdir(get_object_directory(),
-                                     prune_object, NULL, prune_subdir, &opts);
-
-       /* Ensure we show 100% before finishing progress */
-       display_progress(progress, 256);
-       stop_progress(&progress);
-}
-
 int cmd_prune_packed(int argc, const char **argv, const char *prefix)
 {
        int opts = isatty(2) ? PRUNE_PACKED_VERBOSE : 0;
index 2b76872ad2207857077f4ecf285780e94388d00d..fd9acc722247ece74483f6a2a7674a4c5f712570 100644 (file)
@@ -6,6 +6,7 @@
 #include "reachable.h"
 #include "parse-options.h"
 #include "progress.h"
+#include "prune-packed.h"
 #include "object-store.h"
 
 static const char * const prune_usage[] = {
index 3e624d1e008588ed063a7a960026287d34b84965..b5d51ea74fb02ee7bca8c9cf7b4bbf82e466c1f9 100644 (file)
@@ -12,7 +12,7 @@
 #include "parse-options.h"
 #include "exec-cmd.h"
 #include "run-command.h"
-#include "sha1-array.h"
+#include "oid-array.h"
 #include "remote.h"
 #include "dir.h"
 #include "rebase.h"
@@ -110,6 +110,7 @@ static char *opt_ipv4;
 static char *opt_ipv6;
 static int opt_show_forced_updates = -1;
 static char *set_upstream;
+static struct argv_array opt_fetch = ARGV_ARRAY_INIT;
 
 static struct option pull_options[] = {
        /* Shared options */
@@ -207,6 +208,15 @@ static struct option pull_options[] = {
        OPT_PASSTHRU(0, "depth", &opt_depth, N_("depth"),
                N_("deepen history of shallow clone"),
                0),
+       OPT_PASSTHRU_ARGV(0, "shallow-since", &opt_fetch, N_("time"),
+               N_("deepen history of shallow repository based on time"),
+               0),
+       OPT_PASSTHRU_ARGV(0, "shallow-exclude", &opt_fetch, N_("revision"),
+               N_("deepen history of shallow clone, excluding rev"),
+               0),
+       OPT_PASSTHRU_ARGV(0, "deepen", &opt_fetch, N_("n"),
+               N_("deepen history of shallow clone"),
+               0),
        OPT_PASSTHRU(0, "unshallow", &opt_unshallow, NULL,
                N_("convert to a complete repository"),
                PARSE_OPT_NONEG | PARSE_OPT_NOARG),
@@ -216,12 +226,19 @@ static struct option pull_options[] = {
        OPT_PASSTHRU(0, "refmap", &opt_refmap, N_("refmap"),
                N_("specify fetch refmap"),
                PARSE_OPT_NONEG),
+       OPT_PASSTHRU_ARGV('o', "server-option", &opt_fetch,
+               N_("server-specific"),
+               N_("option to transmit"),
+               0),
        OPT_PASSTHRU('4',  "ipv4", &opt_ipv4, NULL,
                N_("use IPv4 addresses only"),
                PARSE_OPT_NOARG),
        OPT_PASSTHRU('6',  "ipv6", &opt_ipv6, NULL,
                N_("use IPv6 addresses only"),
                PARSE_OPT_NOARG),
+       OPT_PASSTHRU_ARGV(0, "negotiation-tip", &opt_fetch, N_("revision"),
+               N_("report that we have only objects reachable from this object"),
+               0),
        OPT_BOOL(0, "show-forced-updates", &opt_show_forced_updates,
                 N_("check for forced-updates on all updated branches")),
        OPT_PASSTHRU(0, "set-upstream", &set_upstream, NULL,
@@ -327,6 +344,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;
 }
 
@@ -551,6 +584,7 @@ static int run_fetch(const char *repo, const char **refspecs)
                argv_array_push(&args, "--no-show-forced-updates");
        if (set_upstream)
                argv_array_push(&args, set_upstream);
+       argv_array_pushv(&args, opt_fetch.argv);
 
        if (repo) {
                argv_array_push(&args, repo);
@@ -976,6 +1010,7 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
 
        if (opt_rebase) {
                int ret = 0;
+               int ran_ff = 0;
                if ((recurse_submodules == RECURSE_SUBMODULES_ON ||
                     recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND) &&
                    submodule_touches_in_range(the_repository, &rebase_fork_point, &curr_head))
@@ -992,10 +1027,12 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
                        if (is_descendant_of(merge_head, list)) {
                                /* we can fast-forward this without invoking rebase */
                                opt_ff = "--ff-only";
+                               ran_ff = 1;
                                ret = run_merge();
                        }
                }
-               ret = run_rebase(&curr_head, merge_heads.oid, &rebase_fork_point);
+               if (!ran_ff)
+                       ret = run_rebase(&curr_head, merge_heads.oid, &rebase_fork_point);
 
                if (!ret && (recurse_submodules == RECURSE_SUBMODULES_ON ||
                             recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND))
index bff53d5d167e8501d7f501046ddb35daf0f2cd17..c466923869d6fdd211347125986e26c82e1d0153 100644 (file)
@@ -85,6 +85,7 @@ struct rebase_options {
        const char *action;
        int signoff;
        int allow_rerere_autoupdate;
+       int keep_empty;
        int autosquash;
        char *gpg_sign_opt;
        int autostash;
@@ -95,11 +96,13 @@ struct rebase_options {
        struct strbuf git_format_patch_opt;
        int reschedule_failed_exec;
        int use_legacy_rebase;
+       int reapply_cherry_picks;
 };
 
 #define REBASE_OPTIONS_INIT {                          \
                .type = REBASE_UNSPECIFIED,             \
                .empty = EMPTY_UNSPECIFIED,             \
+               .keep_empty = 1,                        \
                .default_backend = "merge",             \
                .flags = REBASE_NO_QUIET,               \
                .git_am_opts = ARGV_ARRAY_INIT,         \
@@ -379,11 +382,13 @@ static int run_sequencer_rebase(struct rebase_options *opts,
 
        git_config_get_bool("rebase.abbreviatecommands", &abbreviate_commands);
 
+       flags |= opts->keep_empty ? TODO_LIST_KEEP_EMPTY : 0;
        flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;
        flags |= opts->rebase_merges ? TODO_LIST_REBASE_MERGES : 0;
        flags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;
        flags |= opts->root_with_onto ? TODO_LIST_ROOT_WITH_ONTO : 0;
        flags |= command == ACTION_SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;
+       flags |= opts->reapply_cherry_picks ? TODO_LIST_REAPPLY_CHERRY_PICKS : 0;
 
        switch (command) {
        case ACTION_NONE: {
@@ -442,6 +447,7 @@ static int run_sequencer_rebase(struct rebase_options *opts,
        return ret;
 }
 
+static void imply_merge(struct rebase_options *opts, const char *option);
 static int parse_opt_keep_empty(const struct option *opt, const char *arg,
                                int unset)
 {
@@ -449,10 +455,8 @@ static int parse_opt_keep_empty(const struct option *opt, const char *arg,
 
        BUG_ON_OPT_ARG(arg);
 
-       /*
-        * If we ever want to remap --keep-empty to --empty=keep, insert:
-        *      opts->empty = unset ? EMPTY_UNSPECIFIED : EMPTY_KEEP;
-        */
+       imply_merge(opts, unset ? "--no-keep-empty" : "--keep-empty");
+       opts->keep_empty = !unset;
        opts->type = REBASE_MERGE;
        return 0;
 }
@@ -471,7 +475,7 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)
                OPT_NEGBIT(0, "ff", &opts.flags, N_("allow fast-forward"),
                           REBASE_FORCE),
                { OPTION_CALLBACK, 'k', "keep-empty", &options, NULL,
-                       N_("(DEPRECATED) keep empty commits"),
+                       N_("keep commits which start empty"),
                        PARSE_OPT_NOARG | PARSE_OPT_HIDDEN,
                        parse_opt_keep_empty },
                OPT_BOOL_F(0, "allow-empty-message", &opts.allow_empty_message,
@@ -559,7 +563,7 @@ static void imply_merge(struct rebase_options *opts, const char *option)
 {
        switch (opts->type) {
        case REBASE_APPLY:
-               die(_("%s requires an interactive rebase"), option);
+               die(_("%s requires the merge backend"), option);
                break;
        case REBASE_MERGE:
        case REBASE_PRESERVE_MERGES:
@@ -868,6 +872,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;
 
@@ -1162,6 +1167,7 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)
                opts->allow_rerere_autoupdate ?
                        opts->allow_rerere_autoupdate == RERERE_AUTOUPDATE ?
                        "--rerere-autoupdate" : "--no-rerere-autoupdate" : "");
+       add_var(&script_snippet, "keep_empty", opts->keep_empty ? "yes" : "");
        add_var(&script_snippet, "autosquash", opts->autosquash ? "t" : "");
        add_var(&script_snippet, "gpg_sign_opt", opts->gpg_sign_opt);
        add_var(&script_snippet, "cmd", opts->cmd);
@@ -1547,7 +1553,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                               N_("how to handle commits that become empty"),
                               PARSE_OPT_NONEG, parse_opt_empty),
                { OPTION_CALLBACK, 'k', "keep-empty", &options, NULL,
-                       N_("(DEPRECATED) keep empty commits"),
+                       N_("keep commits which start empty"),
                        PARSE_OPT_NOARG | PARSE_OPT_HIDDEN,
                        parse_opt_keep_empty },
                OPT_BOOL(0, "autosquash", &options.autosquash,
@@ -1582,6 +1588,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                OPT_BOOL(0, "reschedule-failed-exec",
                         &reschedule_failed_exec,
                         N_("automatically re-schedule any `exec` that fails")),
+               OPT_BOOL(0, "reapply-cherry-picks", &options.reapply_cherry_picks,
+                        N_("apply all changes, even those already present upstream")),
                OPT_END(),
        };
        int i;
@@ -1592,6 +1600,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 
        options.allow_empty_message = 1;
        git_config(rebase_config, &options);
+       /* options.gpg_sign_opt will be either "-S" or NULL */
+       gpg_sign = options.gpg_sign_opt ? "" : NULL;
+       FREE_AND_NULL(options.gpg_sign_opt);
 
        if (options.use_legacy_rebase ||
            !git_env_bool("GIT_TEST_REBASE_USE_BUILTIN", -1))
@@ -1822,10 +1833,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
        if (options.empty != EMPTY_UNSPECIFIED)
                imply_merge(&options, "--empty");
 
-       if (gpg_sign) {
-               free(options.gpg_sign_opt);
+       if (options.reapply_cherry_picks)
+               imply_merge(&options, "--reapply-cherry-picks");
+
+       if (gpg_sign)
                options.gpg_sign_opt = xstrfmt("-S%s", gpg_sign);
-       }
 
        if (exec.nr) {
                int i;
index 2cc18bbffdcf41cf2510becf6fcdd74c5946494a..239094d2dcac5ec806b0d6d2661c8ac70a61b4a1 100644 (file)
@@ -13,7 +13,7 @@
 #include "remote.h"
 #include "connect.h"
 #include "string-list.h"
-#include "sha1-array.h"
+#include "oid-array.h"
 #include "connected.h"
 #include "argv-array.h"
 #include "version.h"
@@ -499,12 +499,27 @@ static char *find_header(const char *msg, size_t len, const char *key,
        return NULL;
 }
 
+/*
+ * Return zero if a and b are equal up to n bytes and nonzero if they are not.
+ * This operation is guaranteed to run in constant time to avoid leaking data.
+ */
+static int constant_memequal(const char *a, const char *b, size_t n)
+{
+       int res = 0;
+       size_t i;
+
+       for (i = 0; i < n; i++)
+               res |= a[i] ^ b[i];
+       return res;
+}
+
 static const char *check_nonce(const char *buf, size_t len)
 {
        char *nonce = find_header(buf, len, "nonce", NULL);
        timestamp_t stamp, ostamp;
        char *bohmac, *expect = NULL;
        const char *retval = NONCE_BAD;
+       size_t noncelen;
 
        if (!nonce) {
                retval = NONCE_MISSING;
@@ -546,8 +561,14 @@ static const char *check_nonce(const char *buf, size_t len)
                goto leave;
        }
 
+       noncelen = strlen(nonce);
        expect = prepare_push_cert_nonce(service_dir, stamp);
-       if (strcmp(expect, nonce)) {
+       if (noncelen != strlen(expect)) {
+               /* This is not even the right size. */
+               retval = NONCE_BAD;
+               goto leave;
+       }
+       if (constant_memequal(expect, nonce, noncelen)) {
                /* Not what we would have signed earlier */
                retval = NONCE_BAD;
                goto leave;
index 81dfd563c0d937eb8943f84493d2d48e8af15f1b..52ecf6d43c1010e431c2f408a4acb79a31a1543e 100644 (file)
@@ -459,7 +459,7 @@ static struct reflog_expire_cfg *find_cfg_ent(const char *pattern, size_t len)
 static int reflog_expire_config(const char *var, const char *value, void *cb)
 {
        const char *pattern, *key;
-       int pattern_len;
+       size_t pattern_len;
        timestamp_t expire;
        int slot;
        struct reflog_expire_cfg *ent;
index 0781763b06e80d0188acfe3c902061729b762809..1b686ee9cee7c13f7c0052f397e1006d079667a0 100644 (file)
@@ -10,6 +10,7 @@
 #include "argv-array.h"
 #include "midx.h"
 #include "packfile.h"
+#include "prune-packed.h"
 #include "object-store.h"
 #include "promisor-remote.h"
 
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 098ebf22d0d65a6f98606c7ea567467641976f6b..f2c5a34402962ac2ff263c532462e2fc30a19622 100644 (file)
@@ -11,7 +11,7 @@
 #include "quote.h"
 #include "transport.h"
 #include "version.h"
-#include "sha1-array.h"
+#include "oid-array.h"
 #include "gpg-interface.h"
 #include "gettext.h"
 #include "protocol.h"
index 940596edb6bccf78e3e1a8b90cdc3c638d890093..a43a92ec7438c571c9322d5d206effb0ea07febb 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..dd160b49c7d407c9929bb91f48aec194dfc05f12 100644 (file)
@@ -17,7 +17,7 @@
 #include "diff.h"
 #include "revision.h"
 #include "gpg-interface.h"
-#include "sha1-array.h"
+#include "oid-array.h"
 #include "column.h"
 #include "ref-filter.h"
 
@@ -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..0f0485ecfe2cd9a2270cffcec3552e901658539a 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -14,7 +14,7 @@
 #include "pack-revindex.h"
 #include "hash.h"
 #include "path.h"
-#include "sha1-array.h"
+#include "oid-array.h"
 #include "repository.h"
 #include "mem-pool.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 d5c4d839dcf23c0204f93d486a53d41860d8631b..002e0e5438bc27472d032ac562a2f3a29ec20a68 100644 (file)
@@ -10,7 +10,7 @@
 #include "log-tree.h"
 #include "refs.h"
 #include "userdiff.h"
-#include "sha1-array.h"
+#include "oid-array.h"
 #include "revision.h"
 
 static int compare_paths(const struct combine_diff_path *one,
index f013a84e294b13b552b62257bbf2fa7e1c353a82..0d0d37787a0c3561d63b6d237fb43790ac8870bd 100644 (file)
@@ -1707,7 +1707,7 @@ static void expire_commit_graphs(struct write_commit_graph_context *ctx)
        timestamp_t expire_time = time(NULL);
 
        if (ctx->split_opts && ctx->split_opts->expire_time)
-               expire_time -= ctx->split_opts->expire_time;
+               expire_time = ctx->split_opts->expire_time;
        if (!ctx->split) {
                char *chain_file_name = get_chain_filename(ctx->odb);
                unlink(chain_file_name);
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 d14065d60ec497131a23549727439d624ff4c710..8ee0b6408e945b369b30d9052c91aa7e1f31553a 100644 (file)
@@ -460,8 +460,21 @@ static int mingw_open_append(wchar_t const *wfilename, int oflags, ...)
        handle = CreateFileW(wfilename, FILE_APPEND_DATA,
                        FILE_SHARE_WRITE | FILE_SHARE_READ,
                        NULL, create, FILE_ATTRIBUTE_NORMAL, NULL);
-       if (handle == INVALID_HANDLE_VALUE)
-               return errno = err_win_to_posix(GetLastError()), -1;
+       if (handle == INVALID_HANDLE_VALUE) {
+               DWORD err = GetLastError();
+
+               /*
+                * Some network storage solutions (e.g. Isilon) might return
+                * ERROR_INVALID_PARAMETER instead of expected error
+                * ERROR_PATH_NOT_FOUND, which results in an unknown error. If
+                * so, let's turn the error to ERROR_PATH_NOT_FOUND instead.
+                */
+               if (err == ERROR_INVALID_PARAMETER)
+                       err = ERROR_PATH_NOT_FOUND;
+
+               errno = err_win_to_posix(err);
+               return -1;
+       }
 
        /*
         * No O_APPEND here, because the CRT uses it only to reset the
@@ -964,7 +977,16 @@ revert_attrs:
 size_t mingw_strftime(char *s, size_t max,
                      const char *format, const struct tm *tm)
 {
-       size_t ret = strftime(s, max, format, tm);
+       /* a pointer to the original strftime in case we can't find the UCRT version */
+       static size_t (*fallback)(char *, size_t, const char *, const struct tm *) = strftime;
+       size_t ret;
+       DECLARE_PROC_ADDR(ucrtbase.dll, size_t, strftime, char *, size_t,
+               const char *, const struct tm *);
+
+       if (INIT_PROC_ADDR(strftime))
+               ret = strftime(s, max, format, tm);
+       else
+               ret = fallback(s, max, format, tm);
 
        if (!ret && errno == EINVAL)
                die("invalid strftime format: '%s'", format);
@@ -1479,6 +1501,7 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen
        const char *(*quote_arg)(const char *arg) =
                is_msys2_sh(cmd ? cmd : *argv) ?
                quote_arg_msys2 : quote_arg_msvc;
+       const char *strace_env;
 
        /* Make sure to override previous errors, if any */
        errno = 0;
@@ -1562,6 +1585,31 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen
                        free(quoted);
        }
 
+       strace_env = getenv("GIT_STRACE_COMMANDS");
+       if (strace_env) {
+               char *p = path_lookup("strace.exe", 1);
+               if (!p)
+                       return error("strace not found!");
+               if (xutftowcs_path(wcmd, p) < 0) {
+                       free(p);
+                       return -1;
+               }
+               free(p);
+               if (!strcmp("1", strace_env) ||
+                   !strcasecmp("yes", strace_env) ||
+                   !strcasecmp("true", strace_env))
+                       strbuf_insert(&args, 0, "strace ", 7);
+               else {
+                       const char *quoted = quote_arg(strace_env);
+                       struct strbuf buf = STRBUF_INIT;
+                       strbuf_addf(&buf, "strace -o %s ", quoted);
+                       if (quoted != strace_env)
+                               free((char *)quoted);
+                       strbuf_insert(&args, 0, buf.buf, buf.len);
+                       strbuf_release(&buf);
+               }
+       }
+
        ALLOC_ARRAY(wargs, st_add(st_mult(2, args.len), 1));
        xutftowcs(wargs, args.buf, 2 * args.len + 1);
        strbuf_release(&args);
@@ -2581,12 +2629,14 @@ not_a_reserved_name:
                                        continue;
                                }
                                break;
-                       case 'c': case 'C': /* COM<N>, CON, CONIN$, CONOUT$ */
+                       case 'c': case 'C':
+                               /* COM1 ... COM9, CON, CONIN$, CONOUT$ */
                                if ((c = path[++i]) != 'o' && c != 'O')
                                        goto not_a_reserved_name;
                                c = path[++i];
-                               if (c == 'm' || c == 'M') { /* COM<N> */
-                                       if (!isdigit(path[++i]))
+                               if (c == 'm' || c == 'M') { /* COM1 ... COM9 */
+                                       c = path[++i];
+                                       if (c < '1' || c > '9')
                                                goto not_a_reserved_name;
                                } else if (c == 'n' || c == 'N') { /* CON */
                                        c = path[i + 1];
index f2e70872cd20675dd2f84f72e7608799f16941ad..bba2b644080f064cb5160ce41da16841afadeeed 100644 (file)
@@ -20,6 +20,17 @@ static inline char *win32_find_last_dir_sep(const char *path)
        return ret;
 }
 #define find_last_dir_sep win32_find_last_dir_sep
+static inline int win32_has_dir_sep(const char *path)
+{
+       /*
+        * See how long the non-separator part of the given path is, and
+        * if and only if it covers the whole path (i.e. path[len] is NUL),
+        * there is no separator in the path---otherwise there is a separator.
+        */
+       size_t len = strcspn(path, "/\\");
+       return !!path[len];
+}
+#define has_dir_sep(path) win32_has_dir_sep(path)
 int win32_offset_1st_component(const char *path);
 #define offset_1st_component win32_offset_1st_component
 
index d17d2bd9dcdef8e4f16316090e1265145834926b..8db9c77098f01bec5178751c8b4e64880d473619 100644 (file)
--- a/config.c
+++ b/config.c
@@ -37,6 +37,7 @@ struct config_source {
        enum config_error_action default_error_action;
        int linenr;
        int eof;
+       size_t total_len;
        struct strbuf value;
        struct strbuf var;
        unsigned subsection_case_sensitive : 1;
@@ -309,7 +310,7 @@ int git_config_include(const char *var, const char *value, void *data)
 {
        struct config_include_data *inc = data;
        const char *cond, *key;
-       int cond_len;
+       size_t cond_len;
        int ret;
 
        /*
@@ -358,12 +359,13 @@ static inline int iskeychar(int c)
  *
  * store_key - pointer to char* which will hold a copy of the key with
  *             lowercase section and variable name
- * baselen - pointer to int which will hold the length of the
+ * baselen - pointer to size_t which will hold the length of the
  *           section + subsection part, can be NULL
  */
-static int git_config_parse_key_1(const char *key, char **store_key, int *baselen_, int quiet)
+static int git_config_parse_key_1(const char *key, char **store_key, size_t *baselen_, int quiet)
 {
-       int i, dot, baselen;
+       size_t i, baselen;
+       int dot;
        const char *last_dot = strrchr(key, '.');
 
        /*
@@ -425,7 +427,7 @@ out_free_ret_1:
        return -CONFIG_INVALID_KEY;
 }
 
-int git_config_parse_key(const char *key, char **store_key, int *baselen)
+int git_config_parse_key(const char *key, char **store_key, size_t *baselen)
 {
        return git_config_parse_key_1(key, store_key, baselen, 0);
 }
@@ -523,6 +525,19 @@ static int get_next_char(void)
                        c = '\r';
                }
        }
+
+       if (c != EOF && ++cf->total_len > INT_MAX) {
+               /*
+                * This is an absurdly long config file; refuse to parse
+                * further in order to protect downstream code from integer
+                * overflows. Note that we can't return an error specifically,
+                * but we can mark EOF and put trash in the return value,
+                * which will trigger a parse error.
+                */
+               cf->eof = 1;
+               return 0;
+       }
+
        if (c == '\n')
                cf->linenr++;
        if (c == EOF) {
@@ -728,7 +743,7 @@ static int git_parse_source(config_fn_t fn, void *data,
                            const struct config_options *opts)
 {
        int comment = 0;
-       int baselen = 0;
+       size_t baselen = 0;
        struct strbuf *var = &cf->var;
        int error_return = 0;
        char *error_msg = NULL;
@@ -1539,6 +1554,7 @@ static int do_config_from(struct config_source *top, config_fn_t fn, void *data,
        top->prev = cf;
        top->linenr = 1;
        top->eof = 0;
+       top->total_len = 0;
        strbuf_init(&top->value, 1024);
        strbuf_init(&top->var, 1024);
        cf = top;
@@ -2383,7 +2399,7 @@ void git_die_config(const char *key, const char *err, ...)
  */
 
 struct config_store_data {
-       int baselen;
+       size_t baselen;
        char *key;
        int do_not_match;
        regex_t *value_regex;
@@ -2509,7 +2525,7 @@ static struct strbuf store_create_section(const char *key,
                                          const struct config_store_data *store)
 {
        const char *dot;
-       int i;
+       size_t i;
        struct strbuf sb = STRBUF_INIT;
 
        dot = memchr(key, '.', store->baselen);
@@ -2522,7 +2538,9 @@ static struct strbuf store_create_section(const char *key,
                }
                strbuf_addstr(&sb, "\"]\n");
        } else {
-               strbuf_addf(&sb, "[%.*s]\n", store->baselen, key);
+               strbuf_addch(&sb, '[');
+               strbuf_add(&sb, key, store->baselen);
+               strbuf_addstr(&sb, "]\n");
        }
 
        return sb;
@@ -2545,7 +2563,6 @@ static ssize_t write_pair(int fd, const char *key, const char *value,
 {
        int i;
        ssize_t ret;
-       int length = strlen(key + store->baselen + 1);
        const char *quote = "";
        struct strbuf sb = STRBUF_INIT;
 
@@ -2564,8 +2581,7 @@ static ssize_t write_pair(int fd, const char *key, const char *value,
        if (i && value[i - 1] == ' ')
                quote = "\"";
 
-       strbuf_addf(&sb, "\t%.*s = %s",
-                   length, key + store->baselen + 1, quote);
+       strbuf_addf(&sb, "\t%s = %s", key + store->baselen + 1, quote);
 
        for (i = 0; value[i]; i++)
                switch (value[i]) {
@@ -3238,7 +3254,7 @@ int config_error_nonbool(const char *var)
 
 int parse_config_key(const char *var,
                     const char *section,
-                    const char **subsection, int *subsection_len,
+                    const char **subsection, size_t *subsection_len,
                     const char **key)
 {
        const char *dot;
index 9b3773f77826513226608a592e3295d7a6ea5e3f..060874488f413cd6453e1633cd69c971d6e5d613 100644 (file)
--- a/config.h
+++ b/config.h
@@ -254,7 +254,7 @@ int git_config_set_gently(const char *, const char *);
  */
 void git_config_set(const char *, const char *);
 
-int git_config_parse_key(const char *, char **, int *);
+int git_config_parse_key(const char *, char **, size_t *);
 int git_config_key_is_valid(const char *key);
 int git_config_set_multivar_gently(const char *, const char *, const char *, int);
 void git_config_set_multivar(const char *, const char *, const char *, int);
@@ -359,7 +359,7 @@ int git_config_include(const char *name, const char *value, void *data);
  */
 int parse_config_key(const char *var,
                     const char *section,
-                    const char **subsection, int *subsection_len,
+                    const char **subsection, size_t *subsection_len,
                     const char **key);
 
 /**
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 b6451ab5e8909017edc678b3021384f3920fbe75..23013c634436adeeac65bd5e6a0d895a06fe6fe6 100644 (file)
--- a/connect.c
+++ b/connect.c
@@ -9,7 +9,7 @@
 #include "connect.h"
 #include "url.h"
 #include "string-list.h"
-#include "sha1-array.h"
+#include "oid-array.h"
 #include "transport.h"
 #include "strbuf.h"
 #include "version.h"
index 7e9bd1bc622eefa3dad76c8e786767a0ffa3077f..3135b71e1961ae421855ad4b05887c795c437490 100644 (file)
@@ -52,7 +52,7 @@ int check_connected(oid_iterate_fn fn, void *cb_data,
                strbuf_release(&idx_file);
        }
 
-       if (opt->check_refs_are_promisor_objects_only) {
+       if (has_promisor_remote()) {
                /*
                 * For partial clones, we don't want to have to do a regular
                 * connectivity check because we have to enumerate and exclude
@@ -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;
 
@@ -71,13 +75,18 @@ int check_connected(oid_iterate_fn fn, void *cb_data,
                                if (find_pack_entry_one(oid.hash, p))
                                        goto promisor_pack_found;
                        }
-                       return 1;
+                       /*
+                        * Fallback to rev-list with oid and the rest of the
+                        * object IDs provided by fn.
+                        */
+                       goto no_promisor_pack_found;
 promisor_pack_found:
                        ;
                } while (!fn(cb_data, &oid));
                return 0;
        }
 
+no_promisor_pack_found:
        if (opt->shallow_file) {
                argv_array_push(&rev_list.args, "--shallow-file");
                argv_array_push(&rev_list.args, opt->shallow_file);
index eba5c261bac1887289ac70425cee8c97e9006ece..8d5a6b3ad6fe4bb0f9ca0930f8eea82543968306 100644 (file)
@@ -46,15 +46,6 @@ struct check_connected_options {
         * during a fetch.
         */
        unsigned is_deepening_fetch : 1;
-
-       /*
-        * If non-zero, only check that the top-level objects referenced by the
-        * wanted refs (passed in as cb_data) are promisor objects. This is
-        * useful for partial clones, where enumerating and excluding all
-        * promisor objects is very slow and the commit-walk itself becomes a
-        * no-op.
-        */
-       unsigned check_refs_are_promisor_objects_only : 1;
 };
 
 #define CHECK_CONNECTED_INIT { 0 }
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 6906aae44147810ec2cf7560eb2cae3ef56bb454..6fa7496bfdb3fdbcf42a03e88cbcd773f6a302cc 100644 (file)
@@ -25,14 +25,16 @@ ASCIIDOC_HTML    = xhtml11
 ASCIIDOC_DOCBOOK = docbook
 ASCIIDOC_EXTRA   =
 XMLTO            = xmlto
+XMLTO_EXTRA      =
 
 ifdef USE_ASCIIDOCTOR
 ASCIIDOC         = asciidoctor
 ASCIIDOC_CONF    =
 ASCIIDOC_HTML    = xhtml5
-ASCIIDOC_DOCBOOK = docbook45
+ASCIIDOC_DOCBOOK = docbook
 ASCIIDOC_EXTRA  += -I../../Documentation -rasciidoctor-extensions
 ASCIIDOC_EXTRA  += -alitdd='&\#x2d;&\#x2d;'
+XMLTO_EXTRA     += --skip-validation
 endif
 
 ifndef SHELL_PATH
@@ -78,7 +80,7 @@ install-html: $(GIT_SUBTREE_HTML)
        $(INSTALL) -m 644 $^ $(DESTDIR)$(htmldir)
 
 $(GIT_SUBTREE_DOC): $(GIT_SUBTREE_XML)
-       $(XMLTO) -m $(MANPAGE_XSL) man $^
+       $(XMLTO) -m $(MANPAGE_XSL) $(XMLTO_EXTRA) man $^
 
 $(GIT_SUBTREE_XML): $(GIT_SUBTREE_TXT)
        $(ASCIIDOC) -b $(ASCIIDOC_DOCBOOK) -d manpage $(ASCIIDOC_CONF) \
index 5ead3ce678bb746fa82cef9e3b8e8ac407aa0741..572449825c5a4c8fd1ff65bb0c947d0c567d002b 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;
 }
@@ -998,7 +1018,7 @@ static int apply_filter(const char *path, const char *src, size_t len,
 static int read_convert_config(const char *var, const char *value, void *cb)
 {
        const char *key, *name;
-       int namelen;
+       size_t namelen;
        struct convert_driver *drv;
 
        /*
@@ -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..064e25e5d5754b68e54664fbb13b79b289d4e006 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,12 +391,22 @@ 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, ':');
-       slash = strchrnul(cp, '/');
+
+       /*
+        * A query or fragment marker before the slash ends the host portion.
+        * We'll just continue to call this "slash" for simplicity. Notably our
+        * "trim leading slashes" part won't skip over this part of the path,
+        * but that's what we'd want.
+        */
+       slash = cp + strcspn(cp, "/?#");
 
        if (!at || slash <= at) {
                /* Case (1) */
@@ -392,10 +427,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 +439,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)
index 09dbd3cf72ba99d0f8ab793298b6b80215d450d3..aa98b2e54146f7de1f349f16a9a01e754a3a0b73 100644 (file)
@@ -17,7 +17,7 @@
 #include "pack-bitmap.h"
 #include "pack-objects.h"
 #include "delta-islands.h"
-#include "sha1-array.h"
+#include "oid-array.h"
 #include "config.h"
 
 KHASH_INIT(str, const char *, void *, 1, kh_str_hash_func, kh_str_hash_equal)
diff --git a/diff.c b/diff.c
index f2cfbf2214a29fc9ddf0e356230df7625d3d1139..d1ad6a3c4ad0bc5049d1af26ca0ea33fc9f68a1e 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -573,7 +573,7 @@ static int fill_mmfile(struct repository *r, mmfile_t *mf,
                mf->size = 0;
                return 0;
        }
-       else if (diff_populate_filespec(r, one, 0))
+       else if (diff_populate_filespec(r, one, NULL))
                return -1;
 
        mf->ptr = one->data;
@@ -585,9 +585,13 @@ static int fill_mmfile(struct repository *r, mmfile_t *mf,
 static unsigned long diff_filespec_size(struct repository *r,
                                        struct diff_filespec *one)
 {
+       struct diff_populate_filespec_options dpf_options = {
+               .check_size_only = 1,
+       };
+
        if (!DIFF_FILE_VALID(one))
                return 0;
-       diff_populate_filespec(r, one, CHECK_SIZE_ONLY);
+       diff_populate_filespec(r, one, &dpf_options);
        return one->size;
 }
 
@@ -3020,6 +3024,9 @@ static void show_dirstat(struct diff_options *options)
                struct diff_filepair *p = q->queue[i];
                const char *name;
                unsigned long copied, added, damage;
+               struct diff_populate_filespec_options dpf_options = {
+                       .check_size_only = 1,
+               };
 
                name = p->two->path ? p->two->path : p->one->path;
 
@@ -3047,19 +3054,19 @@ static void show_dirstat(struct diff_options *options)
                }
 
                if (DIFF_FILE_VALID(p->one) && DIFF_FILE_VALID(p->two)) {
-                       diff_populate_filespec(options->repo, p->one, 0);
-                       diff_populate_filespec(options->repo, p->two, 0);
+                       diff_populate_filespec(options->repo, p->one, NULL);
+                       diff_populate_filespec(options->repo, p->two, NULL);
                        diffcore_count_changes(options->repo,
                                               p->one, p->two, NULL, NULL,
                                               &copied, &added);
                        diff_free_filespec_data(p->one);
                        diff_free_filespec_data(p->two);
                } else if (DIFF_FILE_VALID(p->one)) {
-                       diff_populate_filespec(options->repo, p->one, CHECK_SIZE_ONLY);
+                       diff_populate_filespec(options->repo, p->one, &dpf_options);
                        copied = added = 0;
                        diff_free_filespec_data(p->one);
                } else if (DIFF_FILE_VALID(p->two)) {
-                       diff_populate_filespec(options->repo, p->two, CHECK_SIZE_ONLY);
+                       diff_populate_filespec(options->repo, p->two, &dpf_options);
                        copied = 0;
                        added = p->two->size;
                        diff_free_filespec_data(p->two);
@@ -3339,13 +3346,17 @@ static void emit_binary_diff(struct diff_options *o,
 int diff_filespec_is_binary(struct repository *r,
                            struct diff_filespec *one)
 {
+       struct diff_populate_filespec_options dpf_options = {
+               .check_binary = 1,
+       };
+
        if (one->is_binary == -1) {
                diff_filespec_load_driver(one, r->index);
                if (one->driver->binary != -1)
                        one->is_binary = one->driver->binary;
                else {
                        if (!one->data && DIFF_FILE_VALID(one))
-                               diff_populate_filespec(r, one, CHECK_BINARY);
+                               diff_populate_filespec(r, one, &dpf_options);
                        if (one->is_binary == -1 && one->data)
                                one->is_binary = buffer_is_binary(one->data,
                                                one->size);
@@ -3677,8 +3688,8 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
        }
 
        else if (complete_rewrite) {
-               diff_populate_filespec(o->repo, one, 0);
-               diff_populate_filespec(o->repo, two, 0);
+               diff_populate_filespec(o->repo, one, NULL);
+               diff_populate_filespec(o->repo, two, NULL);
                data->deleted = count_lines(one->data, one->size);
                data->added = count_lines(two->data, two->size);
        }
@@ -3914,9 +3925,10 @@ static int diff_populate_gitlink(struct diff_filespec *s, int size_only)
  */
 int diff_populate_filespec(struct repository *r,
                           struct diff_filespec *s,
-                          unsigned int flags)
+                          const struct diff_populate_filespec_options *options)
 {
-       int size_only = flags & CHECK_SIZE_ONLY;
+       int size_only = options ? options->check_size_only : 0;
+       int check_binary = options ? options->check_binary : 0;
        int err = 0;
        int conv_flags = global_conv_flags_eol;
        /*
@@ -3986,7 +3998,7 @@ int diff_populate_filespec(struct repository *r,
                 * opening the file and inspecting the contents, this
                 * is probably fine.
                 */
-               if ((flags & CHECK_BINARY) &&
+               if (check_binary &&
                    s->size > big_file_threshold && s->is_binary == -1) {
                        s->is_binary = 1;
                        return 0;
@@ -4011,12 +4023,30 @@ int diff_populate_filespec(struct repository *r,
                }
        }
        else {
-               enum object_type type;
-               if (size_only || (flags & CHECK_BINARY)) {
-                       type = oid_object_info(r, &s->oid, &s->size);
-                       if (type < 0)
-                               die("unable to read %s",
-                                   oid_to_hex(&s->oid));
+               struct object_info info = {
+                       .sizep = &s->size
+               };
+
+               if (!(size_only || check_binary))
+                       /*
+                        * Set contentp, since there is no chance that merely
+                        * the size is sufficient.
+                        */
+                       info.contentp = &s->data;
+
+               if (options && options->missing_object_cb) {
+                       if (!oid_object_info_extended(r, &s->oid, &info,
+                                                     OBJECT_INFO_LOOKUP_REPLACE |
+                                                     OBJECT_INFO_SKIP_FETCH_OBJECT))
+                               goto object_read;
+                       options->missing_object_cb(options->missing_object_data);
+               }
+               if (oid_object_info_extended(r, &s->oid, &info,
+                                            OBJECT_INFO_LOOKUP_REPLACE))
+                       die("unable to read %s", oid_to_hex(&s->oid));
+
+object_read:
+               if (size_only || check_binary) {
                        if (size_only)
                                return 0;
                        if (s->size > big_file_threshold && s->is_binary == -1) {
@@ -4024,9 +4054,12 @@ int diff_populate_filespec(struct repository *r,
                                return 0;
                        }
                }
-               s->data = repo_read_object_file(r, &s->oid, &type, &s->size);
-               if (!s->data)
-                       die("unable to read %s", oid_to_hex(&s->oid));
+               if (!info.contentp) {
+                       info.contentp = &s->data;
+                       if (oid_object_info_extended(r, &s->oid, &info,
+                                                    OBJECT_INFO_LOOKUP_REPLACE))
+                               die("unable to read %s", oid_to_hex(&s->oid));
+               }
                s->should_free = 1;
        }
        return 0;
@@ -4062,6 +4095,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 +4107,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;
        }
@@ -4141,7 +4177,7 @@ static struct diff_tempfile *prepare_temp_file(struct repository *r,
                return temp;
        }
        else {
-               if (diff_populate_filespec(r, one, 0))
+               if (diff_populate_filespec(r, one, NULL))
                        die("cannot read data blob for %s", one->path);
                prep_temp_blob(r->index, name, temp,
                               one->data, one->size,
@@ -6407,9 +6443,9 @@ static int diff_filespec_is_identical(struct repository *r,
 {
        if (S_ISGITLINK(one->mode))
                return 0;
-       if (diff_populate_filespec(r, one, 0))
+       if (diff_populate_filespec(r, one, NULL))
                return 0;
-       if (diff_populate_filespec(r, two, 0))
+       if (diff_populate_filespec(r, two, NULL))
                return 0;
        return !memcmp(one->data, two->data, one->size);
 }
@@ -6417,6 +6453,12 @@ static int diff_filespec_is_identical(struct repository *r,
 static int diff_filespec_check_stat_unmatch(struct repository *r,
                                            struct diff_filepair *p)
 {
+       struct diff_populate_filespec_options dpf_options = {
+               .check_size_only = 1,
+               .missing_object_cb = diff_queued_diff_prefetch,
+               .missing_object_data = r,
+       };
+
        if (p->done_skip_stat_unmatch)
                return p->skip_stat_unmatch_result;
 
@@ -6439,8 +6481,8 @@ static int diff_filespec_check_stat_unmatch(struct repository *r,
            !DIFF_FILE_VALID(p->two) ||
            (p->one->oid_valid && p->two->oid_valid) ||
            (p->one->mode != p->two->mode) ||
-           diff_populate_filespec(r, p->one, CHECK_SIZE_ONLY) ||
-           diff_populate_filespec(r, p->two, CHECK_SIZE_ONLY) ||
+           diff_populate_filespec(r, p->one, &dpf_options) ||
+           diff_populate_filespec(r, p->two, &dpf_options) ||
            (p->one->size != p->two->size) ||
            !diff_filespec_is_identical(r, p->one, p->two)) /* (2) */
                p->skip_stat_unmatch_result = 1;
@@ -6491,9 +6533,9 @@ void diffcore_fix_diff_index(void)
        QSORT(q->queue, q->nr, diffnamecmp);
 }
 
-static void add_if_missing(struct repository *r,
-                          struct oid_array *to_fetch,
-                          const struct diff_filespec *filespec)
+void diff_add_if_missing(struct repository *r,
+                        struct oid_array *to_fetch,
+                        const struct diff_filespec *filespec)
 {
        if (filespec && filespec->oid_valid &&
            !S_ISGITLINK(filespec->mode) &&
@@ -6502,30 +6544,48 @@ static void add_if_missing(struct repository *r,
                oid_array_append(to_fetch, &filespec->oid);
 }
 
-void diffcore_std(struct diff_options *options)
+void diff_queued_diff_prefetch(void *repository)
 {
-       if (options->repo == the_repository && has_promisor_remote()) {
-               /*
-                * Prefetch the diff pairs that are about to be flushed.
-                */
-               int i;
-               struct diff_queue_struct *q = &diff_queued_diff;
-               struct oid_array to_fetch = OID_ARRAY_INIT;
+       struct repository *repo = repository;
+       int i;
+       struct diff_queue_struct *q = &diff_queued_diff;
+       struct oid_array to_fetch = OID_ARRAY_INIT;
 
-               for (i = 0; i < q->nr; i++) {
-                       struct diff_filepair *p = q->queue[i];
-                       add_if_missing(options->repo, &to_fetch, p->one);
-                       add_if_missing(options->repo, &to_fetch, p->two);
-               }
-               if (to_fetch.nr)
-                       /*
-                        * NEEDSWORK: Consider deduplicating the OIDs sent.
-                        */
-                       promisor_remote_get_direct(options->repo,
-                                                  to_fetch.oid, to_fetch.nr);
-               oid_array_clear(&to_fetch);
+       for (i = 0; i < q->nr; i++) {
+               struct diff_filepair *p = q->queue[i];
+               diff_add_if_missing(repo, &to_fetch, p->one);
+               diff_add_if_missing(repo, &to_fetch, p->two);
        }
 
+       /*
+        * NEEDSWORK: Consider deduplicating the OIDs sent.
+        */
+       promisor_remote_get_direct(repo, to_fetch.oid, to_fetch.nr);
+
+       oid_array_clear(&to_fetch);
+}
+
+void diffcore_std(struct diff_options *options)
+{
+       int output_formats_to_prefetch = DIFF_FORMAT_DIFFSTAT |
+               DIFF_FORMAT_NUMSTAT |
+               DIFF_FORMAT_PATCH |
+               DIFF_FORMAT_SHORTSTAT |
+               DIFF_FORMAT_DIRSTAT;
+
+       /*
+        * Check if the user requested a blob-data-requiring diff output and/or
+        * break-rewrite detection (which requires blob data). If yes, prefetch
+        * the diff pairs.
+        *
+        * If no prefetching occurs, diffcore_rename() will prefetch if it
+        * decides that it needs inexact rename detection.
+        */
+       if (options->repo == the_repository && has_promisor_remote() &&
+           (options->output_format & output_formats_to_prefetch ||
+            options->pickaxe_opts & DIFF_PICKAXE_KINDS_MASK))
+               diff_queued_diff_prefetch(options->repo);
+
        /* NOTE please keep the following in sync with diff_tree_combined() */
        if (options->skip_stat_unmatch)
                diffcore_skip_stat_unmatch(options);
@@ -6771,7 +6831,7 @@ size_t fill_textconv(struct repository *r,
                        *outbuf = "";
                        return 0;
                }
-               if (diff_populate_filespec(r, df, 0))
+               if (diff_populate_filespec(r, df, NULL))
                        die("unable to read files to diff");
                *outbuf = df->data;
                return df->size;
index 9d20a6a6fc1d8abe1cb4a75afea6c38ea01c6d3c..0d4a14964d00b3a77016fb0ed677b1847d1ab72c 100644 (file)
@@ -4,6 +4,7 @@
 #include "cache.h"
 #include "diff.h"
 #include "diffcore.h"
+#include "promisor-remote.h"
 
 static int should_break(struct repository *r,
                        struct diff_filespec *src,
@@ -49,6 +50,8 @@ static int should_break(struct repository *r,
        unsigned long delta_size, max_size;
        unsigned long src_copied, literal_added, src_removed;
 
+       struct diff_populate_filespec_options options = { 0 };
+
        *merge_score_p = 0; /* assume no deletion --- "do not break"
                             * is the default.
                             */
@@ -62,8 +65,13 @@ static int should_break(struct repository *r,
            oideq(&src->oid, &dst->oid))
                return 0; /* they are the same */
 
-       if (diff_populate_filespec(r, src, 0) ||
-           diff_populate_filespec(r, dst, 0))
+       if (r == the_repository && has_promisor_remote()) {
+               options.missing_object_cb = diff_queued_diff_prefetch;
+               options.missing_object_data = r;
+       }
+
+       if (diff_populate_filespec(r, src, &options) ||
+           diff_populate_filespec(r, dst, &options))
                return 0; /* error but caught downstream */
 
        max_size = ((src->size > dst->size) ? src->size : dst->size);
index e189f407af3a8cc0155cd7a1146c9daacfe304a8..99e63e90f89afaf55ef16bb3c11c1546c4d76c2a 100644 (file)
@@ -1,4 +1,5 @@
 /*
+ *
  * Copyright (C) 2005 Junio C Hamano
  */
 #include "cache.h"
@@ -7,6 +8,7 @@
 #include "object-store.h"
 #include "hashmap.h"
 #include "progress.h"
+#include "promisor-remote.h"
 
 /* Table of rename/copy destinations */
 
@@ -128,10 +130,46 @@ struct diff_score {
        short name_score;
 };
 
+struct prefetch_options {
+       struct repository *repo;
+       int skip_unmodified;
+};
+static void prefetch(void *prefetch_options)
+{
+       struct prefetch_options *options = prefetch_options;
+       int i;
+       struct oid_array to_fetch = OID_ARRAY_INIT;
+
+       for (i = 0; i < rename_dst_nr; i++) {
+               if (rename_dst[i].pair)
+                       /*
+                        * The loop in diffcore_rename() will not need these
+                        * blobs, so skip prefetching.
+                        */
+                       continue; /* already found exact match */
+               diff_add_if_missing(options->repo, &to_fetch,
+                                   rename_dst[i].two);
+       }
+       for (i = 0; i < rename_src_nr; i++) {
+               if (options->skip_unmodified &&
+                   diff_unmodified_pair(rename_src[i].p))
+                       /*
+                        * The loop in diffcore_rename() will not need these
+                        * blobs, so skip prefetching.
+                        */
+                       continue;
+               diff_add_if_missing(options->repo, &to_fetch,
+                                   rename_src[i].p->one);
+       }
+       promisor_remote_get_direct(options->repo, to_fetch.oid, to_fetch.nr);
+       oid_array_clear(&to_fetch);
+}
+
 static int estimate_similarity(struct repository *r,
                               struct diff_filespec *src,
                               struct diff_filespec *dst,
-                              int minimum_score)
+                              int minimum_score,
+                              int skip_unmodified)
 {
        /* src points at a file that existed in the original tree (or
         * optionally a file in the destination tree) and dst points
@@ -148,6 +186,15 @@ static int estimate_similarity(struct repository *r,
         */
        unsigned long max_size, delta_size, base_size, src_copied, literal_added;
        int score;
+       struct diff_populate_filespec_options dpf_options = {
+               .check_size_only = 1
+       };
+       struct prefetch_options prefetch_options = {r, skip_unmodified};
+
+       if (r == the_repository && has_promisor_remote()) {
+               dpf_options.missing_object_cb = prefetch;
+               dpf_options.missing_object_data = &prefetch_options;
+       }
 
        /* We deal only with regular files.  Symlink renames are handled
         * only when they are exact matches --- in other words, no edits
@@ -166,10 +213,10 @@ static int estimate_similarity(struct repository *r,
         * say whether the size is valid or not!)
         */
        if (!src->cnt_data &&
-           diff_populate_filespec(r, src, CHECK_SIZE_ONLY))
+           diff_populate_filespec(r, src, &dpf_options))
                return 0;
        if (!dst->cnt_data &&
-           diff_populate_filespec(r, dst, CHECK_SIZE_ONLY))
+           diff_populate_filespec(r, dst, &dpf_options))
                return 0;
 
        max_size = ((src->size > dst->size) ? src->size : dst->size);
@@ -187,9 +234,11 @@ static int estimate_similarity(struct repository *r,
        if (max_size * (MAX_SCORE-minimum_score) < delta_size * MAX_SCORE)
                return 0;
 
-       if (!src->cnt_data && diff_populate_filespec(r, src, 0))
+       dpf_options.check_size_only = 0;
+
+       if (!src->cnt_data && diff_populate_filespec(r, src, &dpf_options))
                return 0;
-       if (!dst->cnt_data && diff_populate_filespec(r, dst, 0))
+       if (!dst->cnt_data && diff_populate_filespec(r, dst, &dpf_options))
                return 0;
 
        if (diffcore_count_changes(r, src, dst,
@@ -261,7 +310,7 @@ static unsigned int hash_filespec(struct repository *r,
                                  struct diff_filespec *filespec)
 {
        if (!filespec->oid_valid) {
-               if (diff_populate_filespec(r, filespec, 0))
+               if (diff_populate_filespec(r, filespec, NULL))
                        return 0;
                hash_object_file(r->hash_algo, filespec->data, filespec->size,
                                 "blob", &filespec->oid);
@@ -566,7 +615,8 @@ void diffcore_rename(struct diff_options *options)
 
                        this_src.score = estimate_similarity(options->repo,
                                                             one, two,
-                                                            minimum_score);
+                                                            minimum_score,
+                                                            skip_unmodified);
                        this_src.name_score = basename_same(one, two);
                        this_src.dst = i;
                        this_src.src = j;
index 7c07347e42b52c0d010b0c13011536afc0c4d581..d2a63c5c71f4e3667643b1fba5c195e049e313c9 100644 (file)
@@ -65,9 +65,25 @@ void free_filespec(struct diff_filespec *);
 void fill_filespec(struct diff_filespec *, const struct object_id *,
                   int, unsigned short);
 
-#define CHECK_SIZE_ONLY 1
-#define CHECK_BINARY    2
-int diff_populate_filespec(struct repository *, struct diff_filespec *, unsigned int);
+/*
+ * Prefetch the entries in diff_queued_diff. The parameter is a pointer to a
+ * struct repository.
+ */
+void diff_queued_diff_prefetch(void *repository);
+
+struct diff_populate_filespec_options {
+       unsigned check_size_only : 1;
+       unsigned check_binary : 1;
+
+       /*
+        * If an object is missing, diff_populate_filespec() will invoke this
+        * callback before attempting to read that object again.
+        */
+       void (*missing_object_cb)(void *);
+       void *missing_object_data;
+};
+int diff_populate_filespec(struct repository *, struct diff_filespec *,
+                          const struct diff_populate_filespec_options *);
 void diff_free_filespec_data(struct diff_filespec *);
 void diff_free_filespec_blob(struct diff_filespec *);
 int diff_filespec_is_binary(struct repository *, struct diff_filespec *);
@@ -182,4 +198,12 @@ int diffcore_count_changes(struct repository *r,
                           unsigned long *src_copied,
                           unsigned long *literal_added);
 
+/*
+ * If filespec contains an OID and if that object is missing from the given
+ * repository, add that OID to to_fetch.
+ */
+void diff_add_if_missing(struct repository *r,
+                        struct oid_array *to_fetch,
+                        const struct diff_filespec *filespec);
+
 #endif
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..c98970274c4056178b0e9a37e3ba38b323707b50 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)
 
 struct object_entry {
        struct pack_idx_entry idx;
-       struct object_entry *next;
+       struct hashmap_entry ent;
        uint32_t type : TYPE_BITS,
                pack_id : PACK_ID_BITS,
                depth : DEPTH_BITS;
 };
 
+static int object_entry_hashcmp(const void *map_data,
+                               const struct hashmap_entry *eptr,
+                               const struct hashmap_entry *entry_or_key,
+                               const void *keydata)
+{
+       const struct object_id *oid = keydata;
+       const struct object_entry *e1, *e2;
+
+       e1 = container_of(eptr, const struct object_entry, ent);
+       if (oid)
+               return oidcmp(&e1->idx.oid, oid);
+
+       e2 = container_of(entry_or_key, const struct object_entry, ent);
+       return oidcmp(&e1->idx.oid, &e2->idx.oid);
+}
+
 struct object_entry_pool {
        struct object_entry_pool *next_pool;
        struct object_entry *next_free;
@@ -53,6 +70,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 +149,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;
@@ -173,7 +194,7 @@ static off_t pack_size;
 /* Table of objects we've written. */
 static unsigned int object_entry_alloc = 5000;
 static struct object_entry_pool *blocks;
-static struct object_entry *object_table[1 << 16];
+static struct hashmap object_table;
 static struct mark_set *marks;
 static const char *export_marks_file;
 static const char *import_marks_file;
@@ -222,6 +243,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 +256,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 +307,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 +385,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);
@@ -424,44 +471,37 @@ static struct object_entry *new_object(struct object_id *oid)
 
 static struct object_entry *find_object(struct object_id *oid)
 {
-       unsigned int h = oid->hash[0] << 8 | oid->hash[1];
-       struct object_entry *e;
-       for (e = object_table[h]; e; e = e->next)
-               if (oideq(oid, &e->idx.oid))
-                       return e;
-       return NULL;
+       return hashmap_get_entry_from_hash(&object_table, oidhash(oid), oid,
+                                          struct object_entry, ent);
 }
 
 static struct object_entry *insert_object(struct object_id *oid)
 {
-       unsigned int h = oid->hash[0] << 8 | oid->hash[1];
-       struct object_entry *e = object_table[h];
+       struct object_entry *e;
+       unsigned int hash = oidhash(oid);
 
-       while (e) {
-               if (oideq(oid, &e->idx.oid))
-                       return e;
-               e = e->next;
+       e = hashmap_get_entry_from_hash(&object_table, hash, oid,
+                                       struct object_entry, ent);
+       if (!e) {
+               e = new_object(oid);
+               e->idx.offset = 0;
+               hashmap_entry_init(&e->ent, hash);
+               hashmap_add(&object_table, &e->ent);
        }
 
-       e = new_object(oid);
-       e->next = object_table[h];
-       e->idx.offset = 0;
-       object_table[h] = e;
        return e;
 }
 
 static void invalidate_pack_id(unsigned int id)
 {
-       unsigned int h;
        unsigned long lu;
        struct tag *t;
+       struct hashmap_iter iter;
+       struct object_entry *e;
 
-       for (h = 0; h < ARRAY_SIZE(object_table); h++) {
-               struct object_entry *e;
-
-               for (e = object_table[h]; e; e = e->next)
-                       if (e->pack_id == id)
-                               e->pack_id = MAX_PACK_ID;
+       hashmap_for_each_entry(&object_table, &iter, e, ent) {
+               if (e->pack_id == id)
+                       e->pack_id = MAX_PACK_ID;
        }
 
        for (lu = 0; lu < branch_table_sz; lu++) {
@@ -493,9 +533,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 +555,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 +957,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 +1155,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 +1693,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 +1722,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 +1730,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 +1769,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 +2171,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 +2275,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 +2449,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 +2470,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 +2574,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 +2638,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 +2812,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 +2970,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 +2985,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 +3054,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 +3227,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 +3307,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 +3389,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 +3499,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)
@@ -3397,6 +3520,8 @@ int cmd_main(int argc, const char **argv)
        avail_tree_table = xcalloc(avail_tree_table_sz, sizeof(struct avail_tree_content*));
        marks = mem_pool_calloc(&fi_mem_pool, 1, sizeof(struct mark_set));
 
+       hashmap_init(&object_table, object_entry_hashcmp, NULL, 0);
+
        /*
         * We don't parse most options until after we've seen the set of
         * "feature" lines at the start of the stream (which allows the command
index 1734a573b010dd2f87541154cc70e5bb71b00463..0b07b3ee73b8727ce2af65b90b43f2cb10246ea9 100644 (file)
@@ -15,7 +15,7 @@
 #include "connect.h"
 #include "transport.h"
 #include "version.h"
-#include "sha1-array.h"
+#include "oid-array.h"
 #include "oidset.h"
 #include "packfile.h"
 #include "object-store.h"
diff --git a/fmt-merge-msg.c b/fmt-merge-msg.c
new file mode 100644 (file)
index 0000000..08022ed
--- /dev/null
@@ -0,0 +1,656 @@
+#include "config.h"
+#include "refs.h"
+#include "object-store.h"
+#include "diff.h"
+#include "revision.h"
+#include "tag.h"
+#include "string-list.h"
+#include "branch.h"
+#include "fmt-merge-msg.h"
+#include "commit-reach.h"
+
+static int use_branch_desc;
+
+int fmt_merge_msg_config(const char *key, const char *value, void *cb)
+{
+       if (!strcmp(key, "merge.log") || !strcmp(key, "merge.summary")) {
+               int is_bool;
+               merge_log_config = git_config_bool_or_int(key, value, &is_bool);
+               if (!is_bool && merge_log_config < 0)
+                       return error("%s: negative length %s", key, value);
+               if (is_bool && merge_log_config)
+                       merge_log_config = DEFAULT_MERGE_LOG_LEN;
+       } else if (!strcmp(key, "merge.branchdesc")) {
+               use_branch_desc = git_config_bool(key, value);
+       } else {
+               return git_default_config(key, value, cb);
+       }
+       return 0;
+}
+
+/* merge data per repository where the merged tips came from */
+struct src_data {
+       struct string_list branch, tag, r_branch, generic;
+       int head_status;
+};
+
+struct origin_data {
+       struct object_id oid;
+       unsigned is_local_branch:1;
+};
+
+static void init_src_data(struct src_data *data)
+{
+       data->branch.strdup_strings = 1;
+       data->tag.strdup_strings = 1;
+       data->r_branch.strdup_strings = 1;
+       data->generic.strdup_strings = 1;
+}
+
+static struct string_list srcs = STRING_LIST_INIT_DUP;
+static struct string_list origins = STRING_LIST_INIT_DUP;
+
+struct merge_parents {
+       int alloc, nr;
+       struct merge_parent {
+               struct object_id given;
+               struct object_id commit;
+               unsigned char used;
+       } *item;
+};
+
+/*
+ * I know, I know, this is inefficient, but you won't be pulling and merging
+ * hundreds of heads at a time anyway.
+ */
+static struct merge_parent *find_merge_parent(struct merge_parents *table,
+                                             struct object_id *given,
+                                             struct object_id *commit)
+{
+       int i;
+       for (i = 0; i < table->nr; i++) {
+               if (given && !oideq(&table->item[i].given, given))
+                       continue;
+               if (commit && !oideq(&table->item[i].commit, commit))
+                       continue;
+               return &table->item[i];
+       }
+       return NULL;
+}
+
+static void add_merge_parent(struct merge_parents *table,
+                            struct object_id *given,
+                            struct object_id *commit)
+{
+       if (table->nr && find_merge_parent(table, given, commit))
+               return;
+       ALLOC_GROW(table->item, table->nr + 1, table->alloc);
+       oidcpy(&table->item[table->nr].given, given);
+       oidcpy(&table->item[table->nr].commit, commit);
+       table->item[table->nr].used = 0;
+       table->nr++;
+}
+
+static int handle_line(char *line, struct merge_parents *merge_parents)
+{
+       int i, len = strlen(line);
+       struct origin_data *origin_data;
+       char *src;
+       const char *origin, *tag_name;
+       struct src_data *src_data;
+       struct string_list_item *item;
+       int pulling_head = 0;
+       struct object_id oid;
+       const unsigned hexsz = the_hash_algo->hexsz;
+
+       if (len < hexsz + 3 || line[hexsz] != '\t')
+               return 1;
+
+       if (starts_with(line + hexsz + 1, "not-for-merge"))
+               return 0;
+
+       if (line[hexsz + 1] != '\t')
+               return 2;
+
+       i = get_oid_hex(line, &oid);
+       if (i)
+               return 3;
+
+       if (!find_merge_parent(merge_parents, &oid, NULL))
+               return 0; /* subsumed by other parents */
+
+       origin_data = xcalloc(1, sizeof(struct origin_data));
+       oidcpy(&origin_data->oid, &oid);
+
+       if (line[len - 1] == '\n')
+               line[len - 1] = 0;
+       line += hexsz + 2;
+
+       /*
+        * At this point, line points at the beginning of comment e.g.
+        * "branch 'frotz' of git://that/repository.git".
+        * Find the repository name and point it with src.
+        */
+       src = strstr(line, " of ");
+       if (src) {
+               *src = 0;
+               src += 4;
+               pulling_head = 0;
+       } else {
+               src = line;
+               pulling_head = 1;
+       }
+
+       item = unsorted_string_list_lookup(&srcs, src);
+       if (!item) {
+               item = string_list_append(&srcs, src);
+               item->util = xcalloc(1, sizeof(struct src_data));
+               init_src_data(item->util);
+       }
+       src_data = item->util;
+
+       if (pulling_head) {
+               origin = src;
+               src_data->head_status |= 1;
+       } else if (skip_prefix(line, "branch ", &origin)) {
+               origin_data->is_local_branch = 1;
+               string_list_append(&src_data->branch, origin);
+               src_data->head_status |= 2;
+       } else if (skip_prefix(line, "tag ", &tag_name)) {
+               origin = line;
+               string_list_append(&src_data->tag, tag_name);
+               src_data->head_status |= 2;
+       } else if (skip_prefix(line, "remote-tracking branch ", &origin)) {
+               string_list_append(&src_data->r_branch, origin);
+               src_data->head_status |= 2;
+       } else {
+               origin = src;
+               string_list_append(&src_data->generic, line);
+               src_data->head_status |= 2;
+       }
+
+       if (!strcmp(".", src) || !strcmp(src, origin)) {
+               int len = strlen(origin);
+               if (origin[0] == '\'' && origin[len - 1] == '\'')
+                       origin = xmemdupz(origin + 1, len - 2);
+       } else
+               origin = xstrfmt("%s of %s", origin, src);
+       if (strcmp(".", src))
+               origin_data->is_local_branch = 0;
+       string_list_append(&origins, origin)->util = origin_data;
+       return 0;
+}
+
+static void print_joined(const char *singular, const char *plural,
+               struct string_list *list, struct strbuf *out)
+{
+       if (list->nr == 0)
+               return;
+       if (list->nr == 1) {
+               strbuf_addf(out, "%s%s", singular, list->items[0].string);
+       } else {
+               int i;
+               strbuf_addstr(out, plural);
+               for (i = 0; i < list->nr - 1; i++)
+                       strbuf_addf(out, "%s%s", i > 0 ? ", " : "",
+                                   list->items[i].string);
+               strbuf_addf(out, " and %s", list->items[list->nr - 1].string);
+       }
+}
+
+static void add_branch_desc(struct strbuf *out, const char *name)
+{
+       struct strbuf desc = STRBUF_INIT;
+
+       if (!read_branch_desc(&desc, name)) {
+               const char *bp = desc.buf;
+               while (*bp) {
+                       const char *ep = strchrnul(bp, '\n');
+                       if (*ep)
+                               ep++;
+                       strbuf_addf(out, "  : %.*s", (int)(ep - bp), bp);
+                       bp = ep;
+               }
+               strbuf_complete_line(out);
+       }
+       strbuf_release(&desc);
+}
+
+#define util_as_integral(elem) ((intptr_t)((elem)->util))
+
+static void record_person_from_buf(int which, struct string_list *people,
+                                  const char *buffer)
+{
+       char *name_buf, *name, *name_end;
+       struct string_list_item *elem;
+       const char *field;
+
+       field = (which == 'a') ? "\nauthor " : "\ncommitter ";
+       name = strstr(buffer, field);
+       if (!name)
+               return;
+       name += strlen(field);
+       name_end = strchrnul(name, '<');
+       if (*name_end)
+               name_end--;
+       while (isspace(*name_end) && name <= name_end)
+               name_end--;
+       if (name_end < name)
+               return;
+       name_buf = xmemdupz(name, name_end - name + 1);
+
+       elem = string_list_lookup(people, name_buf);
+       if (!elem) {
+               elem = string_list_insert(people, name_buf);
+               elem->util = (void *)0;
+       }
+       elem->util = (void*)(util_as_integral(elem) + 1);
+       free(name_buf);
+}
+
+
+static void record_person(int which, struct string_list *people,
+                         struct commit *commit)
+{
+       const char *buffer = get_commit_buffer(commit, NULL);
+       record_person_from_buf(which, people, buffer);
+       unuse_commit_buffer(commit, buffer);
+}
+
+static int cmp_string_list_util_as_integral(const void *a_, const void *b_)
+{
+       const struct string_list_item *a = a_, *b = b_;
+       return util_as_integral(b) - util_as_integral(a);
+}
+
+static void add_people_count(struct strbuf *out, struct string_list *people)
+{
+       if (people->nr == 1)
+               strbuf_addstr(out, people->items[0].string);
+       else if (people->nr == 2)
+               strbuf_addf(out, "%s (%d) and %s (%d)",
+                           people->items[0].string,
+                           (int)util_as_integral(&people->items[0]),
+                           people->items[1].string,
+                           (int)util_as_integral(&people->items[1]));
+       else if (people->nr)
+               strbuf_addf(out, "%s (%d) and others",
+                           people->items[0].string,
+                           (int)util_as_integral(&people->items[0]));
+}
+
+static void credit_people(struct strbuf *out,
+                         struct string_list *them,
+                         int kind)
+{
+       const char *label;
+       const char *me;
+
+       if (kind == 'a') {
+               label = "By";
+               me = git_author_info(IDENT_NO_DATE);
+       } else {
+               label = "Via";
+               me = git_committer_info(IDENT_NO_DATE);
+       }
+
+       if (!them->nr ||
+           (them->nr == 1 &&
+            me &&
+            skip_prefix(me, them->items->string, &me) &&
+            starts_with(me, " <")))
+               return;
+       strbuf_addf(out, "\n%c %s ", comment_line_char, label);
+       add_people_count(out, them);
+}
+
+static void add_people_info(struct strbuf *out,
+                           struct string_list *authors,
+                           struct string_list *committers)
+{
+       QSORT(authors->items, authors->nr,
+             cmp_string_list_util_as_integral);
+       QSORT(committers->items, committers->nr,
+             cmp_string_list_util_as_integral);
+
+       credit_people(out, authors, 'a');
+       credit_people(out, committers, 'c');
+}
+
+static void shortlog(const char *name,
+                    struct origin_data *origin_data,
+                    struct commit *head,
+                    struct rev_info *rev,
+                    struct fmt_merge_msg_opts *opts,
+                    struct strbuf *out)
+{
+       int i, count = 0;
+       struct commit *commit;
+       struct object *branch;
+       struct string_list subjects = STRING_LIST_INIT_DUP;
+       struct string_list authors = STRING_LIST_INIT_DUP;
+       struct string_list committers = STRING_LIST_INIT_DUP;
+       int flags = UNINTERESTING | TREESAME | SEEN | SHOWN | ADDED;
+       struct strbuf sb = STRBUF_INIT;
+       const struct object_id *oid = &origin_data->oid;
+       int limit = opts->shortlog_len;
+
+       branch = deref_tag(the_repository, parse_object(the_repository, oid),
+                          oid_to_hex(oid),
+                          the_hash_algo->hexsz);
+       if (!branch || branch->type != OBJ_COMMIT)
+               return;
+
+       setup_revisions(0, NULL, rev, NULL);
+       add_pending_object(rev, branch, name);
+       add_pending_object(rev, &head->object, "^HEAD");
+       head->object.flags |= UNINTERESTING;
+       if (prepare_revision_walk(rev))
+               die("revision walk setup failed");
+       while ((commit = get_revision(rev)) != NULL) {
+               struct pretty_print_context ctx = {0};
+
+               if (commit->parents && commit->parents->next) {
+                       /* do not list a merge but count committer */
+                       if (opts->credit_people)
+                               record_person('c', &committers, commit);
+                       continue;
+               }
+               if (!count && opts->credit_people)
+                       /* the 'tip' committer */
+                       record_person('c', &committers, commit);
+               if (opts->credit_people)
+                       record_person('a', &authors, commit);
+               count++;
+               if (subjects.nr > limit)
+                       continue;
+
+               format_commit_message(commit, "%s", &sb, &ctx);
+               strbuf_ltrim(&sb);
+
+               if (!sb.len)
+                       string_list_append(&subjects,
+                                          oid_to_hex(&commit->object.oid));
+               else
+                       string_list_append_nodup(&subjects,
+                                                strbuf_detach(&sb, NULL));
+       }
+
+       if (opts->credit_people)
+               add_people_info(out, &authors, &committers);
+       if (count > limit)
+               strbuf_addf(out, "\n* %s: (%d commits)\n", name, count);
+       else
+               strbuf_addf(out, "\n* %s:\n", name);
+
+       if (origin_data->is_local_branch && use_branch_desc)
+               add_branch_desc(out, name);
+
+       for (i = 0; i < subjects.nr; i++)
+               if (i >= limit)
+                       strbuf_addstr(out, "  ...\n");
+               else
+                       strbuf_addf(out, "  %s\n", subjects.items[i].string);
+
+       clear_commit_marks((struct commit *)branch, flags);
+       clear_commit_marks(head, flags);
+       free_commit_list(rev->commits);
+       rev->commits = NULL;
+       rev->pending.nr = 0;
+
+       string_list_clear(&authors, 0);
+       string_list_clear(&committers, 0);
+       string_list_clear(&subjects, 0);
+}
+
+static void fmt_merge_msg_title(struct strbuf *out,
+                               const char *current_branch)
+{
+       int i = 0;
+       char *sep = "";
+
+       strbuf_addstr(out, "Merge ");
+       for (i = 0; i < srcs.nr; i++) {
+               struct src_data *src_data = srcs.items[i].util;
+               const char *subsep = "";
+
+               strbuf_addstr(out, sep);
+               sep = "; ";
+
+               if (src_data->head_status == 1) {
+                       strbuf_addstr(out, srcs.items[i].string);
+                       continue;
+               }
+               if (src_data->head_status == 3) {
+                       subsep = ", ";
+                       strbuf_addstr(out, "HEAD");
+               }
+               if (src_data->branch.nr) {
+                       strbuf_addstr(out, subsep);
+                       subsep = ", ";
+                       print_joined("branch ", "branches ", &src_data->branch,
+                                       out);
+               }
+               if (src_data->r_branch.nr) {
+                       strbuf_addstr(out, subsep);
+                       subsep = ", ";
+                       print_joined("remote-tracking branch ", "remote-tracking branches ",
+                                       &src_data->r_branch, out);
+               }
+               if (src_data->tag.nr) {
+                       strbuf_addstr(out, subsep);
+                       subsep = ", ";
+                       print_joined("tag ", "tags ", &src_data->tag, out);
+               }
+               if (src_data->generic.nr) {
+                       strbuf_addstr(out, subsep);
+                       print_joined("commit ", "commits ", &src_data->generic,
+                                       out);
+               }
+               if (strcmp(".", srcs.items[i].string))
+                       strbuf_addf(out, " of %s", srcs.items[i].string);
+       }
+
+       if (!strcmp("master", current_branch))
+               strbuf_addch(out, '\n');
+       else
+               strbuf_addf(out, " into %s\n", current_branch);
+}
+
+static void fmt_tag_signature(struct strbuf *tagbuf,
+                             struct strbuf *sig,
+                             const char *buf,
+                             unsigned long len)
+{
+       const char *tag_body = strstr(buf, "\n\n");
+       if (tag_body) {
+               tag_body += 2;
+               strbuf_add(tagbuf, tag_body, buf + len - tag_body);
+       }
+       strbuf_complete_line(tagbuf);
+       if (sig->len) {
+               strbuf_addch(tagbuf, '\n');
+               strbuf_add_commented_lines(tagbuf, sig->buf, sig->len);
+       }
+}
+
+static void fmt_merge_msg_sigs(struct strbuf *out)
+{
+       int i, tag_number = 0, first_tag = 0;
+       struct strbuf tagbuf = STRBUF_INIT;
+
+       for (i = 0; i < origins.nr; i++) {
+               struct object_id *oid = origins.items[i].util;
+               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)
+                       goto next;
+               len = parse_signature(buf, size);
+
+               if (size == len)
+                       ; /* merely annotated */
+               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);
+                       first_tag = i;
+               } else {
+                       if (tag_number == 2) {
+                               struct strbuf tagline = STRBUF_INIT;
+                               strbuf_addch(&tagline, '\n');
+                               strbuf_add_commented_lines(&tagline,
+                                               origins.items[first_tag].string,
+                                               strlen(origins.items[first_tag].string));
+                               strbuf_insert(&tagbuf, 0, tagline.buf,
+                                             tagline.len);
+                               strbuf_release(&tagline);
+                       }
+                       strbuf_addch(&tagbuf, '\n');
+                       strbuf_add_commented_lines(&tagbuf,
+                                       origins.items[i].string,
+                                       strlen(origins.items[i].string));
+                       fmt_tag_signature(&tagbuf, &sig, buf, len);
+               }
+               strbuf_release(&sig);
+       next:
+               free(buf);
+       }
+       if (tagbuf.len) {
+               strbuf_addch(out, '\n');
+               strbuf_addbuf(out, &tagbuf);
+       }
+       strbuf_release(&tagbuf);
+}
+
+static void find_merge_parents(struct merge_parents *result,
+                              struct strbuf *in, struct object_id *head)
+{
+       struct commit_list *parents;
+       struct commit *head_commit;
+       int pos = 0, i, j;
+
+       parents = NULL;
+       while (pos < in->len) {
+               int len;
+               char *p = in->buf + pos;
+               char *newline = strchr(p, '\n');
+               const char *q;
+               struct object_id oid;
+               struct commit *parent;
+               struct object *obj;
+
+               len = newline ? newline - p : strlen(p);
+               pos += len + !!newline;
+
+               if (parse_oid_hex(p, &oid, &q) ||
+                   q[0] != '\t' ||
+                   q[1] != '\t')
+                       continue; /* skip not-for-merge */
+               /*
+                * Do not use get_merge_parent() here; we do not have
+                * "name" here and we do not want to contaminate its
+                * util field yet.
+                */
+               obj = parse_object(the_repository, &oid);
+               parent = (struct commit *)peel_to_type(NULL, 0, obj, OBJ_COMMIT);
+               if (!parent)
+                       continue;
+               commit_list_insert(parent, &parents);
+               add_merge_parent(result, &obj->oid, &parent->object.oid);
+       }
+       head_commit = lookup_commit(the_repository, head);
+       if (head_commit)
+               commit_list_insert(head_commit, &parents);
+       reduce_heads_replace(&parents);
+
+       while (parents) {
+               struct commit *cmit = pop_commit(&parents);
+               for (i = 0; i < result->nr; i++)
+                       if (oideq(&result->item[i].commit, &cmit->object.oid))
+                               result->item[i].used = 1;
+       }
+
+       for (i = j = 0; i < result->nr; i++) {
+               if (result->item[i].used) {
+                       if (i != j)
+                               result->item[j] = result->item[i];
+                       j++;
+               }
+       }
+       result->nr = j;
+}
+
+
+int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
+                 struct fmt_merge_msg_opts *opts)
+{
+       int i = 0, pos = 0;
+       struct object_id head_oid;
+       const char *current_branch;
+       void *current_branch_to_free;
+       struct merge_parents merge_parents;
+
+       memset(&merge_parents, 0, sizeof(merge_parents));
+
+       /* get current branch */
+       current_branch = current_branch_to_free =
+               resolve_refdup("HEAD", RESOLVE_REF_READING, &head_oid, NULL);
+       if (!current_branch)
+               die("No current branch");
+       if (starts_with(current_branch, "refs/heads/"))
+               current_branch += 11;
+
+       find_merge_parents(&merge_parents, in, &head_oid);
+
+       /* get a line */
+       while (pos < in->len) {
+               int len;
+               char *newline, *p = in->buf + pos;
+
+               newline = strchr(p, '\n');
+               len = newline ? newline - p : strlen(p);
+               pos += len + !!newline;
+               i++;
+               p[len] = 0;
+               if (handle_line(p, &merge_parents))
+                       die("error in line %d: %.*s", i, len, p);
+       }
+
+       if (opts->add_title && srcs.nr)
+               fmt_merge_msg_title(out, current_branch);
+
+       if (origins.nr)
+               fmt_merge_msg_sigs(out);
+
+       if (opts->shortlog_len) {
+               struct commit *head;
+               struct rev_info rev;
+
+               head = lookup_commit_or_die(&head_oid, "HEAD");
+               repo_init_revisions(the_repository, &rev, NULL);
+               rev.commit_format = CMIT_FMT_ONELINE;
+               rev.ignore_merges = 1;
+               rev.limited = 1;
+
+               strbuf_complete_line(out);
+
+               for (i = 0; i < origins.nr; i++)
+                       shortlog(origins.items[i].string,
+                                origins.items[i].util,
+                                head, &rev, opts, out);
+       }
+
+       strbuf_complete_line(out);
+       free(current_branch_to_free);
+       free(merge_parents.item);
+       return 0;
+}
index 01e3aa88c50c1994065a99515bd13f484558b553..f2ab0e0085ada6509c02427503b1ea04b18951e0 100644 (file)
@@ -1,7 +1,20 @@
 #ifndef FMT_MERGE_MSG_H
 #define FMT_MERGE_MSG_H
 
+#include "strbuf.h"
+
+#define DEFAULT_MERGE_LOG_LEN 20
+
+struct fmt_merge_msg_opts {
+       unsigned add_title:1,
+               credit_people:1;
+       int shortlog_len;
+};
+
 extern int merge_log_config;
 int fmt_merge_msg_config(const char *key, const char *value, void *cb);
+int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
+                 struct fmt_merge_msg_opts *);
+
 
 #endif /* FMT_MERGE_MSG_H */
diff --git a/fsck.c b/fsck.c
index 640d813d8430c6835ab1068c0c4512560e91c2b8..087a7f1ffc7fa78356c8492a1bf910bf9e65d82d 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;
@@ -920,7 +1065,7 @@ static int fsck_gitmodules_fn(const char *var, const char *value, void *vdata)
 {
        struct fsck_gitmodules_data *data = vdata;
        const char *subsection, *key;
-       int subsection_len;
+       size_t subsection_len;
        char *name;
 
        if (parse_config_key(var, "submodule", &subsection, &subsection_len, &key) < 0 ||
@@ -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,
index aed0b5d4f9028a5ae38e8676ec2d1c3a63dfdc97..8ba576e81e3e52843cc84350978a26df8e3344e7 100644 (file)
@@ -389,6 +389,14 @@ static inline char *git_find_last_dir_sep(const char *path)
 #define find_last_dir_sep git_find_last_dir_sep
 #endif
 
+#ifndef has_dir_sep
+static inline int git_has_dir_sep(const char *path)
+{
+       return !!strchr(path, '/');
+}
+#define has_dir_sep(path) git_has_dir_sep(path)
+#endif
+
 #ifndef query_user_email
 #define query_user_email() NULL
 #endif
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..b8b2a1679e7336e27d794947e9191cd4b5ec1b57 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
@@ -34,37 +34,17 @@ import zipfile
 import zlib
 import ctypes
 import errno
+import glob
 
+# 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 +93,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 +166,118 @@ 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()
+        sys.stderr.flush()
+        sys.stdout.write(prompt_text)
+        sys.stdout.flush()
+        response=sys.stdin.readline().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 run_git_hook(cmd, param=[]):
+    """Execute a hook if the hook exists."""
+    if verbose:
+        sys.stderr.write("Looking for hook: %s\n" % cmd)
+        sys.stderr.flush()
+
+    hooks_path = gitConfig("core.hooksPath")
+    if len(hooks_path) <= 0:
+        hooks_path = os.path.join(os.environ["GIT_DIR"], "hooks")
+
+    if not isinstance(param, list):
+        param=[param]
+
+    # resolve hook file name, OS depdenent
+    hook_file = os.path.join(hooks_path, cmd)
+    if platform.system() == 'Windows':
+        if not os.path.isfile(hook_file):
+            # look for the file with an extension
+            files = glob.glob(hook_file + ".*")
+            if not files:
+                return True
+            files.sort()
+            hook_file = files.pop()
+            while hook_file.upper().endswith(".SAMPLE"):
+                # The file is a sample hook. We don't want it
+                if len(files) > 0:
+                    hook_file = files.pop()
+                else:
+                    return True
+
+    if not os.path.isfile(hook_file) or not os.access(hook_file, os.X_OK):
+        return True
+
+    return run_hook_command(hook_file, param) == 0
+
+def run_hook_command(cmd, param):
+    """Executes a git hook command
+       cmd = the command line file to be executed. This can be
+       a file that is run by OS association.
+
+       param = a list of parameters to pass to the cmd command
+
+       On windows, the extension is checked to see if it should
+       be run with the Git for Windows Bash shell.  If there
+       is no file extension, the file is deemed a bash shell
+       and will be handed off to sh.exe. Otherwise, Windows
+       will be called with the shell to handle the file assocation.
+
+       For non Windows operating systems, the file is called
+       as an executable.
+    """
+    cli = [cmd] + param
+    use_shell = False
+    if platform.system() == 'Windows':
+        (root,ext) = os.path.splitext(cmd)
+        if ext == "":
+            exe_path = os.environ.get("EXEPATH")
+            if exe_path is None:
+                exe_path = ""
+            else:
+                exe_path = os.path.join(exe_path, "bin")
+            cli = [os.path.join(exe_path, "SH.EXE")] + cli
+        else:
+            use_shell = True
+    return subprocess.call(cli, shell=use_shell)
+
+
 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 +289,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 +301,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 +319,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 +331,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 +374,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 +384,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 +396,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 +634,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 +651,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 +672,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 +728,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 +745,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 +763,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 +827,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 +843,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 +995,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 +1273,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 +1380,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 +1516,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:
@@ -1536,13 +1638,39 @@ class P4Submit(Command, P4UserMap):
                                      "work from a local git branch that is not master"),
                 optparse.make_option("--disable-p4sync", dest="disable_p4sync", action="store_true",
                                      help="Skip Perforce sync of p4/master after submit or shelve"),
+                optparse.make_option("--no-verify", dest="no_verify", action="store_true",
+                                     help="Bypass p4-pre-submit and p4-changelist hooks"),
         ]
         self.description = """Submit changes from git to the perforce depot.\n
-    The `p4-pre-submit` hook is executed if it exists and is executable.
-    The hook takes no parameters and nothing from standard input. Exiting with
-    non-zero status from this script prevents `git-p4 submit` from launching.
-
-    One usage scenario is to run unit tests in the hook."""
+    The `p4-pre-submit` hook is executed if it exists and is executable. It
+    can be bypassed with the `--no-verify` command line option. The hook takes
+    no parameters and nothing from standard input. Exiting with a non-zero status
+    from this script prevents `git-p4 submit` from launching.
+
+    One usage scenario is to run unit tests in the hook.
+
+    The `p4-prepare-changelist` hook is executed right after preparing the default
+    changelist message and before the editor is started. It takes one parameter,
+    the name of the file that contains the changelist text. Exiting with a non-zero
+    status from the script will abort the process.
+
+    The purpose of the hook is to edit the message file in place, and it is not
+    supressed by the `--no-verify` option. This hook is called even if
+    `--prepare-p4-only` is set.
+
+    The `p4-changelist` hook is executed after the changelist message has been
+    edited by the user. It can be bypassed with the `--no-verify` option. It
+    takes a single parameter, the name of the file that holds the proposed
+    changelist text. Exiting with a non-zero status causes the command to abort.
+
+    The hook is allowed to edit the changelist file and can be used to normalize
+    the text into some project standard format. It can also be used to refuse the
+    Submit after inspect the message file.
+
+    The `p4-post-changelist` hook is invoked after the submit has successfully
+    occured in P4. It takes no parameters and is meant primarily for notification
+    and cannot affect the outcome of the git p4 submit action.
+    """
 
         self.usage += " [name of git branch to submit into perforce depot]"
         self.origin = ""
@@ -1560,6 +1688,7 @@ class P4Submit(Command, P4UserMap):
         self.exportLabels = False
         self.p4HasMoveCommand = p4_has_move_command()
         self.branch = None
+        self.no_verify = False
 
         if gitConfig('git-p4.largeFileSystem'):
             die("Large file system not supported for git-p4 submit command. Please remove it from config.")
@@ -1698,7 +1827,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 +1892,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']
@@ -1946,6 +2076,9 @@ class P4Submit(Command, P4UserMap):
         applyPatchCmd = patchcmd + "--check --apply -"
         patch_succeeded = True
 
+        if verbose:
+            print("TryPatch: %s" % tryPatchCmd)
+
         if os.system(tryPatchCmd) != 0:
             fixed_rcs_keywords = False
             patch_succeeded = False
@@ -1985,6 +2118,7 @@ class P4Submit(Command, P4UserMap):
                 print("Retrying the patch with RCS keywords cleaned up")
                 if os.system(tryPatchCmd) == 0:
                     patch_succeeded = True
+                    print("Patch succeesed this time with RCS keywords cleaned")
 
         if not patch_succeeded:
             for f in editedFiles:
@@ -2042,58 +2176,76 @@ 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:
-            #
-            # Leave the p4 tree prepared, and the submit template around
-            # and let the user decide what to do next
-            #
-            print()
-            print("P4 workspace prepared for submission.")
-            print("To submit or revert, go to client workspace")
-            print("  " + self.clientPath)
-            print()
-            print("To submit, use \"p4 submit\" to write a new description,")
-            print("or \"p4 submit -i <%s\" to use the one prepared by" \
-                  " \"git p4\"." % fileName)
-            print("You can delete the file \"%s\" when finished." % fileName)
-
-            if self.preserveUser and p4User and not self.p4UserIsMe(p4User):
-                print("To preserve change ownership by user %s, you must\n" \
-                      "do \"p4 change -f <change>\" after submitting and\n" \
-                      "edit the User field.")
-            if pureRenameCopy:
-                print("After submitting, renamed files must be re-synced.")
-                print("Invoke \"p4 sync -f\" on each of these files:")
-                for f in pureRenameCopy:
-                    print("  " + f)
-
-            print()
-            print("To revert the changes, use \"p4 revert ...\", and delete")
-            print("the submit template file \"%s\"" % fileName)
-            if filesToAdd:
-                print("Since the commit adds new files, they must be deleted:")
-                for f in filesToAdd:
-                    print("  " + f)
-            print()
-            return True
-
-        #
-        # Let the user edit the change description, then submit it.
-        #
         submitted = False
 
         try:
+            # Allow the hook to edit the changelist text before presenting it
+            # to the user.
+            if not run_git_hook("p4-prepare-changelist", [fileName]):
+                return False
+
+            if self.prepare_p4_only:
+                #
+                # Leave the p4 tree prepared, and the submit template around
+                # and let the user decide what to do next
+                #
+                submitted = True
+                print("")
+                print("P4 workspace prepared for submission.")
+                print("To submit or revert, go to client workspace")
+                print("  " + self.clientPath)
+                print("")
+                print("To submit, use \"p4 submit\" to write a new description,")
+                print("or \"p4 submit -i <%s\" to use the one prepared by" \
+                      " \"git p4\"." % fileName)
+                print("You can delete the file \"%s\" when finished." % fileName)
+
+                if self.preserveUser and p4User and not self.p4UserIsMe(p4User):
+                    print("To preserve change ownership by user %s, you must\n" \
+                          "do \"p4 change -f <change>\" after submitting and\n" \
+                          "edit the User field.")
+                if pureRenameCopy:
+                    print("After submitting, renamed files must be re-synced.")
+                    print("Invoke \"p4 sync -f\" on each of these files:")
+                    for f in pureRenameCopy:
+                        print("  " + f)
+
+                print("")
+                print("To revert the changes, use \"p4 revert ...\", and delete")
+                print("the submit template file \"%s\"" % fileName)
+                if filesToAdd:
+                    print("Since the commit adds new files, they must be deleted:")
+                    for f in filesToAdd:
+                        print("  " + f)
+                print("")
+                sys.stdout.flush()
+                return True
+
             if self.edit_template(fileName):
+                if not self.no_verify:
+                    if not run_git_hook("p4-changelist", [fileName]):
+                        print("The p4-changelist hook failed.")
+                        sys.stdout.flush()
+                        return False
+
                 # 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")
-                submitTemplate = message[:message.index(separatorLine)]
+                if message.find(separatorLine) != -1:
+                    submitTemplate = message[:message.index(separatorLine)]
+                else:
+                    submitTemplate = message
+
+                if len(submitTemplate.strip()) == 0:
+                    print("Changelist is empty, aborting this changelist.")
+                    sys.stdout.flush()
+                    return False
 
                 if update_shelve:
                     p4_write_pipe(['shelve', '-r', '-i'], submitTemplate)
@@ -2116,20 +2268,23 @@ class P4Submit(Command, P4UserMap):
 
                 submitted = True
 
+                run_git_hook("p4-post-changelist")
         finally:
-            # skip this patch
+            # Revert changes if we skip this patch
             if not submitted or self.shelve:
                 if self.shelve:
                     print ("Reverting shelved files.")
                 else:
                     print ("Submission cancelled, undoing p4 changes.")
+                sys.stdout.flush()
                 for f in editedFiles | filesToDelete:
                     p4_revert(f)
                 for f in filesToAdd:
                     p4_revert(f)
                     os.remove(f)
 
-        os.remove(fileName)
+            if not self.prepare_p4_only:
+                os.remove(fileName)
         return submitted
 
     # Export git tags as p4 labels. Create a p4 label and then tag
@@ -2353,13 +2508,17 @@ class P4Submit(Command, P4UserMap):
             sys.exit("number of commits (%d) must match number of shelved changelist (%d)" %
                      (len(commits), num_shelves))
 
-        hooks_path = gitConfig("core.hooksPath")
-        if len(hooks_path) <= 0:
-            hooks_path = os.path.join(os.environ.get("GIT_DIR", ".git"), "hooks")
-
-        hook_file = os.path.join(hooks_path, "p4-pre-submit")
-        if os.path.isfile(hook_file) and os.access(hook_file, os.X_OK) and subprocess.call([hook_file]) != 0:
-            sys.exit(1)
+        if not self.no_verify:
+            try:
+                if not run_git_hook("p4-pre-submit"):
+                    print("\nThe p4-pre-submit hook failed, aborting the submit.\n\nYou can skip " \
+                        "this pre-submission check by adding\nthe command line option '--no-verify', " \
+                        "however,\nthis will also skip the p4-changelist hook as well.")
+                    sys.exit(1)
+            except Exception as e:
+                print("\nThe p4-pre-submit hook failed, aborting the submit.\n\nThe hook failed "\
+                    "with the error '{0}'".format(e.message) )
+                sys.exit(1)
 
         #
         # Apply the commits, one at a time.  On failure, ask if should
@@ -2509,7 +2668,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 +2677,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 +2692,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 +2808,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 +2822,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 +2831,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 +2865,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 +2899,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 +2916,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 +2932,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 +2954,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 +2974,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 +2999,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 +3007,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 +3028,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 +3038,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 +3142,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 +3225,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 +3798,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
@@ -4160,7 +4332,6 @@ commands = {
     "unshelve" : P4Unshelve,
 }
 
-
 def main():
     if len(sys.argv[1:]) == 0:
         printUsage(commands.keys())
index 89f915cae99b12d134aeb7e72ec460dfe1e8e247..08e0439df0a2cccf62274c9636e8eeb609827d2f 100755 (executable)
@@ -48,6 +48,8 @@ depth=
 progress=
 dissociate=
 single_branch=
+jobs=
+recommend_shallow=
 
 die_if_unmatched ()
 {
diff --git a/git.c b/git.c
index 7be7ad34bd053884ec48923706e70c81719a8660..2e4efb4ff08f16cd6040350bf2720e0375adab96 100644 (file)
--- a/git.c
+++ b/git.c
@@ -351,6 +351,7 @@ static int handle_alias(int *argcp, const char ***argv)
 
                        trace2_cmd_alias(alias_command, child.args.argv);
                        trace2_cmd_list_config();
+                       trace2_cmd_list_env_vars();
                        trace2_cmd_name("_run_shell_alias_");
 
                        ret = run_command(&child);
@@ -388,6 +389,7 @@ static int handle_alias(int *argcp, const char ***argv)
 
                trace2_cmd_alias(alias_command, new_argv);
                trace2_cmd_list_config();
+               trace2_cmd_list_env_vars();
 
                *argv = new_argv;
                *argcp += count - 1;
@@ -439,6 +441,7 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv)
        trace_argv_printf(argv, "trace: built-in: git");
        trace2_cmd_name(p->cmd);
        trace2_cmd_list_config();
+       trace2_cmd_list_env_vars();
 
        validate_cache_entries(the_repository->index);
        status = p->fn(argc, argv, prefix);
@@ -574,12 +577,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 65a3a9e62e809b1a74722536e7b2adc8389c595e..1a02a1242d5be347b9dd70d5366f3bf077732366 100755 (executable)
@@ -1291,9 +1291,23 @@ our $is_last_request = sub { 1 };
 our ($pre_dispatch_hook, $post_dispatch_hook, $pre_listen_hook);
 our $CGI = 'CGI';
 our $cgi;
+our $FCGI_Stream_PRINT_raw = \&FCGI::Stream::PRINT;
 sub configure_as_fcgi {
        require CGI::Fast;
        our $CGI = 'CGI::Fast';
+       # FCGI is not Unicode aware hence the UTF-8 encoding must be done manually.
+       # However no encoding must be done within git_blob_plain() and git_snapshot()
+       # which must still output in raw binary mode.
+       no warnings 'redefine';
+       my $enc = Encode::find_encoding('UTF-8');
+       *FCGI::Stream::PRINT = sub {
+               my @OUTPUT = @_;
+               for (my $i = 1; $i < @_; $i++) {
+                       $OUTPUT[$i] = $enc->encode($_[$i], Encode::FB_CROAK|Encode::LEAVE_SRC);
+               }
+               @_ = @OUTPUT;
+               goto $FCGI_Stream_PRINT_raw;
+       };
 
        my $request_number = 0;
        # let each child service 100 requests
@@ -7079,6 +7093,7 @@ sub git_blob_plain {
                        ($sandbox ? 'attachment' : 'inline')
                        . '; filename="' . $save_as . '"');
        local $/ = undef;
+       local *FCGI::Stream::PRINT = $FCGI_Stream_PRINT_raw;
        binmode STDOUT, ':raw';
        print <$fd>;
        binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi
@@ -7417,6 +7432,7 @@ sub git_snapshot {
 
        open my $fd, "-|", $cmd
                or die_error(500, "Execute git-archive failed");
+       local *FCGI::Stream::PRINT = $FCGI_Stream_PRINT_raw;
        binmode STDOUT, ':raw';
        print <$fd>;
        binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi
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 9010e00950b379501a6607d660497e94f8412d95..40e1738dbb3817aaf59df68a7c967c43548f4f2f 100644 (file)
@@ -519,7 +519,7 @@ static void fill_line_ends(struct repository *r,
        unsigned long *ends = NULL;
        char *data = NULL;
 
-       if (diff_populate_filespec(r, spec, 0))
+       if (diff_populate_filespec(r, spec, NULL))
                die("Cannot read blob %s", oid_to_hex(&spec->oid));
 
        ALLOC_ARRAY(ends, size);
@@ -1045,12 +1045,12 @@ static int process_diff_filepair(struct rev_info *rev,
                return 0;
 
        assert(pair->two->oid_valid);
-       diff_populate_filespec(rev->diffopt.repo, pair->two, 0);
+       diff_populate_filespec(rev->diffopt.repo, pair->two, NULL);
        file_target.ptr = pair->two->data;
        file_target.size = pair->two->size;
 
        if (pair->one->oid_valid) {
-               diff_populate_filespec(rev->diffopt.repo, pair->one, 0);
+               diff_populate_filespec(rev->diffopt.repo, pair->one, NULL);
                file_parent.ptr = pair->one->data;
                file_parent.size = pair->one->size;
        } else {
index d65a8971db732782822feede96e02f86d336e547..1ec0b959e015b20442aaddc417952f7a7a731d42 100644 (file)
@@ -247,7 +247,7 @@ static int read_merge_config(const char *var, const char *value, void *cb)
 {
        struct ll_merge_driver *fn;
        const char *key, *name;
-       int namelen;
+       size_t namelen;
 
        if (!strcmp(var, "merge.default"))
                return git_config_string(&default_ll_merge, var, value);
index 52127427ffeace62621a3761dc540a174671f4a5..0064788b252cadb98b71eec4d3517f92c88bba86 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 */
        }
 
@@ -693,6 +693,7 @@ void show_log(struct rev_info *opt)
        ctx.abbrev = opt->diffopt.abbrev;
        ctx.after_subject = extra_headers;
        ctx.preserve_subject = opt->preserve_subject;
+       ctx.encode_email_headers = opt->encode_email_headers;
        ctx.reflog_info = opt->reflog_info;
        ctx.fmt = opt->commit_format;
        ctx.mailmap = opt->mailmap;
index 818aef70a09e7cedc2f241f235302e69969b2e3b..50d86866c6eac5957951e5b685d7912fcf12eed7 100644 (file)
--- a/ls-refs.c
+++ b/ls-refs.c
@@ -93,7 +93,7 @@ int ls_refs(struct repository *r, struct argv_array *keys,
 
        git_config(ls_refs_config, NULL);
 
-       while (packet_reader_read(request) != PACKET_READ_FLUSH) {
+       while (packet_reader_read(request) == PACKET_READ_NORMAL) {
                const char *arg = request->line;
                const char *out;
 
@@ -105,6 +105,9 @@ int ls_refs(struct repository *r, struct argv_array *keys,
                        argv_array_push(&data.prefixes, out);
        }
 
+       if (request->status != PACKET_READ_FLUSH)
+               die(_("expected flush after ls-refs arguments"));
+
        head_ref_namespaced(send_ref, &data);
        for_each_namespaced_ref(send_ref, &data);
        packet_flush(1);
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)) {
diff --git a/midx.c b/midx.c
index 1527e464a7b43e352f352dfc6c6f2b011e35986d..a520e263956086a988b77d6e22a7262c9aeba9b8 100644 (file)
--- a/midx.c
+++ b/midx.c
@@ -923,6 +923,12 @@ static int write_midx_internal(const char *object_dir, struct multi_pack_index *
        cur_chunk = 0;
        num_chunks = large_offsets_needed ? 5 : 4;
 
+       if (packs.nr - dropped_packs == 0) {
+               error(_("no pack files to index."));
+               result = 1;
+               goto cleanup;
+       }
+
        written = write_midx_header(f, num_chunks, packs.nr - dropped_packs);
 
        chunk_ids[cur_chunk] = MIDX_CHUNKID_PACKNAMES;
@@ -1124,6 +1130,15 @@ int verify_midx_file(struct repository *r, const char *object_dir, unsigned flag
                                    i, oid_fanout1, oid_fanout2, i + 1);
        }
 
+       if (m->num_objects == 0) {
+               midx_report(_("the midx contains no oid"));
+               /*
+                * Remaining tests assume that we have objects, so we can
+                * return here.
+                */
+               return verify_midx_error;
+       }
+
        if (flags & MIDX_PROGRESS)
                progress = start_sparse_progress(_("Verifying OID order in multi-pack-index"),
                                                 m->num_objects - 1);
index be72fee7d557758a9e0efbaa13efeb2b6fb84276..d1e490f2035d2f48583a30eb7a44cc4613a662fd 100644 (file)
@@ -4,7 +4,7 @@
 #include "cache.h"
 #include "oidmap.h"
 #include "list.h"
-#include "sha1-array.h"
+#include "oid-array.h"
 #include "strbuf.h"
 #include "thread-utils.h"
 
index 2dbabfca0ab8185f52cca6d3b7c69658e838ae49..b22328b8383ef026df6ad05decb62c9af60ec6d5 100644 (file)
--- a/object.h
+++ b/object.h
@@ -59,7 +59,7 @@ struct object_array {
 
 /*
  * object flag allocation:
- * revision.h:               0---------10                              25----28
+ * revision.h:               0---------10         15                   25----28
  * fetch-pack.c:             01
  * negotiator/default.c:       2--5
  * walker.c:                 0-2
similarity index 93%
rename from sha1-array.c
rename to oid-array.c
index 3eeadfede94b94ed017b9196ebf42041d1e30c74..8657a5cedfa68cbc3711d4b288e514978ff5b6df 100644 (file)
@@ -1,5 +1,5 @@
 #include "cache.h"
-#include "sha1-array.h"
+#include "oid-array.h"
 #include "sha1-lookup.h"
 
 void oid_array_append(struct oid_array *array, const struct object_id *oid)
@@ -46,9 +46,9 @@ int oid_array_for_each(struct oid_array *array,
                       for_each_oid_fn fn,
                       void *data)
 {
-       int i;
+       size_t i;
 
-       /* No oid_array_sort() here! See sha1-array.h */
+       /* No oid_array_sort() here! See oid-array.h */
 
        for (i = 0; i < array->nr; i++) {
                int ret = fn(array->oid + i, data);
@@ -62,7 +62,7 @@ int oid_array_for_each_unique(struct oid_array *array,
                              for_each_oid_fn fn,
                              void *data)
 {
-       int i;
+       size_t i;
 
        if (!array->sorted)
                oid_array_sort(array);
@@ -82,7 +82,7 @@ void oid_array_filter(struct oid_array *array,
                      for_each_oid_fn want,
                      void *cb_data)
 {
-       unsigned nr = array->nr, src, dst;
+       size_t nr = array->nr, src, dst;
        struct object_id *oids = array->oid;
 
        for (src = dst = 0; src < nr; src++) {
similarity index 97%
rename from sha1-array.h
rename to oid-array.h
index dc1bca9c9aea6cdbf001b8d43d2e43b99c034d81..f28d322c9006553c1335b836cea9252f80bb1e96 100644 (file)
@@ -19,7 +19,7 @@
  *
  * void some_func(void)
  * {
- *     struct sha1_array hashes = OID_ARRAY_INIT;
+ *     struct oid_array hashes = OID_ARRAY_INIT;
  *     struct object_id oid;
  *
  *     // Read objects into our set
@@ -49,8 +49,8 @@
  */
 struct oid_array {
        struct object_id *oid;
-       int nr;
-       int alloc;
+       size_t nr;
+       size_t alloc;
        int sorted;
 };
 
index 5346563b0bccb602b8de745d1308793e8995a5e5..3a2d9d1115a45e47a93fa3950ad91a0eb1af4c7c 100644 (file)
--- a/oidset.h
+++ b/oidset.h
@@ -1,11 +1,10 @@
 #ifndef OIDSET_H
 #define OIDSET_H
 
-#include "hashmap.h"
 #include "khash.h"
 
 /**
- * This API is similar to sha1-array, in that it maintains a set of object ids
+ * This API is similar to oid-array, in that it maintains a set of object ids
  * in a memory-efficient way. The major differences are:
  *
  *   1. It uses a hash, so we can do online duplicate removal, rather than
index a28b55be48dc84582f18df446b1eb36f26b17167..86cd3930136e269ca68759b0113875e0a8e80e8a 100644 (file)
@@ -5,7 +5,7 @@
 #include "color.h"
 #include "string-list.h"
 #include "argv-array.h"
-#include "sha1-array.h"
+#include "oid-array.h"
 
 /*----- some often used options -----*/
 
index 63d6bab60c0ef7ea4a836c6c8dc3f70daf8516d4..c57618d53736c754cee32b341f7c97d881c3036c 100644 (file)
@@ -648,6 +648,7 @@ static struct option *preprocess_options(struct parse_opt_ctx_t *ctx,
                int short_name;
                const char *long_name;
                const char *source;
+               struct strbuf help = STRBUF_INIT;
                int j;
 
                if (newopt[i].type != OPTION_ALIAS)
@@ -659,6 +660,7 @@ static struct option *preprocess_options(struct parse_opt_ctx_t *ctx,
 
                if (!long_name)
                        BUG("An alias must have long option name");
+               strbuf_addf(&help, _("alias of --%s"), source);
 
                for (j = 0; j < nr; j++) {
                        const char *name = options[j].long_name;
@@ -669,15 +671,10 @@ static struct option *preprocess_options(struct parse_opt_ctx_t *ctx,
                        if (options[j].type == OPTION_ALIAS)
                                BUG("No please. Nested aliases are not supported.");
 
-                       /*
-                        * NEEDSWORK: this is a bit inconsistent because
-                        * usage_with_options() on the original options[] will print
-                        * help string as "alias of %s" but "git cmd -h" will
-                        * print the original help string.
-                        */
                        memcpy(newopt + i, options + j, sizeof(*newopt));
                        newopt[i].short_name = short_name;
                        newopt[i].long_name = long_name;
+                       newopt[i].help = strbuf_detach(&help, NULL);
                        break;
                }
 
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 28afc701b6a46d1aca84d7867549391d79a6ab68..2a3d46bf42fea1d375eb1b29e52ee1caaa0372a0 100644 (file)
--- a/pretty.c
+++ b/pretty.c
@@ -474,7 +474,8 @@ void pp_user_info(struct pretty_print_context *pp,
                }
 
                strbuf_addstr(sb, "From: ");
-               if (needs_rfc2047_encoding(namebuf, namelen)) {
+               if (pp->encode_email_headers &&
+                   needs_rfc2047_encoding(namebuf, namelen)) {
                        add_rfc2047(sb, namebuf, namelen,
                                    encoding, RFC2047_ADDRESS);
                        max_length = 76; /* per rfc2047 */
@@ -1767,7 +1768,8 @@ void pp_title_line(struct pretty_print_context *pp,
        if (pp->print_email_subject) {
                if (pp->rev)
                        fmt_output_email_subject(sb, pp->rev);
-               if (needs_rfc2047_encoding(title.buf, title.len))
+               if (pp->encode_email_headers &&
+                   needs_rfc2047_encoding(title.buf, title.len))
                        add_rfc2047(sb, title.buf, title.len,
                                                encoding, RFC2047_SUBJECT);
                else
index 4ad1fc31ff33408881abf2ea5e28ab813d6c1f85..071f2fb8e449cee51c6556998b416c4fa7c8005a 100644 (file)
--- a/pretty.h
+++ b/pretty.h
@@ -43,6 +43,7 @@ struct pretty_print_context {
        struct string_list *mailmap;
        int color;
        struct ident_split *from_ident;
+       unsigned encode_email_headers:1;
 
        /*
         * Fields below here are manipulated internally by pp_* functions and
index 9f338c945faf997a11d2a55065ddbc14d8be6cdc..baaea12fd69b3b2c86f9fe5308a06c65e2eff3eb 100644 (file)
@@ -101,7 +101,7 @@ static void promisor_remote_move_to_tail(struct promisor_remote *r,
 static int promisor_remote_config(const char *var, const char *value, void *data)
 {
        const char *name;
-       int namelen;
+       size_t namelen;
        const char *subkey;
 
        if (!strcmp(var, "core.partialclonefilter"))
@@ -241,6 +241,9 @@ int promisor_remote_get_direct(struct repository *repo,
        int to_free = 0;
        int res = -1;
 
+       if (oid_nr == 0)
+               return 0;
+
        promisor_remote_init();
 
        for (r = promisors; r; r = r->next) {
index 737bac3a330e22fe11058d5667cdb3f199c0484a..6343c47d1830f947aae456ed48e305faef26b6c0 100644 (file)
@@ -20,6 +20,14 @@ struct promisor_remote {
 void promisor_remote_reinit(void);
 struct promisor_remote *promisor_remote_find(const char *remote_name);
 int has_promisor_remote(void);
+
+/*
+ * Fetches all requested objects from all promisor remotes, trying them one at
+ * a time until all objects are fetched. Returns 0 upon success, and non-zero
+ * otherwise.
+ *
+ * If oid_nr is 0, this function returns 0 (success) immediately.
+ */
 int promisor_remote_get_direct(struct repository *repo,
                               const struct object_id *oids,
                               int oid_nr);
index 6d5885d0096407d7d12f4511100ebee5cb5f752d..5ded21a017f1089c5a5f63c998c3b11631c0a5d6 100644 (file)
--- a/prompt.c
+++ b/prompt.c
@@ -74,3 +74,15 @@ char *git_prompt(const char *prompt, int flags)
        }
        return r;
 }
+
+int git_read_line_interactively(struct strbuf *line)
+{
+       int ret;
+
+       fflush(stdout);
+       ret = strbuf_getline_lf(line, stdin);
+       if (ret != EOF)
+               strbuf_trim_trailing_newline(line);
+
+       return ret;
+}
index e04cced030ca4df6d729c2132ca4671b959952b4..e294e93541cfa0749cfb60fdceac2ebd3f41751d 100644 (file)
--- a/prompt.h
+++ b/prompt.h
@@ -6,4 +6,6 @@
 
 char *git_prompt(const char *prompt, int flags);
 
+int git_read_line_interactively(struct strbuf *line);
+
 #endif /* PROMPT_H */
diff --git a/prune-packed.c b/prune-packed.c
new file mode 100644 (file)
index 0000000..261520b
--- /dev/null
@@ -0,0 +1,43 @@
+#include "object-store.h"
+#include "packfile.h"
+#include "progress.h"
+#include "prune-packed.h"
+
+static struct progress *progress;
+
+static int prune_subdir(unsigned int nr, const char *path, void *data)
+{
+       int *opts = data;
+       display_progress(progress, nr + 1);
+       if (!(*opts & PRUNE_PACKED_DRY_RUN))
+               rmdir(path);
+       return 0;
+}
+
+static int prune_object(const struct object_id *oid, const char *path,
+                        void *data)
+{
+       int *opts = data;
+
+       if (!has_object_pack(oid))
+               return 0;
+
+       if (*opts & PRUNE_PACKED_DRY_RUN)
+               printf("rm -f %s\n", path);
+       else
+               unlink_or_warn(path);
+       return 0;
+}
+
+void prune_packed_objects(int opts)
+{
+       if (opts & PRUNE_PACKED_VERBOSE)
+               progress = start_delayed_progress(_("Removing duplicate objects"), 256);
+
+       for_each_loose_file_in_objdir(get_object_directory(),
+                                     prune_object, NULL, prune_subdir, &opts);
+
+       /* Ensure we show 100% before finishing progress */
+       display_progress(progress, 256);
+       stop_progress(&progress);
+}
diff --git a/prune-packed.h b/prune-packed.h
new file mode 100644 (file)
index 0000000..936fa9d
--- /dev/null
@@ -0,0 +1,9 @@
+#ifndef PRUNE_PACKED_H
+#define PRUNE_PACKED_H
+
+#define PRUNE_PACKED_DRY_RUN 01
+#define PRUNE_PACKED_VERBOSE 02
+
+void prune_packed_objects(int);
+
+#endif
index b1812cb69a173c983bf434bcab0522bb96d03dd0..35776838f4540d793132989a7c5e8dded3848e04 100644 (file)
@@ -1976,10 +1976,9 @@ static int for_each_fullref_in_pattern(struct ref_filter *filter,
 }
 
 /*
- * Given a ref (sha1, refname), check if the ref belongs to the array
- * of sha1s. If the given ref is a tag, check if the given tag points
- * at one of the sha1s in the given sha1 array.
- * the given sha1_array.
+ * Given a ref (oid, refname), check if the ref belongs to the array
+ * of oids. If the given ref is a tag, check if the given tag points
+ * at one of the oids in the given oid array.
  * NEEDSWORK:
  * 1. Only a single level of inderection is obtained, we might want to
  * change this to account for multiple levels (e.g. annotated tags
index f1dcff4c6e23e831eff9915b659da062a835e8c8..64330e9601a6bf88b52f01ac3b9db88df8321d1b 100644 (file)
@@ -1,7 +1,7 @@
 #ifndef REF_FILTER_H
 #define REF_FILTER_H
 
-#include "sha1-array.h"
+#include "oid-array.h"
 #include "refs.h"
 #include "commit.h"
 #include "parse-options.h"
diff --git a/refs.c b/refs.c
index 1ab0bb54d3d73bfe17f0eab4cd02d760bb9a7d01..b8759116cd0906632249170bdae8623371b05ffc 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -1852,14 +1852,14 @@ static struct ref_store *ref_store_init(const char *gitdir,
 
 struct ref_store *get_main_ref_store(struct repository *r)
 {
-       if (r->refs)
-               return r->refs;
+       if (r->refs_private)
+               return r->refs_private;
 
        if (!r->gitdir)
                BUG("attempting to get main_ref_store outside of repository");
 
-       r->refs = ref_store_init(r->gitdir, REF_STORE_ALL_CAPS);
-       return r->refs;
+       r->refs_private = ref_store_init(r->gitdir, REF_STORE_ALL_CAPS);
+       return r->refs_private;
 }
 
 /*
index e4cd3218447ff385d31d621eb70f67f295f3f92a..1c9aa3d0ab978c1f98f1b4ff45143d765fcf6c27 100644 (file)
@@ -12,7 +12,7 @@
 #include "sideband.h"
 #include "argv-array.h"
 #include "credential.h"
-#include "sha1-array.h"
+#include "oid-array.h"
 #include "send-pack.h"
 #include "protocol.h"
 #include "quote.h"
index c43196ec06c50beb1b7c93e180a62c031d67c10e..534c6426f1e653e6fbb81bd7256a6b3f9d2dfb19 100644 (file)
--- a/remote.c
+++ b/remote.c
@@ -174,54 +174,43 @@ static void add_merge(struct branch *branch, const char *name)
        branch->merge_name[branch->merge_nr++] = name;
 }
 
-static struct branch *make_branch(const char *name, int len)
+static struct branch *make_branch(const char *name, size_t len)
 {
        struct branch *ret;
        int i;
 
        for (i = 0; i < branches_nr; i++) {
-               if (len ? (!strncmp(name, branches[i]->name, len) &&
-                          !branches[i]->name[len]) :
-                   !strcmp(name, branches[i]->name))
+               if (!strncmp(name, branches[i]->name, len) &&
+                   !branches[i]->name[len])
                        return branches[i];
        }
 
        ALLOC_GROW(branches, branches_nr + 1, branches_alloc);
        ret = xcalloc(1, sizeof(struct branch));
        branches[branches_nr++] = ret;
-       if (len)
-               ret->name = xstrndup(name, len);
-       else
-               ret->name = xstrdup(name);
+       ret->name = xstrndup(name, len);
        ret->refname = xstrfmt("refs/heads/%s", ret->name);
 
        return ret;
 }
 
-static struct rewrite *make_rewrite(struct rewrites *r, const char *base, int len)
+static struct rewrite *make_rewrite(struct rewrites *r,
+                                   const char *base, size_t len)
 {
        struct rewrite *ret;
        int i;
 
        for (i = 0; i < r->rewrite_nr; i++) {
-               if (len
-                   ? (len == r->rewrite[i]->baselen &&
-                      !strncmp(base, r->rewrite[i]->base, len))
-                   : !strcmp(base, r->rewrite[i]->base))
+               if (len == r->rewrite[i]->baselen &&
+                   !strncmp(base, r->rewrite[i]->base, len))
                        return r->rewrite[i];
        }
 
        ALLOC_GROW(r->rewrite, r->rewrite_nr + 1, r->rewrite_alloc);
        ret = xcalloc(1, sizeof(struct rewrite));
        r->rewrite[r->rewrite_nr++] = ret;
-       if (len) {
-               ret->base = xstrndup(base, len);
-               ret->baselen = len;
-       }
-       else {
-               ret->base = xstrdup(base);
-               ret->baselen = strlen(base);
-       }
+       ret->base = xstrndup(base, len);
+       ret->baselen = len;
        return ret;
 }
 
@@ -316,7 +305,7 @@ static void read_branches_file(struct remote *remote)
 static int handle_config(const char *key, const char *value, void *cb)
 {
        const char *name;
-       int namelen;
+       size_t namelen;
        const char *subkey;
        struct remote *remote;
        struct branch *branch;
@@ -470,7 +459,7 @@ static void read_config(void)
                const char *head_ref = resolve_ref_unsafe("HEAD", 0, NULL, &flag);
                if (head_ref && (flag & REF_ISSYMREF) &&
                    skip_prefix(head_ref, "refs/heads/", &head_ref)) {
-                       current_branch = make_branch(head_ref, 0);
+                       current_branch = make_branch(head_ref, strlen(head_ref));
                }
        }
        git_config(handle_config, NULL);
@@ -1584,7 +1573,7 @@ struct branch *branch_get(const char *name)
        if (!name || !*name || !strcmp(name, "HEAD"))
                ret = current_branch;
        else
-               ret = make_branch(name, 0);
+               ret = make_branch(name, strlen(name));
        set_merge(ret);
        return ret;
 }
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 040057dea6f4f32bf11d429b3ad10a37d4b10520..6534fbb7b31301aa514c6cfad4f55b03289228b5 100644 (file)
@@ -67,8 +67,12 @@ struct repository {
         */
        struct parsed_object_pool *parsed_objects;
 
-       /* The store in which the refs are held. */
-       struct ref_store *refs;
+       /*
+        * The store in which the refs are held. This should generally only be
+        * accessed via get_main_ref_store(), as that will lazily initialize
+        * the ref object.
+        */
+       struct ref_store *refs_private;
 
        /*
         * Contains path to often used file names.
index 8136929e23626e3bd9a8e9c6108fd2e32b2bd1cb..5bc96444b66813c817c3260151462ee678efcd45 100644 (file)
@@ -870,7 +870,19 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
                        }
                        parent->next = NULL;
                        commit->parents = parent;
-                       commit->object.flags |= TREESAME;
+
+                       /*
+                        * A merge commit is a "diversion" if it is not
+                        * TREESAME to its first parent but is TREESAME
+                        * to a later parent. In the simplified history,
+                        * we "divert" the history walk to the later
+                        * parent. These commits are shown when "show_pulls"
+                        * is enabled, so do not mark the object as
+                        * TREESAME here.
+                        */
+                       if (!revs->show_pulls || !nth_parent)
+                               commit->object.flags |= TREESAME;
+
                        return;
 
                case REV_TREE_NEW:
@@ -897,6 +909,10 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
                                relevant_change = 1;
                        else
                                irrelevant_change = 1;
+
+                       if (!nth_parent)
+                               commit->object.flags |= PULL_MERGE;
+
                        continue;
                }
                die("bad tree compare for commit %s", oid_to_hex(&commit->object.oid));
@@ -2241,6 +2257,10 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
                revs->topo_order = 1;
                revs->rewrite_parents = 1;
                revs->graph = graph_init(revs);
+       } else if (!strcmp(arg, "--encode-email-headers")) {
+               revs->encode_email_headers = 1;
+       } else if (!strcmp(arg, "--no-encode-email-headers")) {
+               revs->encode_email_headers = 0;
        } else if (!strcmp(arg, "--root")) {
                revs->show_root_diff = 1;
        } else if (!strcmp(arg, "--no-commit-id")) {
@@ -2265,6 +2285,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
        } else if (!strcmp(arg, "--full-diff")) {
                revs->diff = 1;
                revs->full_diff = 1;
+       } else if (!strcmp(arg, "--show-pulls")) {
+               revs->show_pulls = 1;
        } else if (!strcmp(arg, "--full-history")) {
                revs->simplify_history = 0;
        } else if (!strcmp(arg, "--relative-date")) {
@@ -3019,7 +3041,8 @@ static struct commit_list **simplify_one(struct rev_info *revs, struct commit *c
        if (!cnt ||
            (commit->object.flags & UNINTERESTING) ||
            !(commit->object.flags & TREESAME) ||
-           (parent = one_relevant_parent(revs, commit->parents)) == NULL)
+           (parent = one_relevant_parent(revs, commit->parents)) == NULL ||
+           (revs->show_pulls && (commit->object.flags & PULL_MERGE)))
                st->simplified = commit;
        else {
                pst = locate_simplify_state(revs, parent);
@@ -3602,6 +3625,10 @@ enum commit_action get_commit_action(struct rev_info *revs, struct commit *commi
                        /* drop merges unless we want parenthood */
                        if (!want_ancestry(revs))
                                return commit_ignore;
+
+                       if (revs->show_pulls && (commit->object.flags & PULL_MERGE))
+                               return commit_show;
+
                        /*
                         * If we want ancestry, then need to keep any merges
                         * between relevant commits to tie together topology.
index 475f048fb61185519b0010ee132d42b1b2c768f7..c1af164b30e6ce823635d6b382e7dac47f7e7a41 100644 (file)
@@ -34,6 +34,9 @@
 #define SYMMETRIC_LEFT (1u<<8)
 #define PATCHSAME      (1u<<9)
 #define BOTTOM         (1u<<10)
+
+/* WARNING: This is also used as REACHABLE in commit-graph.c. */
+#define PULL_MERGE     (1u<<15)
 /*
  * Indicates object was reached by traversal. i.e. not given by user on
  * command-line or stdin.
@@ -43,7 +46,7 @@
  */
 #define NOT_USER_GIVEN (1u<<25)
 #define TRACK_LINEAR   (1u<<26)
-#define ALL_REV_FLAGS  (((1u<<11)-1) | NOT_USER_GIVEN | TRACK_LINEAR)
+#define ALL_REV_FLAGS  (((1u<<11)-1) | NOT_USER_GIVEN | TRACK_LINEAR | PULL_MERGE)
 
 #define TOPO_WALK_EXPLORED     (1u<<27)
 #define TOPO_WALK_INDEGREE     (1u<<28)
@@ -129,6 +132,7 @@ struct rev_info {
                        no_walk:2,
                        remove_empty_trees:1,
                        simplify_history:1,
+                       show_pulls:1,
                        topo_order:1,
                        simplify_merges:1,
                        simplify_by_decoration:1,
@@ -203,7 +207,8 @@ struct rev_info {
                        use_terminator:1,
                        missing_newline:1,
                        date_mode_explicit:1,
-                       preserve_subject:1;
+                       preserve_subject:1,
+                       encode_email_headers:1;
        unsigned int    disable_stdin:1;
        /* --show-linear-break */
        unsigned int    track_linear:1,
index f5e1149f9b395e77c3d55a147332c1117746d7b1..0f41af3b550f1f58d89f496b539e44f28559fbf6 100644 (file)
@@ -421,12 +421,12 @@ static int prepare_cmd(struct argv_array *out, const struct child_process *cmd)
        }
 
        /*
-        * If there are no '/' characters in the command then perform a path
-        * lookup and use the resolved path as the command to exec.  If there
-        * are '/' characters, we have exec attempt to invoke the command
-        * directly.
+        * If there are no dir separator characters in the command then perform
+        * a path lookup and use the resolved path as the command to exec. If
+        * there are dir separator characters, we have exec attempt to invoke
+        * the command directly.
         */
-       if (!strchr(out->argv[1], '/')) {
+       if (!has_dir_sep(out->argv[1])) {
                char *program = locate_in_PATH(out->argv[1]);
                if (program) {
                        free((char *)out->argv[1]);
index 0407841ae87af99254f254b65bb53815790fec9a..d1b7edc9957874dea1a1d1c8daf62e2ef2d7f8d8 100644 (file)
@@ -12,7 +12,7 @@
 #include "quote.h"
 #include "transport.h"
 #include "version.h"
-#include "sha1-array.h"
+#include "oid-array.h"
 #include "gpg-interface.h"
 #include "cache.h"
 
@@ -190,10 +190,8 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)
 
                if (reader->line[0] == 'o' && reader->line[1] == 'k')
                        hint->status = REF_STATUS_OK;
-               else {
+               else
                        hint->status = REF_STATUS_REMOTE_REJECT;
-                       ret = -1;
-               }
                hint->remote_status = xstrdup_or_null(msg);
                /* start our next search from the next ref */
                hint = hint->next;
@@ -322,29 +320,6 @@ free_return:
        return update_seen;
 }
 
-
-static int atomic_push_failure(struct send_pack_args *args,
-                              struct ref *remote_refs,
-                              struct ref *failing_ref)
-{
-       struct ref *ref;
-       /* Mark other refs as failed */
-       for (ref = remote_refs; ref; ref = ref->next) {
-               if (!ref->peer_ref && !args->send_mirror)
-                       continue;
-
-               switch (ref->status) {
-               case REF_STATUS_EXPECTING_REPORT:
-                       ref->status = REF_STATUS_ATOMIC_PUSH_FAILED;
-                       continue;
-               default:
-                       break; /* do nothing */
-               }
-       }
-       return error("atomic push failed for ref %s. status: %d\n",
-                    failing_ref->name, failing_ref->status);
-}
-
 #define NONCE_LEN_LIMIT 256
 
 static void reject_invalid_nonce(const char *nonce, int len)
@@ -489,7 +464,10 @@ int send_pack(struct send_pack_args *args,
                        if (use_atomic) {
                                strbuf_release(&req_buf);
                                strbuf_release(&cap_buf);
-                               return atomic_push_failure(args, remote_refs, ref);
+                               reject_atomic_push(remote_refs, args->send_mirror);
+                               error("atomic push failed for ref %s. status: %d\n",
+                                     ref->name, ref->status);
+                               return args->porcelain ? 0 : -1;
                        }
                        /* else fallthrough */
                default:
index e528225e787f4e26de604807efc798e8b967010e..f30bb73c703a2b872dec85de7c68f6aa6c0b075d 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")
@@ -946,6 +946,8 @@ static int run_git_commit(struct repository *r,
                argv_array_push(&cmd.args, "--amend");
        if (opts->gpg_sign)
                argv_array_pushf(&cmd.args, "-S%s", opts->gpg_sign);
+       else
+               argv_array_push(&cmd.args, "--no-gpg-sign");
        if (defmsg)
                argv_array_pushl(&cmd.args, "-F", defmsg, NULL);
        else if (!(flags & EDIT_MSG))
@@ -1323,7 +1325,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 +1435,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 +1472,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;
 }
@@ -1564,7 +1580,7 @@ static const char *command_to_string(const enum todo_command command)
 
 static char command_to_char(const enum todo_command command)
 {
-       if (command < TODO_COMMENT && todo_command_info[command].c)
+       if (command < TODO_COMMENT)
                return todo_command_info[command].c;
        return comment_line_char;
 }
@@ -1929,7 +1945,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 +1983,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 +3019,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));
@@ -3113,7 +3130,7 @@ static int do_exec(struct repository *r, const char *command_line)
        const char *child_argv[] = { NULL, NULL };
        int dirty, status;
 
-       fprintf(stderr, "Executing: %s\n", command_line);
+       fprintf(stderr, _("Executing: %s\n"), command_line);
        child_argv[0] = command_line;
        argv_array_pushf(&child_env, "GIT_DIR=%s", absolute_path(get_git_dir()));
        argv_array_pushf(&child_env, "GIT_WORK_TREE=%s",
@@ -3290,6 +3307,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);
@@ -3708,10 +3726,11 @@ static const char *reflog_message(struct replay_opts *opts,
 {
        va_list ap;
        static struct strbuf buf = STRBUF_INIT;
+       char *reflog_action = getenv(GIT_REFLOG_ACTION);
 
        va_start(ap, fmt);
        strbuf_reset(&buf);
-       strbuf_addstr(&buf, action_name(opts));
+       strbuf_addstr(&buf, reflog_action ? reflog_action : action_name(opts));
        if (sub_action)
                strbuf_addf(&buf, " (%s)", sub_action);
        if (fmt) {
@@ -3799,8 +3818,11 @@ static int pick_commits(struct repository *r,
                        struct replay_opts *opts)
 {
        int res = 0, reschedule = 0;
+       char *prev_reflog_action;
 
+       /* Note that 0 for 3rd parameter of setenv means set only if not set */
        setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
+       prev_reflog_action = xstrdup(getenv(GIT_REFLOG_ACTION));
        if (opts->allow_ff)
                assert(!(opts->signoff || opts->no_commit ||
                                opts->record_origin || opts->edit));
@@ -3825,7 +3847,7 @@ static int pick_commits(struct repository *r,
                                        fclose(f);
                                }
                                if (!opts->quiet)
-                                       fprintf(stderr, "Rebasing (%d/%d)%s",
+                                       fprintf(stderr, _("Rebasing (%d/%d)%s"),
                                                todo_list->done_nr,
                                                todo_list->total_nr,
                                                opts->verbose ? "\n" : "\r");
@@ -3845,12 +3867,14 @@ static int pick_commits(struct repository *r,
                }
                if (item->command <= TODO_SQUASH) {
                        if (is_rebase_i(opts))
-                               setenv("GIT_REFLOG_ACTION", reflog_message(opts,
+                               setenv(GIT_REFLOG_ACTION, reflog_message(opts,
                                        command_to_string(item->command), NULL),
                                        1);
                        res = do_pick_commit(r, item->command, item->commit,
                                             opts, is_final_fixup(todo_list),
                                             &check_todo);
+                       if (is_rebase_i(opts))
+                               setenv(GIT_REFLOG_ACTION, prev_reflog_action, 1);
                        if (is_rebase_i(opts) && res < 0) {
                                /* Reschedule */
                                advise(_(rescheduled_advice),
@@ -4077,7 +4101,7 @@ cleanup_head_ref:
                        if (!opts->verbose)
                                term_clear_line();
                        fprintf(stderr,
-                               "Successfully rebased and updated %s.\n",
+                               _("Successfully rebased and updated %s.\n"),
                                head_ref.buf);
                }
 
@@ -4591,6 +4615,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,
                                   struct rev_info *revs, struct strbuf *out,
                                   unsigned flags)
 {
+       int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
        int rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;
        int root_with_onto = flags & TODO_LIST_ROOT_WITH_ONTO;
        struct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;
@@ -4645,6 +4670,8 @@ static int make_script_with_merges(struct pretty_print_context *pp,
                is_empty = is_original_commit_empty(commit);
                if (!is_empty && (commit->object.flags & PATCHSAME))
                        continue;
+               if (is_empty && !keep_empty)
+                       continue;
 
                strbuf_reset(&oneline);
                pretty_print_commit(pp, commit, &oneline);
@@ -4656,6 +4683,9 @@ static int make_script_with_merges(struct pretty_print_context *pp,
                        strbuf_addf(&buf, "%s %s %s", cmd_pick,
                                    oid_to_hex(&commit->object.oid),
                                    oneline.buf);
+                       if (is_empty)
+                               strbuf_addf(&buf, " %c empty",
+                                           comment_line_char);
 
                        FLEX_ALLOC_STR(entry, string, buf.buf);
                        oidcpy(&entry->entry.oid, &commit->object.oid);
@@ -4819,14 +4849,16 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
        struct pretty_print_context pp = {0};
        struct rev_info revs;
        struct commit *commit;
+       int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
        const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? "p" : "pick";
        int rebase_merges = flags & TODO_LIST_REBASE_MERGES;
+       int reapply_cherry_picks = flags & TODO_LIST_REAPPLY_CHERRY_PICKS;
 
        repo_init_revisions(r, &revs, NULL);
        revs.verbose_header = 1;
        if (!rebase_merges)
                revs.max_parents = 1;
-       revs.cherry_mark = 1;
+       revs.cherry_mark = !reapply_cherry_picks;
        revs.limited = 1;
        revs.reverse = 1;
        revs.right_only = 1;
@@ -4858,9 +4890,13 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
 
                if (!is_empty && (commit->object.flags & PATCHSAME))
                        continue;
+               if (is_empty && !keep_empty)
+                       continue;
                strbuf_addf(out, "%s %s ", insn,
                            oid_to_hex(&commit->object.oid));
                pretty_print_commit(&pp, commit, out);
+               if (is_empty)
+                       strbuf_addf(out, " %c empty", comment_line_char);
                strbuf_addch(out, '\n');
        }
        return 0;
@@ -4947,6 +4983,8 @@ static void todo_list_to_strbuf(struct repository *r, struct todo_list *todo_lis
                max = num;
 
        for (item = todo_list->items, i = 0; i < max; i++, item++) {
+               char cmd;
+
                /* if the item is not a command write it and continue */
                if (item->command >= TODO_COMMENT) {
                        strbuf_addf(buf, "%.*s\n", item->arg_len,
@@ -4955,8 +4993,9 @@ static void todo_list_to_strbuf(struct repository *r, struct todo_list *todo_lis
                }
 
                /* add command to the buffer */
-               if (flags & TODO_LIST_ABBREVIATE_CMDS)
-                       strbuf_addch(buf, command_to_char(item->command));
+               cmd = command_to_char(item->command);
+               if ((flags & TODO_LIST_ABBREVIATE_CMDS) && cmd)
+                       strbuf_addch(buf, cmd);
                else
                        strbuf_addstr(buf, command_to_string(item->command));
 
@@ -5315,3 +5354,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..9611605711fd6f41bbf9b561ac05768e00f2f24e 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);
@@ -134,7 +134,7 @@ int sequencer_rollback(struct repository *repo, struct replay_opts *opts);
 int sequencer_skip(struct repository *repo, struct replay_opts *opts);
 int sequencer_remove_state(struct replay_opts *opts);
 
-/* #define TODO_LIST_KEEP_EMPTY (1U << 0) */ /* No longer used */
+#define TODO_LIST_KEEP_EMPTY (1U << 0)
 #define TODO_LIST_SHORTEN_IDS (1U << 1)
 #define TODO_LIST_ABBREVIATE_CMDS (1U << 2)
 #define TODO_LIST_REBASE_MERGES (1U << 3)
@@ -150,7 +150,7 @@ int sequencer_remove_state(struct replay_opts *opts);
  * `--onto`, we do not want to re-generate the root commits.
  */
 #define TODO_LIST_ROOT_WITH_ONTO (1U << 6)
-
+#define TODO_LIST_REAPPLY_CHERRY_PICKS (1U << 7)
 
 int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
                          const char **argv, unsigned flags);
@@ -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..ccd34dd9e8ce9c498facd694162c30656bfe3195 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)
@@ -868,9 +881,7 @@ void prepare_alt_odb(struct repository *r)
 /* Returns 1 if we have successfully freshened the file, 0 otherwise. */
 static int freshen_file(const char *fn)
 {
-       struct utimbuf t;
-       t.actime = t.modtime = time(NULL);
-       return !utime(fn, &t);
+       return !utime(fn, NULL);
 }
 
 /*
index 5bb006e5a9e0fe6373a3a69c3d2c3cb7d1147738..0b8cb5247abc05bcc498908fcd34b1e24c817976 100644 (file)
@@ -8,7 +8,7 @@
 #include "refs.h"
 #include "remote.h"
 #include "dir.h"
-#include "sha1-array.h"
+#include "oid-array.h"
 #include "packfile.h"
 #include "object-store.h"
 #include "repository.h"
@@ -1815,8 +1815,8 @@ static enum get_oid_result get_oid_with_context_1(struct repository *repo,
 
                        cb.repo = repo;
                        cb.list = &list;
-                       refs_for_each_ref(repo->refs, handle_one_ref, &cb);
-                       refs_head_ref(repo->refs, handle_one_ref, &cb);
+                       refs_for_each_ref(get_main_ref_store(repo), handle_one_ref, &cb);
+                       refs_head_ref(get_main_ref_store(repo), handle_one_ref, &cb);
                        commit_list_sort_by_date(&list);
                        return get_oid_oneline(repo, name + 2, oid, list);
                }
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 7fd04afed19af7de9dc1438b328567d0b7f2b2b4..14f7fa6e27ebd22a15be35edaf867f619a054703 100644 (file)
--- a/shallow.c
+++ b/shallow.c
@@ -8,7 +8,7 @@
 #include "pkt-line.h"
 #include "remote.h"
 #include "refs.h"
-#include "sha1-array.h"
+#include "oid-array.h"
 #include "diff.h"
 #include "revision.h"
 #include "commit-slab.h"
diff --git a/shell.c b/shell.c
index 54cca7439de636daa37b7f8627c5cdd8bb431703..cef7ffdc9e1d3040930841134577c591cf02f650 100644 (file)
--- a/shell.c
+++ b/shell.c
@@ -4,6 +4,7 @@
 #include "strbuf.h"
 #include "run-command.h"
 #include "alias.h"
+#include "prompt.h"
 
 #define COMMAND_DIR "git-shell-commands"
 #define HELP_COMMAND COMMAND_DIR "/help"
@@ -76,12 +77,11 @@ static void run_shell(void)
                int count;
 
                fprintf(stderr, "git> ");
-               if (strbuf_getline_lf(&line, stdin) == EOF) {
+               if (git_read_line_interactively(&line) == EOF) {
                        fprintf(stderr, "\n");
                        strbuf_release(&line);
                        break;
                }
-               strbuf_trim(&line);
                rawargs = strbuf_detach(&line, NULL);
                split_args = xstrdup(rawargs);
                count = split_cmdline(split_args, &argv);
index 4d1c92d5826da800360a98e88d577f6f25d27f96..e175dfbc388ab50a55429bfff287541bb2a48530 100644 (file)
@@ -225,7 +225,8 @@ static int name_and_item_from_var(const char *var, struct strbuf *name,
                                  struct strbuf *item)
 {
        const char *subsection, *key;
-       int subsection_len, parse;
+       size_t subsection_len;
+       int parse;
        parse = parse_config_key(var, "submodule", &subsection,
                        &subsection_len, &key);
        if (parse < 0 || !subsection)
index 31f391d7d2541c1498387248e669248de3430b5d..e2ef5698c893c3587e500ac88b46aa9ba68609ea 100644 (file)
@@ -12,7 +12,7 @@
 #include "diffcore.h"
 #include "refs.h"
 #include "string-list.h"
-#include "sha1-array.h"
+#include "oid-array.h"
 #include "argv-array.h"
 #include "blob.h"
 #include "thread-utils.h"
@@ -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 9afd61e3ca0d19e569c5d31855fbbc79c83c9d4e..13747f134410fe55f3ad6de0f2bac90fb4b12407 100644 (file)
--- a/t/README
+++ b/t/README
@@ -69,7 +69,8 @@ You can also run each test individually from command line, like this:
 
 You can pass --verbose (or -v), --debug (or -d), and --immediate
 (or -i) command line argument to the test, or by setting GIT_TEST_OPTS
-appropriately before running "make".
+appropriately before running "make". Short options can be bundled, i.e.
+'-d -v' is the same as '-dv'.
 
 -v::
 --verbose::
@@ -386,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).
@@ -550,6 +547,41 @@ Here are the "do's:"
    reports "ok" or "not ok" to the end user running the tests. Under
    --verbose, they are shown to help debug the tests.
 
+ - Be careful when you loop
+
+   You may need to verify multiple things in a loop, but the
+   following does not work correctly:
+
+       test_expect_success 'test three things' '
+           for i in one two three
+           do
+               test_something "$i"
+           done &&
+           test_something_else
+       '
+
+   Because the status of the loop itself is the exit status of the
+   test_something in the last round, the loop does not fail when
+   "test_something" for "one" or "two" fails.  This is not what you
+   want.
+
+   Instead, you can break out of the loop immediately when you see a
+   failure.  Because all test_expect_* snippets are executed inside
+   a function, "return 1" can be used to fail the test immediately
+   upon a failure:
+
+       test_expect_success 'test three things' '
+           for i in one two three
+           do
+               test_something "$i" || return 1
+           done &&
+           test_something_else
+       '
+
+   Note that we still &&-chain the loop to propagate failures from
+   earlier commands.
+
+
 And here are the "don'ts:"
 
  - Don't exit() within a <script> part.
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;
similarity index 83%
rename from t/helper/test-sha1-array.c
rename to t/helper/test-oid-array.c
index ad5e69f9d3b0e03442f0d23b3b559bbfc163ee7b..ce9fd5f0919f76ee3660cec5464b8a31c65eba07 100644 (file)
@@ -1,6 +1,6 @@
 #include "test-tool.h"
 #include "cache.h"
-#include "sha1-array.h"
+#include "oid-array.h"
 
 static int print_oid(const struct object_id *oid, void *data)
 {
@@ -8,7 +8,7 @@ static int print_oid(const struct object_id *oid, void *data)
        return 0;
 }
 
-int cmd__sha1_array(int argc, const char **argv)
+int cmd__oid_array(int argc, const char **argv)
 {
        struct oid_array array = OID_ARRAY_INIT;
        struct strbuf line = STRBUF_INIT;
@@ -19,11 +19,11 @@ int cmd__sha1_array(int argc, const char **argv)
 
                if (skip_prefix(line.buf, "append ", &arg)) {
                        if (get_oid_hex(arg, &oid))
-                               die("not a hexadecimal SHA1: %s", arg);
+                               die("not a hexadecimal oid: %s", arg);
                        oid_array_append(&array, &oid);
                } else if (skip_prefix(line.buf, "lookup ", &arg)) {
                        if (get_oid_hex(arg, &oid))
-                               die("not a hexadecimal SHA1: %s", arg);
+                               die("not a hexadecimal oid: %s", arg);
                        printf("%d\n", oid_array_lookup(&array, &oid));
                } else if (!strcmp(line.buf, "clear"))
                        oid_array_clear(&array);
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 282d53638446bb2f0c91b81b313852cbadfb3c1f..12ca698e17a1d556bf345355495849b452b79330 100644 (file)
@@ -67,7 +67,7 @@ static void unpack_sideband(void)
                case PACKET_READ_NORMAL:
                        band = reader.line[0] & 0xff;
                        if (band < 1 || band > 2)
-                               die("unexpected side band %d", band);
+                               continue; /* skip non-sideband packets */
                        fd = band;
 
                        write_or_die(fd, reader.line + 1, reader.pktlen - 1);
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..2ece4d1ebfbbd40fb2b868bacc4dad05733c59ff 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 },
@@ -36,6 +37,7 @@ static struct test_cmd cmds[] = {
        { "match-trees", cmd__match_trees },
        { "mergesort", cmd__mergesort },
        { "mktemp", cmd__mktemp },
+       { "oid-array", cmd__oid_array },
        { "oidmap", cmd__oidmap },
        { "online-cpus", cmd__online_cpus },
        { "parse-options", cmd__parse_options },
@@ -56,7 +58,6 @@ static struct test_cmd cmds[] = {
        { "scrap-cache-tree", cmd__scrap_cache_tree },
        { "serve-v2", cmd__serve_v2 },
        { "sha1", cmd__sha1 },
-       { "sha1-array", cmd__sha1_array },
        { "sha256", cmd__sha256 },
        { "sigchain", cmd__sigchain },
        { "strcmp-offset", cmd__strcmp_offset },
@@ -111,6 +112,7 @@ int cmd_main(int argc, const char **argv)
                        argc--;
                        trace2_cmd_name(cmds[i].name);
                        trace2_cmd_list_config();
+                       trace2_cmd_list_env_vars();
                        return cmds[i].fn(argc, argv);
                }
        }
index c8549fd87f23b6372a41b2ad5d7ba28df3e258c7..1cbaec02f305fe04b6f647c589d67dfe35f5afe7 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);
@@ -46,7 +47,7 @@ int cmd__run_command(int argc, const char **argv);
 int cmd__scrap_cache_tree(int argc, const char **argv);
 int cmd__serve_v2(int argc, const char **argv);
 int cmd__sha1(int argc, const char **argv);
-int cmd__sha1_array(int argc, const char **argv);
+int cmd__oid_array(int argc, const char **argv);
 int cmd__sha256(int argc, const char **argv);
 int cmd__sigchain(int argc, const char **argv);
 int cmd__strcmp_offset(int argc, const char **argv);
old mode 100755 (executable)
new mode 100644 (file)
index 937b831..dea2cbe
@@ -1,4 +1,5 @@
-#!/bin/sh
+# Shell library for testing credential handling including helpers. See t0302
+# for an example of testing a specific helper.
 
 # Try a set of credential helpers; the expected stdin,
 # stdout and stderr should be provided on stdin,
@@ -19,7 +20,7 @@ check() {
                false
        fi &&
        test_cmp expect-stdout stdout &&
-       test_cmp expect-stderr stderr
+       test_i18ncmp expect-stderr stderr
 }
 
 read_chunk() {
old mode 100755 (executable)
new mode 100644 (file)
index 8d28652..9fc5241
@@ -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 '
old mode 100755 (executable)
new mode 100644 (file)
old mode 100755 (executable)
new mode 100644 (file)
index 1dd17fc..64fc648
@@ -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 7743f4f4c9ff728001b4d9d8201654a288c1ef9b..80c53edca7af46556e50aa64c0fdfb912cae6a70 100755 (executable)
@@ -31,10 +31,6 @@ test_perf 'simulated fetch' '
        } | git pack-objects --revs --stdout >/dev/null
 '
 
-test_perf 'pack to file' '
-       git pack-objects --all pack1 </dev/null >/dev/null
-'
-
 test_perf 'pack to file (bitmap)' '
        git pack-objects --use-bitmap-index --all pack1b </dev/null >/dev/null
 '
diff --git a/t/perf/p9300-fast-import-export.sh b/t/perf/p9300-fast-import-export.sh
new file mode 100755 (executable)
index 0000000..586161e
--- /dev/null
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+test_description='test fast-import and fast-export performance'
+. ./perf-lib.sh
+
+test_perf_default_repo
+
+# Use --no-data here to produce a vastly smaller export file.
+# This is much cheaper to work with but should still exercise
+# fast-import pretty well (we'll still process all commits and
+# trees, which account for 60% or more of objects in most repos).
+#
+# Use --reencode to avoid the default of aborting on non-utf8 commits,
+# which lets this test run against a wider variety of sample repos.
+test_perf 'export (no-blobs)' '
+       git fast-export --reencode=yes --no-data HEAD >export
+'
+
+test_perf 'import (no-blobs)' '
+       git fast-import --force <export
+'
+
+test_done
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 &&
index 26f82063267f3043f7f9c36745d913c12dbe0a27..1edd5aeb8f01d490b1cceba2c1d67df181868280 100755 (executable)
@@ -392,13 +392,6 @@ test_expect_success SYMLINKS 're-init to move gitdir symlink' '
        test_path_is_dir realgitdir/refs
 '
 
-# Tests for the hidden file attribute on windows
-is_hidden () {
-       # Use the output of `attrib`, ignore the absolute path
-       case "$(attrib "$1")" in *H*?:*) return 0;; esac
-       return 1
-}
-
 test_expect_success MINGW '.git hidden' '
        rm -rf newdir &&
        (
@@ -406,7 +399,7 @@ test_expect_success MINGW '.git hidden' '
                mkdir newdir &&
                cd newdir &&
                git init &&
-               is_hidden .git
+               test_path_is_hidden .git
        ) &&
        check_config newdir/.git false unset
 '
index 1f600e2cae544b598823a2fd3854493fc8e828c9..88b9ae81588d1cd8581c29faf67ee8fe4a4e9a6d 100755 (executable)
@@ -17,7 +17,7 @@ test_expect_success 'get GIT_COMMITTER_IDENT' '
        test_cmp expect actual
 '
 
-test_expect_success !FAIL_PREREQS,!AUTOIDENT 'requested identites are strict' '
+test_expect_success !FAIL_PREREQS,!AUTOIDENT 'requested identities are strict' '
        (
                sane_unset GIT_COMMITTER_NAME &&
                sane_unset GIT_COMMITTER_EMAIL &&
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 3483b72db42a61563ffa88d30c0f084178d91ab3..f8178ee4e392fa5cfdbef07b0f42710ea68bc67b 100755 (executable)
@@ -54,7 +54,7 @@ Alias
     -A, --alias-source <string>
                           get a string
     -Z, --alias-target <string>
-                          get a string
+                          alias of --alias-source
 
 EOF
 
index 2ea2d00c39a6a53ae121dd35014a4c539ea1b35b..56db5c8abab62e7ae327bece57768f0f4ae6f0e4 100755 (executable)
@@ -476,6 +476,7 @@ test_expect_success MINGW 'is_valid_path() on Windows' '
                C:\\git \
                comm \
                conout.c \
+               com0.c \
                lptN \
                \
                --not \
@@ -488,6 +489,7 @@ test_expect_success MINGW 'is_valid_path() on Windows' '
                "AUX.c" \
                "abc/conOut\$  .xyz/test" \
                lpt8 \
+               com9.c \
                "lpt*" \
                Nul \
                "PRN./abc"
index 5dda570b9a1ef3139165a4b901e16f64a3263209..45685af2fd5095a9901fdb150de1c6fd8703728f 100755 (executable)
@@ -18,7 +18,7 @@ test_expect_success 'ordered enumeration' '
        {
                echoid append 88 44 aa 55 &&
                echo for_each_unique
-       } | test-tool sha1-array >actual &&
+       } | test-tool oid-array >actual &&
        test_cmp expect actual
 '
 
@@ -28,7 +28,7 @@ test_expect_success 'ordered enumeration with duplicate suppression' '
                echoid append 88 44 aa 55 &&
                echoid append 88 44 aa 55 &&
                echo for_each_unique
-       } | test-tool sha1-array >actual &&
+       } | test-tool oid-array >actual &&
        test_cmp expect actual
 '
 
@@ -36,7 +36,7 @@ test_expect_success 'lookup' '
        {
                echoid append 88 44 aa 55 &&
                echoid lookup 55
-       } | test-tool sha1-array >actual &&
+       } | test-tool oid-array >actual &&
        n=$(cat actual) &&
        test "$n" -eq 1
 '
@@ -45,7 +45,7 @@ test_expect_success 'lookup non-existing entry' '
        {
                echoid append 88 44 aa 55 &&
                echoid lookup 33
-       } | test-tool sha1-array >actual &&
+       } | test-tool oid-array >actual &&
        n=$(cat actual) &&
        test "$n" -lt 0
 '
@@ -55,7 +55,7 @@ test_expect_success 'lookup with duplicates' '
                echoid append 88 44 aa 55 &&
                echoid append 88 44 aa 55 &&
                echoid lookup 55
-       } | test-tool sha1-array >actual &&
+       } | test-tool oid-array >actual &&
        n=$(cat actual) &&
        test "$n" -ge 2 &&
        test "$n" -le 3
@@ -66,7 +66,7 @@ test_expect_success 'lookup non-existing entry with duplicates' '
                echoid append 88 44 aa 55 &&
                echoid append 88 44 aa 55 &&
                echoid lookup 66
-       } | test-tool sha1-array >actual &&
+       } | test-tool oid-array >actual &&
        n=$(cat actual) &&
        test "$n" -lt 0
 '
@@ -81,7 +81,7 @@ test_expect_success 'lookup with almost duplicate values' '
                echo "append $id1" &&
                echo "append $id2" &&
                echoid lookup 55
-       } | test-tool sha1-array >actual &&
+       } | test-tool oid-array >actual &&
        n=$(cat actual) &&
        test "$n" -eq 0
 '
@@ -90,7 +90,7 @@ test_expect_success 'lookup with single duplicate value' '
        {
                echoid append 55 55 &&
                echoid lookup 55
-       } | test-tool sha1-array >actual &&
+       } | test-tool oid-array >actual &&
        n=$(cat actual) &&
        test "$n" -ge 0 &&
        test "$n" -le 1
index 7065a1b9373b0a02a9b632b00aba2a1c5d5b1465..1529155cf01629a2c82bc66ebc37c3617086f422 100755 (executable)
@@ -199,6 +199,43 @@ test_expect_success JSON_PP 'event stream, list config' '
        test_cmp expect actual
 '
 
+# Test listing of all "interesting" environment variables.
+
+test_expect_success JSON_PP 'event stream, list env vars' '
+       test_when_finished "rm trace.event actual expect" &&
+       GIT_TRACE2_EVENT="$(pwd)/trace.event" \
+               GIT_TRACE2_ENV_VARS="A_VAR,OTHER_VAR,MISSING" \
+               A_VAR=1 OTHER_VAR="hello world" test-tool trace2 001return 0 &&
+       perl "$TEST_DIRECTORY/t0212/parse_events.perl" <trace.event >actual &&
+       sed -e "s/^|//" >expect <<-EOF &&
+       |VAR1 = {
+       |  "_SID0_":{
+       |    "argv":[
+       |      "_EXE_",
+       |      "trace2",
+       |      "001return",
+       |      "0"
+       |    ],
+       |    "exit_code":0,
+       |    "hierarchy":"trace2",
+       |    "name":"trace2",
+       |    "params":[
+       |      {
+       |        "param":"A_VAR",
+       |        "value":"1"
+       |      },
+       |      {
+       |        "param":"OTHER_VAR",
+       |        "value":"hello world"
+       |      }
+       |    ],
+       |    "version":"$V"
+       |  }
+       |};
+       EOF
+       test_cmp expect actual
+'
+
 test_expect_success JSON_PP 'basic trace2_data' '
        test_when_finished "rm trace.event actual expect" &&
        GIT_TRACE2_EVENT="$(pwd)/trace.event" test-tool trace2 006data test_category k1 v1 test_category k2 v2 &&
index 39f097ea9e5094530b981c49193df8df2cbda406..48484cbcf6eda7e728515c9f217be4d5515fa8e4 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,119 @@ 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 path 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
+'
+
+# usage: check_host_and_path <url> <expected-host> <expected-path>
+check_host_and_path () {
+       # we always parse the path component, but we need this to make sure it
+       # is passed to the helper
+       test_config credential.useHTTPPath true &&
+       check fill "verbatim user pass" <<-EOF
+       url=$1
+       --
+       protocol=https
+       host=$2
+       path=$3
+       username=user
+       password=pass
+       --
+       verbatim: get
+       verbatim: protocol=https
+       verbatim: host=$2
+       verbatim: path=$3
        EOF
+}
+
+test_expect_success 'url parser handles bare query marker' '
+       check_host_and_path https://example.com?foo.git example.com ?foo.git
+'
+
+test_expect_success 'url parser handles bare fragment marker' '
+       check_host_and_path https://example.com#foo.git example.com "#foo.git"
+'
+
+test_expect_success 'url parser not confused by encoded markers' '
+       check_host_and_path https://example.com%23%3f%2f/foo.git \
+               "example.com#?/" foo.git
 '
 
 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 a1ec501a872b9ae4087c93a5736fcd297bda7fc8..6e032716a687a3cd64b37683c67d3a476f06287f 100755 (executable)
@@ -162,4 +162,81 @@ test_expect_success 'rebase --skip works with two conflicts in a row' '
        git rebase --skip
 '
 
+test_expect_success '--reapply-cherry-picks' '
+       git init repo &&
+
+       # O(1-10) -- O(1-11) -- O(0-10) master
+       #        \
+       #         -- O(1-11) -- O(1-12) otherbranch
+
+       printf "Line %d\n" $(test_seq 1 10) >repo/file.txt &&
+       git -C repo add file.txt &&
+       git -C repo commit -m "base commit" &&
+
+       printf "Line %d\n" $(test_seq 1 11) >repo/file.txt &&
+       git -C repo commit -a -m "add 11" &&
+
+       printf "Line %d\n" $(test_seq 0 10) >repo/file.txt &&
+       git -C repo commit -a -m "add 0 delete 11" &&
+
+       git -C repo checkout -b otherbranch HEAD^^ &&
+       printf "Line %d\n" $(test_seq 1 11) >repo/file.txt &&
+       git -C repo commit -a -m "add 11 in another branch" &&
+
+       printf "Line %d\n" $(test_seq 1 12) >repo/file.txt &&
+       git -C repo commit -a -m "add 12 in another branch" &&
+
+       # Regular rebase fails, because the 1-11 commit is deduplicated
+       test_must_fail git -C repo rebase --merge master 2> err &&
+       test_i18ngrep "error: could not apply.*add 12 in another branch" err &&
+       git -C repo rebase --abort &&
+
+       # With --reapply-cherry-picks, it works
+       git -C repo rebase --merge --reapply-cherry-picks master
+'
+
+test_expect_success '--reapply-cherry-picks refrains from reading unneeded blobs' '
+       git init server &&
+
+       # O(1-10) -- O(1-11) -- O(1-12) master
+       #        \
+       #         -- O(0-10) otherbranch
+
+       printf "Line %d\n" $(test_seq 1 10) >server/file.txt &&
+       git -C server add file.txt &&
+       git -C server commit -m "merge base" &&
+
+       printf "Line %d\n" $(test_seq 1 11) >server/file.txt &&
+       git -C server commit -a -m "add 11" &&
+
+       printf "Line %d\n" $(test_seq 1 12) >server/file.txt &&
+       git -C server commit -a -m "add 12" &&
+
+       git -C server checkout -b otherbranch HEAD^^ &&
+       printf "Line %d\n" $(test_seq 0 10) >server/file.txt &&
+       git -C server commit -a -m "add 0" &&
+
+       test_config -C server uploadpack.allowfilter 1 &&
+       test_config -C server uploadpack.allowanysha1inwant 1 &&
+
+       git clone --filter=blob:none "file://$(pwd)/server" client &&
+       git -C client checkout origin/master &&
+       git -C client checkout origin/otherbranch &&
+
+       # Sanity check to ensure that the blobs from the merge base and "add
+       # 11" are missing
+       git -C client rev-list --objects --all --missing=print >missing_list &&
+       MERGE_BASE_BLOB=$(git -C server rev-parse master^^:file.txt) &&
+       ADD_11_BLOB=$(git -C server rev-parse master^:file.txt) &&
+       grep "[?]$MERGE_BASE_BLOB" missing_list &&
+       grep "[?]$ADD_11_BLOB" missing_list &&
+
+       git -C client rebase --merge --reapply-cherry-picks origin/master &&
+
+       # The blob from the merge base had to be fetched, but not "add 11"
+       git -C client rev-list --objects --all --missing=print >missing_list &&
+       ! grep "[?]$MERGE_BASE_BLOB" missing_list &&
+       grep "[?]$ADD_11_BLOB" missing_list
+'
+
 test_done
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 61b76f3301982d26b808044bde9ce928f663112e..927a4f4a4e4a2d1d79f3d12f77ae4918c4fd6cee 100755 (executable)
@@ -89,22 +89,22 @@ test_expect_success 'GIT_REFLOG_ACTION' '
        git checkout -b reflog-topic start &&
        test_commit reflog-to-rebase &&
 
-       git rebase --apply reflog-onto &&
+       git rebase reflog-onto &&
        git log -g --format=%gs -3 >actual &&
        cat >expect <<-\EOF &&
-       rebase finished: returning to refs/heads/reflog-topic
-       rebase: reflog-to-rebase
-       rebase: checkout reflog-onto
+       rebase (finish): returning to refs/heads/reflog-topic
+       rebase (pick): reflog-to-rebase
+       rebase (start): checkout reflog-onto
        EOF
        test_cmp expect actual &&
 
        git checkout -b reflog-prefix reflog-to-rebase &&
-       GIT_REFLOG_ACTION=change-the-reflog git rebase --apply reflog-onto &&
+       GIT_REFLOG_ACTION=change-the-reflog git rebase reflog-onto &&
        git log -g --format=%gs -3 >actual &&
        cat >expect <<-\EOF &&
-       rebase finished: returning to refs/heads/reflog-prefix
-       change-the-reflog: reflog-to-rebase
-       change-the-reflog: checkout reflog-onto
+       change-the-reflog (finish): returning to refs/heads/reflog-prefix
+       change-the-reflog (pick): reflog-to-rebase
+       change-the-reflog (start): checkout reflog-onto
        EOF
        test_cmp expect actual
 '
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 cf8dfd6c203b30b0da4339b099cf6cabcbee04a9..4a9204b4b6491e4bb734897268a2154ff407e62c 100755 (executable)
@@ -220,14 +220,13 @@ test_have_prereq !REBASE_P || test_run_rebase failure -p
 test_run_rebase () {
        result=$1
        shift
-       test_expect_$result "rebase $* --keep-empty" "
+       test_expect_$result "rebase $* --no-keep-empty drops begin-empty commits" "
                reset_rebase &&
-               git rebase $* --keep-empty c l &&
-               test_cmp_rev c HEAD~3 &&
-               test_linear_range 'd l' c..
+               git rebase $* --no-keep-empty c l &&
+               test_cmp_rev c HEAD~2 &&
+               test_linear_range 'd l' c..
        "
 }
-test_run_rebase success --apply
 test_run_rebase success -m
 test_run_rebase success -i
 test_have_prereq !REBASE_P || test_run_rebase success -p
@@ -242,7 +241,6 @@ test_run_rebase () {
                test_linear_range 'd k l' j..
        "
 }
-test_run_rebase success --apply
 test_run_rebase success -m
 test_run_rebase success -i
 test_have_prereq !REBASE_P || test_run_rebase success -p
index e1e30517ea646c56f6653c70b41968ca8c6222ed..5e1045a0afc9ecfe122904db0a3c589ff804c732 100755 (executable)
@@ -123,6 +123,42 @@ test_expect_success 'rebase --interactive uses default of --empty=ask' '
        test_cmp expect actual
 '
 
+test_expect_success 'rebase --merge --empty=drop --keep-empty' '
+       git checkout -B testing localmods &&
+       git rebase --merge --empty=drop --keep-empty upstream &&
+
+       test_write_lines D C B A >expect &&
+       git log --format=%s >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'rebase --merge --empty=drop --no-keep-empty' '
+       git checkout -B testing localmods &&
+       git rebase --merge --empty=drop --no-keep-empty upstream &&
+
+       test_write_lines C B A >expect &&
+       git log --format=%s >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'rebase --merge --empty=keep --keep-empty' '
+       git checkout -B testing localmods &&
+       git rebase --merge --empty=keep --keep-empty upstream &&
+
+       test_write_lines D C2 C B A >expect &&
+       git log --format=%s >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'rebase --merge --empty=keep --no-keep-empty' '
+       git checkout -B testing localmods &&
+       git rebase --merge --empty=keep --no-keep-empty upstream &&
+
+       test_write_lines C2 C B A >expect &&
+       git log --format=%s >actual &&
+       test_cmp expect actual
+'
+
 test_expect_success 'rebase --merge does not leave state laying around' '
        git checkout -B testing localmods~2 &&
        git rebase --merge upstream &&
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 6c9d4a13758194dfcf4b005e36f8358ed5a27f80..6f0452c0eac516796e27c5689b2e091c2b21b8ce 100755 (executable)
@@ -28,10 +28,12 @@ test_rebase_same_head () {
        shift &&
        cmp_f="$1" &&
        shift &&
-       test_rebase_same_head_ $status_n $what_n $cmp_n " --apply" "$*" &&
-       test_rebase_same_head_ $status_f $what_f $cmp_f " --apply --no-ff" "$*"
-       test_rebase_same_head_ $status_n $what_n $cmp_n " --merge" "$*" &&
-       test_rebase_same_head_ $status_f $what_f $cmp_f " --merge --no-ff" "$*"
+       test_rebase_same_head_ $status_n $what_n $cmp_n 0 " --apply" "$*" &&
+       test_rebase_same_head_ $status_f $what_f $cmp_f 0 " --apply --no-ff" "$*"
+       test_rebase_same_head_ $status_n $what_n $cmp_n 0 " --merge" "$*" &&
+       test_rebase_same_head_ $status_f $what_f $cmp_f 0 " --merge --no-ff" "$*"
+       test_rebase_same_head_ $status_n $what_n $cmp_n 1 " --merge" "$*" &&
+       test_rebase_same_head_ $status_f $what_f $cmp_f 1 " --merge --no-ff" "$*"
 }
 
 test_rebase_same_head_ () {
@@ -41,9 +43,21 @@ test_rebase_same_head_ () {
        shift &&
        cmp="$1" &&
        shift &&
+       abbreviate="$1" &&
+       shift &&
        flag="$1"
        shift &&
-       test_expect_$status "git rebase$flag $* with $changes is $what with $cmp HEAD" "
+       if test $abbreviate -eq 1
+       then
+               msg="git rebase$flag $* (rebase.abbreviateCommands = true) with $changes is $what with $cmp HEAD"
+       else
+               msg="git rebase$flag $* with $changes is $what with $cmp HEAD"
+       fi &&
+       test_expect_$status "$msg" "
+               if test $abbreviate -eq 1
+               then
+                       test_config rebase.abbreviateCommands true
+               fi &&
                oldhead=\$(git rev-parse HEAD) &&
                test_when_finished 'git reset --hard \$oldhead' &&
                cp .git/logs/HEAD expect &&
diff --git a/t/t3435-rebase-gpg-sign.sh b/t/t3435-rebase-gpg-sign.sh
new file mode 100755 (executable)
index 0000000..b47c59c
--- /dev/null
@@ -0,0 +1,71 @@
+#!/bin/sh
+#
+# Copyright (c) 2020 Doan Tran Cong Danh
+#
+
+test_description='test rebase --[no-]gpg-sign'
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY/lib-rebase.sh"
+. "$TEST_DIRECTORY/lib-gpg.sh"
+
+if ! test_have_prereq GPG
+then
+       skip_all='skip all test rebase --[no-]gpg-sign, gpg not available'
+       test_done
+fi
+
+test_rebase_gpg_sign () {
+       local must_fail= will=will fake_editor=
+       if test "x$1" = "x!"
+       then
+               must_fail=test_must_fail
+               will="won't"
+               shift
+       fi
+       conf=$1
+       shift
+       test_expect_success "rebase $* with commit.gpgsign=$conf $will sign commit" "
+               git reset two &&
+               git config commit.gpgsign $conf &&
+               set_fake_editor &&
+               FAKE_LINES='r 1 p 2' git rebase --force-rebase --root $* &&
+               $must_fail git verify-commit HEAD^ &&
+               $must_fail git verify-commit HEAD
+       "
+}
+
+test_expect_success 'setup' '
+       test_commit one &&
+       test_commit two &&
+       test_must_fail git verify-commit HEAD &&
+       test_must_fail git verify-commit HEAD^
+'
+
+test_expect_success 'setup: merge commit' '
+       test_commit fork-point &&
+       git switch -c side &&
+       test_commit three &&
+       git switch master &&
+       git merge --no-ff side &&
+       git tag merged
+'
+
+test_rebase_gpg_sign ! false
+test_rebase_gpg_sign   true
+test_rebase_gpg_sign ! true  --no-gpg-sign
+test_rebase_gpg_sign ! true  --gpg-sign --no-gpg-sign
+test_rebase_gpg_sign   false --no-gpg-sign --gpg-sign
+test_rebase_gpg_sign   true  -i
+test_rebase_gpg_sign ! true  -i --no-gpg-sign
+test_rebase_gpg_sign ! true  -i --gpg-sign --no-gpg-sign
+test_rebase_gpg_sign   false -i --no-gpg-sign --gpg-sign
+
+test_expect_failure 'rebase -p --no-gpg-sign override commit.gpgsign' '
+       git reset --hard merged &&
+       git config commit.gpgsign true &&
+       git rebase -p --no-gpg-sign --onto=one fork-point master &&
+       test_must_fail git verify-commit HEAD
+'
+
+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
 '
diff --git a/t/t3514-cherry-pick-revert-gpg.sh b/t/t3514-cherry-pick-revert-gpg.sh
new file mode 100755 (executable)
index 0000000..5b2e250
--- /dev/null
@@ -0,0 +1,86 @@
+#!/bin/sh
+#
+# Copyright (c) 2020 Doan Tran Cong Danh
+#
+
+test_description='test {cherry-pick,revert} --[no-]gpg-sign'
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY/lib-gpg.sh"
+
+if ! test_have_prereq GPG
+then
+       skip_all='skip all test {cherry-pick,revert} --[no-]gpg-sign, gpg not available'
+       test_done
+fi
+
+test_gpg_sign () {
+       local must_fail= will=will fake_editor=
+       if test "x$1" = "x!"
+       then
+               must_fail=test_must_fail
+               will="won't"
+               shift
+       fi
+       conf=$1
+       cmd=$2
+       cmit=$3
+       shift 3
+       test_expect_success "$cmd $* $cmit with commit.gpgsign=$conf $will sign commit" "
+               git reset --hard tip &&
+               git config commit.gpgsign $conf &&
+               git $cmd $* $cmit &&
+               git rev-list tip.. >rev-list &&
+               $must_fail git verify-commit \$(cat rev-list)
+       "
+}
+
+test_expect_success 'setup' '
+       test_commit one &&
+       git switch -c side &&
+       test_commit side1 &&
+       test_commit side2 &&
+       git switch - &&
+       test_commit two &&
+       test_commit three &&
+       test_commit tip
+'
+
+test_gpg_sign ! false cherry-pick   side
+test_gpg_sign ! false cherry-pick ..side
+test_gpg_sign   true  cherry-pick   side
+test_gpg_sign   true  cherry-pick ..side
+test_gpg_sign ! true  cherry-pick   side --no-gpg-sign
+test_gpg_sign ! true  cherry-pick ..side --no-gpg-sign
+test_gpg_sign ! true  cherry-pick   side --gpg-sign --no-gpg-sign
+test_gpg_sign ! true  cherry-pick ..side --gpg-sign --no-gpg-sign
+test_gpg_sign   false cherry-pick   side --no-gpg-sign --gpg-sign
+test_gpg_sign   false cherry-pick ..side --no-gpg-sign --gpg-sign
+test_gpg_sign   true  cherry-pick   side --edit
+test_gpg_sign   true  cherry-pick ..side --edit
+test_gpg_sign ! true  cherry-pick   side --edit --no-gpg-sign
+test_gpg_sign ! true  cherry-pick ..side --edit --no-gpg-sign
+test_gpg_sign ! true  cherry-pick   side --edit --gpg-sign --no-gpg-sign
+test_gpg_sign ! true  cherry-pick ..side --edit --gpg-sign --no-gpg-sign
+test_gpg_sign   false cherry-pick   side --edit --no-gpg-sign --gpg-sign
+test_gpg_sign   false cherry-pick ..side --edit --no-gpg-sign --gpg-sign
+
+test_gpg_sign ! false revert HEAD  --edit
+test_gpg_sign ! false revert two.. --edit
+test_gpg_sign   true  revert HEAD  --edit
+test_gpg_sign   true  revert two.. --edit
+test_gpg_sign ! true  revert HEAD  --edit --no-gpg-sign
+test_gpg_sign ! true  revert two.. --edit --no-gpg-sign
+test_gpg_sign ! true  revert HEAD  --edit --gpg-sign --no-gpg-sign
+test_gpg_sign ! true  revert two.. --edit --gpg-sign --no-gpg-sign
+test_gpg_sign   false revert HEAD  --edit --no-gpg-sign --gpg-sign
+test_gpg_sign   false revert two.. --edit --no-gpg-sign --gpg-sign
+test_gpg_sign   true  revert HEAD  --no-edit
+test_gpg_sign   true  revert two.. --no-edit
+test_gpg_sign ! true  revert HEAD  --no-edit --no-gpg-sign
+test_gpg_sign ! true  revert two.. --no-edit --no-gpg-sign
+test_gpg_sign ! true  revert HEAD  --no-edit --gpg-sign --no-gpg-sign
+test_gpg_sign ! true  revert two.. --no-edit --gpg-sign --no-gpg-sign
+test_gpg_sign   false revert HEAD  --no-edit --no-gpg-sign --gpg-sign
+
+test_done
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 b653dd7d44521270995b456d7b8e2dcfd6957af5..db7e733af9e5be1abe8991e1513f9d31949433f4 100755 (executable)
@@ -1160,6 +1160,59 @@ test_expect_success 'format-patch wraps extremely long from-header (rfc2047)' '
        check_author "Foö Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar"
 '
 
+cat >expect <<'EOF'
+From: Foö Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar
+ Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo
+ Bar Foo Bar Foo Bar Foo Bar <author@example.com>
+EOF
+test_expect_success 'format-patch wraps extremely long from-header (non-ASCII without Q-encoding)' '
+       echo content >>file &&
+       git add file &&
+       GIT_AUTHOR_NAME="Foö Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar" \
+       git commit -m author-check &&
+       git format-patch --no-encode-email-headers --stdout -1 >patch &&
+       sed -n "/^From: /p; /^ /p; /^$/q" patch >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+Subject: [PATCH] Foö
+EOF
+test_expect_success 'subject lines are unencoded with --no-encode-email-headers' '
+       echo content >>file &&
+       git add file &&
+       git commit -m "Foö" &&
+       git format-patch --no-encode-email-headers -1 --stdout >patch &&
+       grep ^Subject: patch >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+Subject: [PATCH] Foö
+EOF
+test_expect_success 'subject lines are unencoded with format.encodeEmailHeaders=false' '
+       echo content >>file &&
+       git add file &&
+       git commit -m "Foö" &&
+       git config format.encodeEmailHeaders false &&
+       git format-patch -1 --stdout >patch &&
+       grep ^Subject: patch >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+Subject: [PATCH] =?UTF-8?q?Fo=C3=B6?=
+EOF
+test_expect_success '--encode-email-headers overrides format.encodeEmailHeaders' '
+       echo content >>file &&
+       git add file &&
+       git commit -m "Foö" &&
+       git config format.encodeEmailHeaders false &&
+       git format-patch --encode-email-headers -1 --stdout >patch &&
+       grep ^Subject: patch >actual &&
+       test_cmp expect actual
+'
+
 cat >expect <<'EOF'
 Subject: header with . in it
 EOF
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 2affd7a100996f93839eec682e4d7381e3ac0b2a..0f7a6d97a8b7e23959ef0c0bf7968fd69339283a 100755 (executable)
@@ -17,7 +17,7 @@ compare_diff () {
 # Compare blame output using the expectation for a diff as reference.
 # Only look for the lines coming from non-boundary commits.
 compare_blame () {
-       sed -n -e "1,4d" -e "s/^\+//p" <"$1" >.tmp-1
+       sed -n -e "1,4d" -e "s/^+//p" <"$1" >.tmp-1
        sed -ne "s/^[^^][^)]*) *//p" <"$2" >.tmp-2
        test_cmp .tmp-1 .tmp-2 && rm -f .tmp-1 .tmp-2
 }
index 4831ad35e61436fdc400533fb0e7f88e6d27027c..c1ed1c2fc4a7f36178220f2c9ead05bfb826ee33 100755 (executable)
@@ -131,4 +131,52 @@ test_expect_success 'diff with rename detection batches blobs' '
        test_line_count = 1 done_lines
 '
 
+test_expect_success 'diff does not fetch anything if inexact rename detection is not needed' '
+       test_when_finished "rm -rf server client trace" &&
+
+       test_create_repo server &&
+       echo a >server/a &&
+       printf "b\nb\nb\nb\nb\n" >server/b &&
+       git -C server add a b &&
+       git -C server commit -m x &&
+       mv server/b server/c &&
+       git -C server add c &&
+       git -C server commit -a -m x &&
+
+       test_config -C server uploadpack.allowfilter 1 &&
+       test_config -C server uploadpack.allowanysha1inwant 1 &&
+       git clone --bare --filter=blob:limit=0 "file://$(pwd)/server" client &&
+
+       # Ensure no fetches.
+       GIT_TRACE_PACKET="$(pwd)/trace" git -C client diff --raw -M HEAD^ HEAD &&
+       ! test_path_exists trace
+'
+
+test_expect_success 'diff --break-rewrites fetches only if necessary, and batches blobs if it does' '
+       test_when_finished "rm -rf server client trace" &&
+
+       test_create_repo server &&
+       echo a >server/a &&
+       printf "b\nb\nb\nb\nb\n" >server/b &&
+       git -C server add a b &&
+       git -C server commit -m x &&
+       printf "c\nc\nc\nc\nc\n" >server/b &&
+       git -C server commit -a -m x &&
+
+       test_config -C server uploadpack.allowfilter 1 &&
+       test_config -C server uploadpack.allowanysha1inwant 1 &&
+       git clone --bare --filter=blob:limit=0 "file://$(pwd)/server" client &&
+
+       # Ensure no fetches.
+       GIT_TRACE_PACKET="$(pwd)/trace" git -C client diff --raw -M HEAD^ HEAD &&
+       ! test_path_exists trace &&
+
+       # But with --break-rewrites, ensure that there is exactly 1 negotiation
+       # by checking that there is only 1 "done" line sent. ("done" marks the
+       # end of negotiation.)
+       GIT_TRACE_PACKET="$(pwd)/trace" git -C client diff --break-rewrites --raw -M HEAD^ HEAD &&
+       grep "git> done" trace >done_lines &&
+       test_line_count = 1 done_lines
+'
+
 test_done
index 971a5a7512ac772f8b7e25a6a75bb4ac2ca43fd3..0ca29821ece3fda711c5c8c82791bed7a829b538 100755 (executable)
@@ -52,6 +52,13 @@ test_fix () {
 
        # find touched lines
        $DIFF file target | sed -n -e "s/^> //p" >fixed
+       # busybox's diff(1) doesn't output normal format
+       if ! test -s fixed
+       then
+               $DIFF -u file target |
+               grep -v '^+++ target' |
+               sed -ne "/^+/s/+//p" >fixed
+       fi
 
        # the changed lines are all expected to change
        fixed_cnt=$(wc -l <fixed)
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 4c8f3b8e1bdd084a6407d76ff26cff0048e90bdf..6cdbe4747aa837376fd01c94420beb1bd1bb6d56 100755 (executable)
@@ -55,6 +55,10 @@ test_expect_success '"git log -- :/a" should not be ambiguous' '
        git log -- :/a
 '
 
+test_expect_success '"git log :/any/path/" should not segfault' '
+       test_must_fail git log :/any/path/
+'
+
 # This differs from the ":/a" check above in that :/in looks like a pathspec,
 # but doesn't match an actual file.
 test_expect_success '"git log :/in" should not be ambiguous' '
index 106eddbd85b04ac8539b722ade63936e28325840..3b76d2eb653f7aed47d4c7391105ec6f24623e97 100755 (executable)
@@ -7,12 +7,12 @@ test_description='git archive --format=zip test'
 SUBSTFORMAT=%H%n
 
 test_lazy_prereq UNZIP_SYMLINKS '
-       (
-               mkdir unzip-symlinks &&
-               cd unzip-symlinks &&
-               "$GIT_UNZIP" "$TEST_DIRECTORY"/t5003/infozip-symlinks.zip &&
-               test -h symlink
-       )
+       "$GIT_UNZIP" "$TEST_DIRECTORY"/t5003/infozip-symlinks.zip &&
+       test -h symlink
+'
+
+test_lazy_prereq UNZIP_CONVERT '
+       "$GIT_UNZIP" -a "$TEST_DIRECTORY"/t5003/infozip-symlinks.zip
 '
 
 check_zip() {
@@ -39,33 +39,33 @@ check_zip() {
        extracted=${dir_with_prefix}a
        original=a
 
-       test_expect_success UNZIP " extract ZIP archive with EOL conversion" '
+       test_expect_success UNZIP_CONVERT " extract ZIP archive with EOL conversion" '
                (mkdir $dir && cd $dir && "$GIT_UNZIP" -a ../$zipfile)
        '
 
-       test_expect_success UNZIP " validate that text files are converted" "
+       test_expect_success UNZIP_CONVERT " validate that text files are converted" "
                test_cmp_bin $extracted/text.cr $extracted/text.crlf &&
                test_cmp_bin $extracted/text.cr $extracted/text.lf
        "
 
-       test_expect_success UNZIP " validate that binary files are unchanged" "
+       test_expect_success UNZIP_CONVERT " validate that binary files are unchanged" "
                test_cmp_bin $original/binary.cr   $extracted/binary.cr &&
                test_cmp_bin $original/binary.crlf $extracted/binary.crlf &&
                test_cmp_bin $original/binary.lf   $extracted/binary.lf
        "
 
-       test_expect_success UNZIP " validate that diff files are converted" "
+       test_expect_success UNZIP_CONVERT " validate that diff files are converted" "
                test_cmp_bin $extracted/diff.cr $extracted/diff.crlf &&
                test_cmp_bin $extracted/diff.cr $extracted/diff.lf
        "
 
-       test_expect_success UNZIP " validate that -diff files are unchanged" "
+       test_expect_success UNZIP_CONVERT " validate that -diff files are unchanged" "
                test_cmp_bin $original/nodiff.cr   $extracted/nodiff.cr &&
                test_cmp_bin $original/nodiff.crlf $extracted/nodiff.crlf &&
                test_cmp_bin $original/nodiff.lf   $extracted/nodiff.lf
        "
 
-       test_expect_success UNZIP " validate that custom diff is unchanged " "
+       test_expect_success UNZIP_CONVERT " validate that custom diff is unchanged " "
                test_cmp_bin $original/custom.cr   $extracted/custom.cr &&
                test_cmp_bin $original/custom.crlf $extracted/custom.crlf &&
                test_cmp_bin $original/custom.lf   $extracted/custom.lf
index 43a7a66c9d1b50640775d6b38df7a31b32e8e548..030a7222b2aca5e97652194ea9a0d8cdf4ca9ab0 100755 (executable)
@@ -42,10 +42,15 @@ test_expect_success 'setup' '
        EOF
 '
 
-test_expect_success 'write midx with no packs' '
-       test_when_finished rm -f pack/multi-pack-index &&
-       git multi-pack-index --object-dir=. write &&
-       midx_read_expect 0 0 4 .
+test_expect_success "don't write midx with no packs" '
+       test_must_fail git multi-pack-index --object-dir=. write &&
+       test_path_is_missing pack/multi-pack-index
+'
+
+test_expect_success "Warn if a midx contains no oid" '
+       cp "$TEST_DIRECTORY"/t5319/no-objects.midx $objdir/pack/multi-pack-index &&
+       test_must_fail git multi-pack-index verify &&
+       rm $objdir/pack/multi-pack-index
 '
 
 generate_objects () {
@@ -521,10 +526,10 @@ test_expect_success 'repack with minimum size does not alter existing packs' '
                cd dup &&
                rm -rf .git/objects/pack &&
                mv .git/objects/pack-backup .git/objects/pack &&
-               touch -m -t 201901010000 .git/objects/pack/pack-D* &&
-               touch -m -t 201901010001 .git/objects/pack/pack-C* &&
-               touch -m -t 201901010002 .git/objects/pack/pack-B* &&
-               touch -m -t 201901010003 .git/objects/pack/pack-A* &&
+               test-tool chmtime =-5 .git/objects/pack/pack-D* &&
+               test-tool chmtime =-4 .git/objects/pack/pack-C* &&
+               test-tool chmtime =-3 .git/objects/pack/pack-B* &&
+               test-tool chmtime =-2 .git/objects/pack/pack-A* &&
                ls .git/objects/pack >expect &&
                MINSIZE=$(test-tool path-utils file-size .git/objects/pack/*pack | sort -n | head -n 1) &&
                git multi-pack-index repack --batch-size=$MINSIZE &&
diff --git a/t/t5319/no-objects.midx b/t/t5319/no-objects.midx
new file mode 100644 (file)
index 0000000..e466b8e
Binary files /dev/null and b/t/t5319/no-objects.midx differ
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 53b2e6b4555de7d7cc27c727e2e7a3d7a4ff3f5b..b8b208fc3da2df2aa85f3b7c52ed17ce85aab446 100755 (executable)
@@ -210,8 +210,14 @@ test_expect_success 'test merge stragety constants' '
                git config core.commitGraph true &&
                test_line_count = 2 $graphdir/commit-graph-chain &&
                test_commit 15 &&
-               git commit-graph write --reachable --split --size-multiple=10 --expire-time=1980-01-01 &&
+               touch $graphdir/to-delete.graph $graphdir/to-keep.graph &&
+               test-tool chmtime =1546362000 $graphdir/to-delete.graph &&
+               test-tool chmtime =1546362001 $graphdir/to-keep.graph &&
+               git commit-graph write --reachable --split --size-multiple=10 \
+                       --expire-time="2019-01-01 12:00 -05:00" &&
                test_line_count = 1 $graphdir/commit-graph-chain &&
+               test_path_is_missing $graphdir/to-delete.graph &&
+               test_path_is_file $graphdir/to-keep.graph &&
                ls $graphdir/graph-*.graph >graph-files &&
                test_line_count = 3 graph-files
        ) &&
index 645b4c78d356971cdf096456d7e90463f58cd0e3..a32efe2b6cdd84690bdb5193609105204ff1dd39 100755 (executable)
@@ -65,6 +65,7 @@ test_expect_success 'fetch with transfer.fsckobjects' '
 cat >exp <<EOF
 To dst
 !      refs/heads/master:refs/heads/test       [remote rejected] (missing necessary objects)
+Done
 EOF
 
 test_expect_success 'push without strict' '
index 04b35402c7aab16319713f8aed205e6c9d809893..e98c3a01741d8ed830d6342dd566b24eee25e8b6 100755 (executable)
@@ -4,6 +4,14 @@ test_description='git ls-remote'
 
 . ./test-lib.sh
 
+generate_references () {
+       for ref
+       do
+               oid=$(git rev-parse "$ref") &&
+               printf '%s\t%s\n' "$oid" "$ref" || return 1
+       done
+}
+
 test_expect_success setup '
        >file &&
        git add file &&
@@ -13,11 +21,11 @@ test_expect_success setup '
        git tag mark1.1 &&
        git tag mark1.2 &&
        git tag mark1.10 &&
-       git show-ref --tags -d | sed -e "s/ /   /" >expected.tag &&
-       (
-               echo "$(git rev-parse HEAD)     HEAD" &&
-               git show-ref -d | sed -e "s/ /  /"
-       >expected.all &&
+       git show-ref --tags -d >expected.tag.raw &&
+       sed -e "s/ /    /" expected.tag.raw >expected.tag &&
+       generate_references HEAD >expected.all &&
+       git show-ref -d >refs &&
+       sed -e "s/ /    /" refs >>expected.all &&
 
        git remote add self "$(pwd)/.git"
 '
@@ -43,34 +51,31 @@ test_expect_success 'ls-remote self' '
 '
 
 test_expect_success 'ls-remote --sort="version:refname" --tags self' '
-       cat >expect <<-EOF &&
-       $(git rev-parse mark)   refs/tags/mark
-       $(git rev-parse mark1.1)        refs/tags/mark1.1
-       $(git rev-parse mark1.2)        refs/tags/mark1.2
-       $(git rev-parse mark1.10)       refs/tags/mark1.10
-       EOF
+       generate_references \
+               refs/tags/mark \
+               refs/tags/mark1.1 \
+               refs/tags/mark1.2 \
+               refs/tags/mark1.10 >expect &&
        git ls-remote --sort="version:refname" --tags self >actual &&
        test_cmp expect actual
 '
 
 test_expect_success 'ls-remote --sort="-version:refname" --tags self' '
-       cat >expect <<-EOF &&
-       $(git rev-parse mark1.10)       refs/tags/mark1.10
-       $(git rev-parse mark1.2)        refs/tags/mark1.2
-       $(git rev-parse mark1.1)        refs/tags/mark1.1
-       $(git rev-parse mark)   refs/tags/mark
-       EOF
+       generate_references \
+               refs/tags/mark1.10 \
+               refs/tags/mark1.2 \
+               refs/tags/mark1.1 \
+               refs/tags/mark >expect &&
        git ls-remote --sort="-version:refname" --tags self >actual &&
        test_cmp expect actual
 '
 
 test_expect_success 'ls-remote --sort="-refname" --tags self' '
-       cat >expect <<-EOF &&
-       $(git rev-parse mark1.2)        refs/tags/mark1.2
-       $(git rev-parse mark1.10)       refs/tags/mark1.10
-       $(git rev-parse mark1.1)        refs/tags/mark1.1
-       $(git rev-parse mark)   refs/tags/mark
-       EOF
+       generate_references \
+               refs/tags/mark1.2 \
+               refs/tags/mark1.10 \
+               refs/tags/mark1.1 \
+               refs/tags/mark >expect &&
        git ls-remote --sort="-refname" --tags self >actual &&
        test_cmp expect actual
 '
@@ -92,7 +97,7 @@ test_expect_success 'use "origin" when no remote specified' '
 
 test_expect_success 'suppress "From <url>" with -q' '
        git ls-remote -q 2>actual_err &&
-       test_must_fail test_cmp exp_err actual_err
+       ! test_cmp exp_err actual_err
 '
 
 test_expect_success 'use branch.<name>.remote if possible' '
@@ -180,8 +185,8 @@ do
                test_config $configsection.hiderefs refs/tags &&
                git ls-remote . >actual &&
                test_unconfig $configsection.hiderefs &&
-               git ls-remote . |
-               sed -e "/       refs\/tags\//d" >expect &&
+               git ls-remote . >expect.raw &&
+               sed -e "/       refs\/tags\//d" expect.raw >expect &&
                test_cmp expect actual
        '
 
@@ -212,17 +217,18 @@ test_expect_success 'protocol v2 supports hiderefs' '
 
 test_expect_success 'ls-remote --symref' '
        git fetch origin &&
-       cat >expect <<-EOF &&
-       ref: refs/heads/master  HEAD
-       $(git rev-parse HEAD)   HEAD
-       $(git rev-parse refs/heads/master)      refs/heads/master
-       $(git rev-parse HEAD)   refs/remotes/origin/HEAD
-       $(git rev-parse refs/remotes/origin/master)     refs/remotes/origin/master
-       $(git rev-parse refs/tags/mark) refs/tags/mark
-       $(git rev-parse refs/tags/mark1.1)      refs/tags/mark1.1
-       $(git rev-parse refs/tags/mark1.10)     refs/tags/mark1.10
-       $(git rev-parse refs/tags/mark1.2)      refs/tags/mark1.2
-       EOF
+       echo "ref: refs/heads/master    HEAD" >expect &&
+       generate_references \
+               HEAD \
+               refs/heads/master >>expect &&
+       oid=$(git rev-parse HEAD) &&
+       echo "$oid      refs/remotes/origin/HEAD" >>expect &&
+       generate_references \
+               refs/remotes/origin/master \
+               refs/tags/mark \
+               refs/tags/mark1.1 \
+               refs/tags/mark1.10 \
+               refs/tags/mark1.2 >>expect &&
        # Protocol v2 supports sending symrefs for refs other than HEAD, so use
        # protocol v0 here.
        GIT_TEST_PROTOCOL_VERSION=0 git ls-remote --symref >actual &&
index 9ff041a093e71aac932f5e563d4922930bc62ff2..9c6218f568ea3bc4c61a1377fc4b2be150b4be1d 100755 (executable)
@@ -1066,6 +1066,7 @@ test_expect_success 'push --porcelain rejected' '
 
        echo >.git/foo  "To testrepo"  &&
        echo >>.git/foo "!      refs/heads/master:refs/heads/master     [remote rejected] (branch is currently checked out)" &&
+       echo >>.git/foo "Done" &&
 
        test_must_fail git push >.git/bar --porcelain  testrepo refs/heads/master:refs/heads/master &&
        test_cmp .git/foo .git/bar
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 23be8ce92d67824166156cf0c5ca380022b79efc..afc680d5e3d3940ee63a1564b9ef5aa1ca39c9a4 100755 (executable)
@@ -177,6 +177,9 @@ test_expect_success 'push (chunked)' '
         test $HEAD = $(git rev-parse --verify HEAD))
 '
 
+## References of remote: atomic1(1)            master(2) collateral(2) other(2)
+## References of local :            atomic2(2) master(1) collateral(3) other(2) collateral1(3) atomic(1)
+## Atomic push         :                       master(1) collateral(3)                         atomic(1)
 test_expect_success 'push --atomic also prevents branch creation, reports collateral' '
        # Setup upstream repo - empty for now
        d=$HTTPD_DOCUMENT_ROOT_PATH/atomic-branches.git &&
@@ -189,7 +192,8 @@ test_expect_success 'push --atomic also prevents branch creation, reports collat
        test_commit atomic2 &&
        git branch collateral &&
        git branch other &&
-       git push "$up" master collateral other &&
+       git push "$up" atomic1 master collateral other &&
+       git tag -d atomic1 &&
 
        # collateral is a valid push, but should be failed by atomic push
        git checkout collateral &&
@@ -224,7 +228,11 @@ test_expect_success 'push --atomic also prevents branch creation, reports collat
 
        # the collateral failure refs should be indicated to the user
        grep "^ ! .*rejected.* atomic -> atomic .*atomic push failed" output &&
-       grep "^ ! .*rejected.* collateral -> collateral .*atomic push failed" output
+       grep "^ ! .*rejected.* collateral -> collateral .*atomic push failed" output &&
+
+       # never report what we do not push
+       ! grep "^ ! .*rejected.* atomic1 " output &&
+       ! grep "^ ! .*rejected.* other " output
 '
 
 test_expect_success 'push --atomic fails on server-side errors' '
index 7079bcf9a0567e926a37031dd56711a0b093d712..620c30d58f00b978bd20753acbf12181d5de2abe 100755 (executable)
@@ -27,6 +27,12 @@ test_refs () {
        test_cmp expect actual
 }
 
+fmt_status_report () {
+       sed -n \
+               -e "/^To / { s/   */ /g; p; }" \
+               -e "/^ ! / { s/   */ /g; p; }"
+}
+
 test_expect_success 'atomic push works for a single branch' '
        mk_repo_pair &&
        (
@@ -191,4 +197,87 @@ test_expect_success 'atomic push is not advertised if configured' '
        test_refs master HEAD@{1}
 '
 
+# References in upstream : master(1) one(1) foo(1)
+# References in workbench: master(2)        foo(1) two(2) bar(2)
+# Atomic push            : master(2)               two(2) bar(2)
+test_expect_success 'atomic push reports (reject by update hook)' '
+       mk_repo_pair &&
+       (
+               cd workbench &&
+               test_commit one &&
+               git branch foo &&
+               git push up master one foo &&
+               git tag -d one
+       ) &&
+       (
+               mkdir -p upstream/.git/hooks &&
+               cat >upstream/.git/hooks/update <<-EOF &&
+               #!/bin/sh
+
+               if test "\$1" = "refs/heads/bar"
+               then
+                       echo >&2 "Pusing to branch bar is prohibited"
+                       exit 1
+               fi
+               EOF
+               chmod a+x upstream/.git/hooks/update
+       ) &&
+       (
+               cd workbench &&
+               test_commit two &&
+               git branch bar
+       ) &&
+       test_must_fail git -C workbench \
+               push --atomic up master two bar >out 2>&1 &&
+       fmt_status_report <out >actual &&
+       cat >expect <<-EOF &&
+       To ../upstream
+        ! [remote rejected] master -> master (atomic push failure)
+        ! [remote rejected] two -> two (atomic push failure)
+        ! [remote rejected] bar -> bar (hook declined)
+       EOF
+       test_cmp expect actual
+'
+
+# References in upstream : master(1) one(1) foo(1)
+# References in workbench: master(2)        foo(1) two(2) bar(2)
+test_expect_success 'atomic push reports (mirror, but reject by update hook)' '
+       (
+               cd workbench &&
+               git remote remove up &&
+               git remote add up ../upstream
+       ) &&
+       test_must_fail git -C workbench \
+               push --atomic --mirror up >out 2>&1 &&
+       fmt_status_report <out >actual &&
+       cat >expect <<-EOF &&
+       To ../upstream
+        ! [remote rejected] master -> master (atomic push failure)
+        ! [remote rejected] one (atomic push failure)
+        ! [remote rejected] bar -> bar (hook declined)
+        ! [remote rejected] two -> two (atomic push failure)
+       EOF
+       test_cmp expect actual
+'
+
+# References in upstream : master(2) one(1) foo(1)
+# References in workbench: master(1)        foo(1) two(2) bar(2)
+test_expect_success 'atomic push reports (reject by non-ff)' '
+       rm upstream/.git/hooks/update &&
+       (
+               cd workbench &&
+               git push up master &&
+               git reset --hard HEAD^
+       ) &&
+       test_must_fail git -C workbench \
+               push --atomic up master foo bar >out 2>&1 &&
+       fmt_status_report <out >actual &&
+       cat >expect <<-EOF &&
+       To ../upstream
+        ! [rejected] master -> master (non-fast-forward)
+        ! [rejected] bar -> bar (atomic push failed)
+       EOF
+       test_cmp expect actual
+'
+
 test_done
diff --git a/t/t5548-push-porcelain.sh b/t/t5548-push-porcelain.sh
new file mode 100755 (executable)
index 0000000..1b19b3e
--- /dev/null
@@ -0,0 +1,279 @@
+#!/bin/sh
+#
+# Copyright (c) 2020 Jiang Xin
+#
+test_description='Test git push porcelain output'
+
+. ./test-lib.sh
+
+# Create commits in <repo> and assign each commit's oid to shell variables
+# given in the arguments (A, B, and C). E.g.:
+#
+#     create_commits_in <repo> A B C
+#
+# NOTE: Never calling this function from a subshell since variable
+# assignments will disappear when subshell exits.
+create_commits_in () {
+       repo="$1" &&
+       if ! parent=$(git -C "$repo" rev-parse HEAD^{} --)
+       then
+               parent=
+       fi &&
+       T=$(git -C "$repo" write-tree) &&
+       shift &&
+       while test $# -gt 0
+       do
+               name=$1 &&
+               test_tick &&
+               if test -z "$parent"
+               then
+                       oid=$(echo $name | git -C "$repo" commit-tree $T)
+               else
+                       oid=$(echo $name | git -C "$repo" commit-tree -p $parent $T)
+               fi &&
+               eval $name=$oid &&
+               parent=$oid &&
+               shift ||
+               return 1
+       done &&
+       git -C "$repo" update-ref refs/heads/master $oid
+}
+
+# Format the output of git-push, git-show-ref and other commands to make a
+# user-friendly and stable text.  We can easily prepare the expect text
+# without having to worry about future changes of the commit ID and spaces
+# of the output.
+make_user_friendly_and_stable_output () {
+       sed \
+               -e "s/  *\$//" \
+               -e "s/   */ /g" \
+               -e "s/  /    /g" \
+               -e "s/$A/<COMMIT-A>/g" \
+               -e "s/$B/<COMMIT-B>/g" \
+               -e "s/$ZERO_OID/<ZERO-OID>/g" \
+               -e "s/$(echo $A | cut -c1-7)[0-9a-f]*/<OID-A>/g" \
+               -e "s/$(echo $B | cut -c1-7)[0-9a-f]*/<OID-B>/g" \
+               -e "s#To $URL_PREFIX/upstream.git#To <URL/of/upstream.git>#"
+}
+
+setup_upstream_and_workbench () {
+       # Upstream  after setup : master(B)  foo(A)  bar(A)  baz(A)
+       # Workbench after setup : master(A)
+       test_expect_success "setup upstream repository and workbench" '
+               rm -rf upstream.git workbench &&
+               git init --bare upstream.git &&
+               git init workbench &&
+               create_commits_in workbench A B &&
+               (
+                       cd workbench &&
+                       # Try to make a stable fixed width for abbreviated commit ID,
+                       # this fixed-width oid will be replaced with "<OID>".
+                       git config core.abbrev 7 &&
+                       git remote add origin ../upstream.git &&
+                       git update-ref refs/heads/master $A &&
+                       git push origin \
+                               $B:refs/heads/master \
+                               $A:refs/heads/foo \
+                               $A:refs/heads/bar \
+                               $A:refs/heads/baz
+               ) &&
+               git -C "workbench" config advice.pushUpdateRejected false &&
+               upstream=upstream.git
+       '
+}
+
+run_git_push_porcelain_output_test() {
+       case $1 in
+       http)
+               PROTOCOL="HTTP protocol"
+               URL_PREFIX="http://.*"
+               ;;
+       file)
+               PROTOCOL="builtin protocol"
+               URL_PREFIX="\.\."
+               ;;
+       esac
+
+       # Refs of upstream : master(B)  foo(A)  bar(A)  baz(A)
+       # Refs of workbench: master(A)                  baz(A)  next(A)
+       # git-push         : master(A)  NULL    (B)     baz(A)  next(A)
+       test_expect_success "porcelain output of successful git-push ($PROTOCOL)" '
+               (
+                       cd workbench &&
+                       git update-ref refs/heads/master $A &&
+                       git update-ref refs/heads/baz $A &&
+                       git update-ref refs/heads/next $A &&
+                       git push --porcelain --force origin \
+                               master \
+                               :refs/heads/foo \
+                               $B:bar \
+                               baz \
+                               next
+               ) >out &&
+               make_user_friendly_and_stable_output <out >actual &&
+               cat >expect <<-EOF &&
+               To <URL/of/upstream.git>
+               =    refs/heads/baz:refs/heads/baz    [up to date]
+                    <COMMIT-B>:refs/heads/bar    <OID-A>..<OID-B>
+               -    :refs/heads/foo    [deleted]
+               +    refs/heads/master:refs/heads/master    <OID-B>...<OID-A> (forced update)
+               *    refs/heads/next:refs/heads/next    [new branch]
+               Done
+               EOF
+               test_cmp expect actual &&
+
+               git -C "$upstream" show-ref >out &&
+               make_user_friendly_and_stable_output <out >actual &&
+               cat >expect <<-EOF &&
+               <COMMIT-B> refs/heads/bar
+               <COMMIT-A> refs/heads/baz
+               <COMMIT-A> refs/heads/master
+               <COMMIT-A> refs/heads/next
+               EOF
+               test_cmp expect actual
+       '
+
+       # Refs of upstream : master(A)  bar(B)  baz(A)  next(A)
+       # Refs of workbench: master(B)  bar(A)  baz(A)  next(A)
+       # git-push         : master(B)  bar(A)  NULL    next(A)
+       test_expect_success "atomic push failed ($PROTOCOL)" '
+               (
+                       cd workbench &&
+                       git update-ref refs/heads/master $B &&
+                       git update-ref refs/heads/bar $A &&
+                       test_must_fail git push --atomic --porcelain origin \
+                               master \
+                               bar \
+                               :baz \
+                               next
+               ) >out &&
+               make_user_friendly_and_stable_output <out >actual &&
+               cat >expect <<-EOF &&
+               To <URL/of/upstream.git>
+               =    refs/heads/next:refs/heads/next    [up to date]
+               !    refs/heads/bar:refs/heads/bar    [rejected] (non-fast-forward)
+               !    (delete):refs/heads/baz    [rejected] (atomic push failed)
+               !    refs/heads/master:refs/heads/master    [rejected] (atomic push failed)
+               Done
+               EOF
+               test_cmp expect actual &&
+
+               git -C "$upstream" show-ref >out &&
+               make_user_friendly_and_stable_output <out >actual &&
+               cat >expect <<-EOF &&
+               <COMMIT-B> refs/heads/bar
+               <COMMIT-A> refs/heads/baz
+               <COMMIT-A> refs/heads/master
+               <COMMIT-A> refs/heads/next
+               EOF
+               test_cmp expect actual
+       '
+       test_expect_success "prepare pre-receive hook ($PROTOCOL)" '
+               write_script "$upstream/hooks/pre-receive" <<-EOF
+               exit 1
+               EOF
+       '
+
+       # Refs of upstream : master(A)  bar(B)  baz(A)  next(A)
+       # Refs of workbench: master(B)  bar(A)  baz(A)  next(A)
+       # git-push         : master(B)  bar(A)  NULL    next(A)
+       test_expect_success "pre-receive hook declined ($PROTOCOL)" '
+               (
+                       cd workbench &&
+                       git update-ref refs/heads/master $B &&
+                       git update-ref refs/heads/bar $A &&
+                       test_must_fail git push --porcelain --force origin \
+                               master \
+                               bar \
+                               :baz \
+                               next
+               ) >out &&
+               make_user_friendly_and_stable_output <out >actual &&
+               cat >expect <<-EOF &&
+               To <URL/of/upstream.git>
+               =    refs/heads/next:refs/heads/next    [up to date]
+               !    refs/heads/bar:refs/heads/bar    [remote rejected] (pre-receive hook declined)
+               !    :refs/heads/baz    [remote rejected] (pre-receive hook declined)
+               !    refs/heads/master:refs/heads/master    [remote rejected] (pre-receive hook declined)
+               Done
+               EOF
+               test_cmp expect actual &&
+
+               git -C "$upstream" show-ref >out &&
+               make_user_friendly_and_stable_output <out >actual &&
+               cat >expect <<-EOF &&
+               <COMMIT-B> refs/heads/bar
+               <COMMIT-A> refs/heads/baz
+               <COMMIT-A> refs/heads/master
+               <COMMIT-A> refs/heads/next
+               EOF
+               test_cmp expect actual
+       '
+
+       test_expect_success "remove pre-receive hook ($PROTOCOL)" '
+               rm "$upstream/hooks/pre-receive"
+       '
+
+       # Refs of upstream : master(A)  bar(B)  baz(A)  next(A)
+       # Refs of workbench: master(B)  bar(A)  baz(A)  next(A)
+       # git-push         : master(B)  bar(A)  NULL    next(A)
+       test_expect_success "non-fastforward push ($PROTOCOL)" '
+               (
+                       cd workbench &&
+                       test_must_fail git push --porcelain origin \
+                               master \
+                               bar \
+                               :baz \
+                               next
+               ) >out &&
+               make_user_friendly_and_stable_output <out >actual &&
+               cat >expect <<-EOF &&
+               To <URL/of/upstream.git>
+               =    refs/heads/next:refs/heads/next    [up to date]
+               -    :refs/heads/baz    [deleted]
+                    refs/heads/master:refs/heads/master    <OID-A>..<OID-B>
+               !    refs/heads/bar:refs/heads/bar    [rejected] (non-fast-forward)
+               Done
+               EOF
+               test_cmp expect actual &&
+
+               git -C "$upstream" show-ref >out &&
+               make_user_friendly_and_stable_output <out >actual &&
+               cat >expect <<-EOF &&
+               <COMMIT-B> refs/heads/bar
+               <COMMIT-B> refs/heads/master
+               <COMMIT-A> refs/heads/next
+               EOF
+               test_cmp expect actual
+       '
+}
+
+# Initialize the upstream repository and local workbench.
+setup_upstream_and_workbench
+
+# Run git-push porcelain test on builtin protocol
+run_git_push_porcelain_output_test file
+
+ROOT_PATH="$PWD"
+. "$TEST_DIRECTORY"/lib-gpg.sh
+. "$TEST_DIRECTORY"/lib-httpd.sh
+. "$TEST_DIRECTORY"/lib-terminal.sh
+start_httpd
+
+# Re-initialize the upstream repository and local workbench.
+setup_upstream_and_workbench
+
+test_expect_success "setup for http" '
+       git -C upstream.git config http.receivepack true &&
+       upstream="$HTTPD_DOCUMENT_ROOT_PATH/upstream.git" &&
+       mv upstream.git "$upstream" &&
+
+       git -C workbench remote set-url origin $HTTPD_URL/smart/upstream.git
+'
+
+setup_askpass_helper
+
+# Run git-push porcelain test on HTTP protocol
+run_git_push_porcelain_output_test http
+
+test_done
index b811d89cfd6df25fb2e42db6f05a69cf0b7a6c3b..50485300eb19ea757dac4ee36ec395329d33e1f0 100755 (executable)
@@ -248,9 +248,7 @@ test_expect_success 'fetch can handle previously-fetched .idx files' '
 '
 
 test_expect_success 'did not use upload-pack service' '
-       test_might_fail grep '/git-upload-pack' <"$HTTPD_ROOT_PATH"/access.log >act &&
-       : >exp &&
-       test_cmp exp act
+       ! grep "/git-upload-pack" "$HTTPD_ROOT_PATH/access.log"
 '
 
 test_expect_success 'git client shows text/plain errors' '
@@ -321,11 +319,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 4a110b307ee53e623bff98a68307c4947139c39f..3f4ac71f83b571a61c14b5627d843b73cb244f0e 100755 (executable)
@@ -53,15 +53,20 @@ test_expect_success 'setup' '
        test_commit c1 &&
        hash_head=$(git rev-parse HEAD) &&
        hash_prev=$(git rev-parse HEAD~1) &&
-       printf "want %s" "$hash_head" | packetize >fetch_body &&
-       printf 0000 >>fetch_body &&
-       printf "have %s" "$hash_prev" | packetize >>fetch_body &&
-       printf done | packetize >>fetch_body &&
+       {
+               packetize "want $hash_head" &&
+               printf 0000 &&
+               packetize "have $hash_prev" &&
+               packetize "done"
+       } >fetch_body &&
        test_copy_bytes 10 <fetch_body >fetch_body.trunc &&
        hash_next=$(git commit-tree -p HEAD -m next HEAD^{tree}) &&
-       printf "%s %s refs/heads/newbranch\\0report-status\\n" "$ZERO_OID" "$hash_next" | packetize >push_body &&
-       printf 0000 >>push_body &&
-       echo "$hash_next" | git pack-objects --stdout >>push_body &&
+       {
+               printf "%s %s refs/heads/newbranch\\0report-status\\n" \
+                       "$ZERO_OID" "$hash_next" | packetize &&
+               printf 0000 &&
+               echo "$hash_next" | git pack-objects --stdout
+       } >push_body &&
        test_copy_bytes 10 <push_body >push_body.trunc &&
        : >empty_body
 '
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 9108ff6fbd6f52d9df6b9ec52226a71ced8fdc06..6d5a977fcba60f70d2a7329eba4c70e7ac66e925 100755 (executable)
@@ -16,7 +16,7 @@ test_expect_success 'setup' '
 
 test_expect_success '"verify" needs a worktree' '
        git bundle create tip.bundle -1 master &&
-       test_must_fail nongit git bundle verify ../tip.bundle 2>err &&
+       nongit test_must_fail git bundle verify ../tip.bundle 2>err &&
        test_i18ngrep "need a repository" err
 '
 
index 60c1ba951b7d4178708574d10986659749925059..8e0fd398236b5fd503565db7c2e266aef744476a 100755 (executable)
@@ -92,24 +92,17 @@ test_expect_success 'clone -c remote.<remote>.fetch=<refspec> --origin=<name>' '
        test_cmp expect actual
 '
 
-# Tests for the hidden file attribute on windows
-is_hidden () {
-       # Use the output of `attrib`, ignore the absolute path
-       case "$(attrib "$1")" in *H*?:*) return 0;; esac
-       return 1
-}
-
 test_expect_success MINGW 'clone -c core.hideDotFiles' '
        test_commit attributes .gitattributes "" &&
        rm -rf child &&
        git clone -c core.hideDotFiles=false . child &&
-       ! is_hidden child/.gitattributes &&
+       ! test_path_is_hidden child/.gitattributes &&
        rm -rf child &&
        git clone -c core.hideDotFiles=dotGitOnly . child &&
-       ! is_hidden child/.gitattributes &&
+       ! test_path_is_hidden child/.gitattributes &&
        rm -rf child &&
        git clone -c core.hideDotFiles=true . child &&
-       is_hidden child/.gitattributes
+       test_path_is_hidden child/.gitattributes
 '
 
 test_done
index e36ac01661d1b5cd26b0ae4fe615b9ccf44e02c8..e3b436d8ae791c587e92ac8d665b189ed1f82220 100755 (executable)
@@ -71,9 +71,9 @@ test_expect_success 'by default all branches will be kept updated' '
        (
                cd dir_all &&
                git fetch &&
-               git for-each-ref refs/remotes/origin |
+               git for-each-ref refs/remotes/origin >refs &&
                sed -e "/HEAD$/d" \
-                   -e "s|/remotes/origin/|/heads/|" >../actual
+                   -e "s|/remotes/origin/|/heads/|" refs >../actual
        ) &&
        # follow both master and side
        git for-each-ref refs/heads >expect &&
@@ -87,7 +87,7 @@ test_expect_success 'by default no tags will be kept updated' '
                git for-each-ref refs/tags >../actual
        ) &&
        git for-each-ref refs/tags >expect &&
-       test_must_fail test_cmp expect actual &&
+       ! test_cmp expect actual &&
        test_line_count = 2 actual
 '
 
@@ -104,9 +104,9 @@ test_expect_success '--single-branch while HEAD pointing at master' '
        (
                cd dir_master &&
                git fetch --force &&
-               git for-each-ref refs/remotes/origin |
+               git for-each-ref refs/remotes/origin >refs &&
                sed -e "/HEAD$/d" \
-                   -e "s|/remotes/origin/|/heads/|" >../actual
+                   -e "s|/remotes/origin/|/heads/|" refs >../actual
        ) &&
        # only follow master
        git for-each-ref refs/heads/master >expect &&
@@ -126,9 +126,9 @@ test_expect_success '--single-branch while HEAD pointing at master and --no-tags
        (
                cd dir_master_no_tags &&
                git fetch &&
-               git for-each-ref refs/remotes/origin |
+               git for-each-ref refs/remotes/origin >refs &&
                sed -e "/HEAD$/d" \
-                   -e "s|/remotes/origin/|/heads/|" >../actual
+                   -e "s|/remotes/origin/|/heads/|" refs >../actual
        ) &&
        # only follow master
        git for-each-ref refs/heads/master >expect &&
@@ -156,9 +156,9 @@ test_expect_success '--single-branch while HEAD pointing at side' '
        (
                cd dir_side &&
                git fetch &&
-               git for-each-ref refs/remotes/origin |
+               git for-each-ref refs/remotes/origin >refs &&
                sed -e "/HEAD$/d" \
-                   -e "s|/remotes/origin/|/heads/|" >../actual
+                   -e "s|/remotes/origin/|/heads/|" refs >../actual
        ) &&
        # only follow side
        git for-each-ref refs/heads/side >expect &&
@@ -169,9 +169,9 @@ test_expect_success '--single-branch with explicit --branch side' '
        (
                cd dir_side2 &&
                git fetch &&
-               git for-each-ref refs/remotes/origin |
+               git for-each-ref refs/remotes/origin >refs &&
                sed -e "/HEAD$/d" \
-                   -e "s|/remotes/origin/|/heads/|" >../actual
+                   -e "s|/remotes/origin/|/heads/|" refs >../actual
        ) &&
        # only follow side
        git for-each-ref refs/heads/side >expect &&
@@ -223,9 +223,9 @@ test_expect_success '--single-branch with detached' '
        (
                cd dir_detached &&
                git fetch &&
-               git for-each-ref refs/remotes/origin |
+               git for-each-ref refs/remotes/origin >refs &&
                sed -e "/HEAD$/d" \
-                   -e "s|/remotes/origin/|/heads/|" >../actual
+                   -e "s|/remotes/origin/|/heads/|" refs >../actual
        ) &&
        # nothing
        test_must_be_empty actual
index 77bb91e97692272b572cd0dd89053e0bb8c7e050..88002b24afc65f4fcab2ee059a208bf1b141a4d8 100755 (executable)
@@ -49,7 +49,7 @@ test_expect_success 'do partial clone 1' '
 test_expect_success 'verify that .promisor file contains refs fetched' '
        ls pc1/.git/objects/pack/pack-*.promisor >promisorlist &&
        test_line_count = 1 promisorlist &&
-       git -C srv.bare rev-list HEAD >headhash &&
+       git -C srv.bare rev-parse --verify HEAD >headhash &&
        grep "$(cat headhash) HEAD" $(cat promisorlist) &&
        grep "$(cat headhash) refs/heads/master" $(cat promisorlist)
 '
@@ -415,6 +415,14 @@ test_expect_success 'verify fetch downloads only one pack when updating refs' '
        test_line_count = 3 pack-list
 '
 
+test_expect_success 'single-branch tag following respects partial clone' '
+       git clone --single-branch -b B --filter=blob:none \
+               "file://$(pwd)/srv.bare" single &&
+       git -C single rev-parse --verify refs/tags/B &&
+       git -C single rev-parse --verify refs/tags/A &&
+       test_must_fail git -C single rev-parse --verify refs/tags/C
+'
+
 . "$TEST_DIRECTORY"/lib-httpd.sh
 start_httpd
 
index 7fba3063bf9150687df4bcbd56cdfecf50c8e289..a34460f7d82ea0ce894dab17ad3b6a7f7765b408 100755 (executable)
@@ -13,10 +13,7 @@ get_actual_refs () {
 }
 
 get_actual_commits () {
-       sed -n -e '/packfile/,/0000/{
-               /packfile/d
-               p
-               }' <out | test-tool pkt-line unpack-sideband >o.pack &&
+       test-tool pkt-line unpack-sideband <out >o.pack &&
        git index-pack o.pack &&
        git verify-pack -v o.idx >objs &&
        grep commit objs | cut -d" " -f1 | sort >actual_commits
diff --git a/t/t5704-protocol-violations.sh b/t/t5704-protocol-violations.sh
new file mode 100755 (executable)
index 0000000..950cfb2
--- /dev/null
@@ -0,0 +1,33 @@
+#!/bin/sh
+
+test_description='Test responses to violations of the network protocol. In most
+of these cases it will generally be acceptable for one side to break off
+communications if the other side says something unexpected. We are mostly
+making sure that we do not segfault or otherwise behave badly.'
+. ./test-lib.sh
+
+test_expect_success 'extra delim packet in v2 ls-refs args' '
+       {
+               packetize command=ls-refs &&
+               printf 0001 &&
+               # protocol expects 0000 flush here
+               printf 0001
+       } >input &&
+       test_must_fail env GIT_PROTOCOL=version=2 \
+               git upload-pack . <input 2>err &&
+       test_i18ngrep "expected flush after ls-refs arguments" err
+'
+
+test_expect_success 'extra delim packet in v2 fetch args' '
+       {
+               packetize command=fetch &&
+               printf 0001 &&
+               # protocol expects 0000 flush here
+               printf 0001
+       } >input &&
+       test_must_fail env GIT_PROTOCOL=version=2 \
+               git upload-pack . <input 2>err &&
+       test_i18ngrep "expected flush after fetch arguments" err
+'
+
+test_done
index 121e5c6edb0a21eb8a6d72d01c7edd5f45d11b2f..0f04b6cddb723316b0edf1135086405a09e4205b 100755 (executable)
@@ -11,9 +11,15 @@ test_description='Test remote-helper import and export commands'
 PATH="$TEST_DIRECTORY/t5801:$PATH"
 
 compare_refs() {
+       fail= &&
+       if test "x$1" = 'x!'
+       then
+               fail='!' &&
+               shift
+       fi &&
        git --git-dir="$1/.git" rev-parse --verify $2 >expect &&
        git --git-dir="$3/.git" rev-parse --verify $4 >actual &&
-       test_cmp expect actual
+       eval $fail test_cmp expect actual
 }
 
 test_expect_success 'setup repository' '
@@ -189,7 +195,7 @@ test_expect_success GPG 'push signed tag' '
        git push origin signed-tag
        ) &&
        compare_refs local signed-tag^{} server signed-tag^{} &&
-       test_must_fail compare_refs local signed-tag server signed-tag
+       compare_refs ! local signed-tag server signed-tag
 '
 
 test_expect_success GPG 'push signed tag with signed-tags capability' '
index a10f0df02b0ec8acdcaf7c90d02da4a9c20ffb1b..b6fa43ace0184f6504aeffd813964f5ac95a9fef 100755 (executable)
@@ -154,4 +154,124 @@ test_expect_success '--full-diff is not affected by --parents' '
        test_cmp expected actual
 '
 
+#
+# Create a new history to demonstrate the value of --show-pulls
+# with respect to the subtleties of simplified history, --full-history,
+# and --simplify-merges.
+#
+#   .-A---M-----C--N---O---P
+#  /     / \  \  \/   /   /
+# I     B   \  R-'`-Z'   /
+#  \   /     \/         /
+#   \ /      /\        /
+#    `---X--'  `---Y--'
+#
+# This example is explained in Documentation/rev-list-options.txt
+
+test_expect_success 'rebuild repo' '
+       rm -rf .git * &&
+       git init &&
+       git switch -c main &&
+
+       echo base >file &&
+       git add file &&
+       test_commit I &&
+
+       echo A >file &&
+       git add file &&
+       test_commit A &&
+
+       git switch -c branchB I &&
+       echo B >file &&
+       git add file &&
+       test_commit B &&
+
+       git switch main &&
+       test_must_fail git merge -m "M" B &&
+       echo A >file &&
+       echo B >>file &&
+       git add file &&
+       git merge --continue &&
+       note M &&
+
+       echo C >other &&
+       git add other &&
+       test_commit C &&
+
+       git switch -c branchX I &&
+       echo X >file &&
+       git add file &&
+       test_commit X &&
+
+       git switch -c branchR M &&
+       git merge -m R -Xtheirs X &&
+       note R &&
+
+       git switch main &&
+       git merge -m N R &&
+       note N &&
+
+       git switch -c branchY M &&
+       echo Y >y &&
+       git add y &&
+       test_commit Y &&
+
+       git switch -c branchZ C &&
+       echo Z >z &&
+       git add z &&
+       test_commit Z &&
+
+       git switch main &&
+       git merge -m O Z &&
+       note O &&
+
+       git merge -m P Y &&
+       note P
+'
+
+check_result 'X I' -- file
+check_result 'N R X I' --show-pulls -- file
+
+check_result 'P O N R X M B A I' --full-history --topo-order -- file
+check_result 'N R X M B A I' --simplify-merges --topo-order --show-pulls -- file
+check_result 'R X M B A I' --simplify-merges --topo-order -- file
+check_result 'N M A I' --first-parent -- file
+check_result 'N M A I' --first-parent --show-pulls -- file
+
+# --ancestry-path implies --full-history
+check_result 'P O N R M' --topo-order \
+       --ancestry-path A..HEAD -- file
+check_result 'P O N R M' --topo-order \
+       --show-pulls \
+       --ancestry-path A..HEAD -- file
+check_result 'P O N R M' --topo-order \
+       --full-history \
+       --ancestry-path A..HEAD -- file
+check_result 'R M' --topo-order \
+       --simplify-merges \
+       --ancestry-path A..HEAD -- file
+check_result 'N R M' --topo-order \
+       --simplify-merges --show-pulls \
+       --ancestry-path A..HEAD -- file
+
+test_expect_success 'log --graph --simplify-merges --show-pulls' '
+       cat >expect <<-\EOF &&
+       * N
+       *   R
+       |\  
+       | * X
+       * |   M
+       |\ \  
+       | * | B
+       | |/  
+       * / A
+       |/  
+       * I
+       EOF
+       git log --graph --pretty="%s" \
+               --simplify-merges --show-pulls \
+               -- file >actual &&
+       test_cmp expect actual
+'
+
 test_done
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 190ae149cf3cb6daa0a89d50a5a44ccafdd2aaec..6738497ea7a851b2395eb422fa8686dc6b4da717 100755 (executable)
@@ -18,7 +18,7 @@ GIT_FORCE_UNTRACKED_CACHE=true
 export GIT_FORCE_UNTRACKED_CACHE
 
 sync_mtime () {
-       find . -type d -ls >/dev/null
+       find . -type d -exec ls -ld {} + >/dev/null
 }
 
 avoid_racy() {
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 352c213d52e2e4a50808128c027eba7f1ac163df..3103be8a32393f736481139dd70793ac4e4d97b7 100644 (file)
@@ -905,7 +905,7 @@ test_expect_code () {
 # - not all diff versions understand "-u"
 
 test_cmp() {
-       $GIT_TEST_CMP "$@"
+       eval "$GIT_TEST_CMP" '"$@"'
 }
 
 # Check that the given config key has the expected value.
@@ -1362,14 +1362,22 @@ nongit () {
        )
 } 7>&2 2>&4
 
-# convert stdin to pktline representation; note that empty input becomes an
-# empty packet, not a flush packet (for that you can just print 0000 yourself).
+# convert function arguments or stdin (if not arguments given) to pktline
+# representation. If multiple arguments are given, they are separated by
+# whitespace and put in a single packet. Note that data containing NULs must be
+# given on stdin, and that empty input becomes an empty packet, not a flush
+# packet (for that you can just print 0000 yourself).
 packetize() {
-       cat >packetize.tmp &&
-       len=$(wc -c <packetize.tmp) &&
-       printf '%04x%s' "$(($len + 4))" &&
-       cat packetize.tmp &&
-       rm -f packetize.tmp
+       if test $# -gt 0
+       then
+               packet="$*"
+               printf '%04x%s' "$((4 + ${#packet}))" "$packet"
+       else
+               perl -e '
+                       my $packet = do { local $/; <STDIN> };
+                       printf "%04x%s", 4 + length($packet), $packet;
+               '
+       fi
 }
 
 # Parse the input as a series of pktlines, writing the result to stdout.
@@ -1543,3 +1551,13 @@ test_bitmap_traversal () {
        test_cmp "$1.normalized" "$2.normalized" &&
        rm -f "$1.normalized" "$2.normalized"
 }
+
+# Tests for the hidden file attribute on Windows
+test_path_is_hidden () {
+       test_have_prereq MINGW ||
+       BUG "test_path_is_hidden can only be used on Windows"
+
+       # Use the output of `attrib`, ignore the absolute path
+       case "$("$SYSTEMROOT"/system32/attrib "$1")" in *H*?:*) return 0;; esac
+       return 1
+}
index 0ea1e5a05edd86b3923cb82fd503b34ebbe93254..0bb1105ec3791eb6ccf254c34e4e25b8eb0aecda 100644 (file)
@@ -78,20 +78,23 @@ then
        exit 1
 fi
 
-# Parse options while taking care to leave $@ intact, so we will still
-# have all the original command line options when executing the test
-# script again for '--tee' and '--verbose-log' below.
 store_arg_to=
-prev_opt=
-for opt
-do
-       if test -n "$store_arg_to"
+opt_required_arg=
+# $1: option string
+# $2: name of the var where the arg will be stored
+mark_option_requires_arg () {
+       if test -n "$opt_required_arg"
        then
-               eval $store_arg_to=\$opt
-               store_arg_to=
-               prev_opt=
-               continue
+               echo "error: options that require args cannot be bundled" \
+                       "together: '$opt_required_arg' and '$1'" >&2
+               exit 1
        fi
+       opt_required_arg=$1
+       store_arg_to=$2
+}
+
+parse_option () {
+       local opt="$1"
 
        case "$opt" in
        -d|--d|--de|--deb|--debu|--debug)
@@ -101,7 +104,7 @@ do
        -l|--l|--lo|--lon|--long|--long-|--long-t|--long-te|--long-tes|--long-test|--long-tests)
                GIT_TEST_LONG=t; export GIT_TEST_LONG ;;
        -r)
-               store_arg_to=run_list
+               mark_option_requires_arg "$opt" run_list
                ;;
        --run=*)
                run_list=${opt#--*=} ;;
@@ -185,12 +188,42 @@ do
        *)
                echo "error: unknown test option '$opt'" >&2; exit 1 ;;
        esac
+}
 
-       prev_opt=$opt
+# Parse options while taking care to leave $@ intact, so we will still
+# have all the original command line options when executing the test
+# script again for '--tee' and '--verbose-log' later.
+for opt
+do
+       if test -n "$store_arg_to"
+       then
+               eval $store_arg_to=\$opt
+               store_arg_to=
+               opt_required_arg=
+               continue
+       fi
+
+       case "$opt" in
+       --*|-?)
+               parse_option "$opt" ;;
+       -?*)
+               # bundled short options must be fed separately to parse_option
+               opt=${opt#-}
+               while test -n "$opt"
+               do
+                       extra=${opt#?}
+                       this=${opt%$extra}
+                       opt=$extra
+                       parse_option "-$this"
+               done
+               ;;
+       *)
+               echo "error: unknown test option '$opt'" >&2; exit 1 ;;
+       esac
 done
 if test -n "$store_arg_to"
 then
-       echo "error: $prev_opt requires an argument" >&2
+       echo "error: $opt_required_arg requires an argument" >&2
        exit 1
 fi
 
@@ -494,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='
 '
@@ -882,6 +900,7 @@ maybe_setup_valgrind () {
        fi
 }
 
+trace_level_=0
 want_trace () {
        test "$trace" = t && {
                test "$verbose" = t || test "$verbose_log" = t
@@ -895,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
                $*"
 }
 
@@ -926,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
 
@@ -1085,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"
 
@@ -1383,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 c7b4f14d29a9d0ef38fba7595d15911757b9cbdb..2c6b570077b3980e9ecfeb088d49a125362610f7 100644 (file)
--- a/trace2.c
+++ b/trace2.c
@@ -121,6 +121,7 @@ static void tr2main_atexit_handler(void)
        tr2_sid_release();
        tr2_cmd_name_release();
        tr2_cfg_free_patterns();
+       tr2_cfg_free_env_vars();
        tr2_sysenv_release();
 
        trace2_enabled = 0;
@@ -311,6 +312,14 @@ void trace2_cmd_list_config_fl(const char *file, int line)
        tr2_cfg_list_config_fl(file, line);
 }
 
+void trace2_cmd_list_env_vars_fl(const char *file, int line)
+{
+       if (!trace2_enabled)
+               return;
+
+       tr2_list_env_vars_fl(file, line);
+}
+
 void trace2_cmd_set_config_fl(const char *file, int line, const char *key,
                              const char *value)
 {
index e5e81c05332f1352fd62935e5fe2d87c519f3567..b18bc5529c6e9a4692f30005ec1ca3d1803821b5 100644 (file)
--- a/trace2.h
+++ b/trace2.h
@@ -182,6 +182,19 @@ void trace2_cmd_list_config_fl(const char *file, int line);
 
 #define trace2_cmd_list_config() trace2_cmd_list_config_fl(__FILE__, __LINE__)
 
+/*
+ * Emit one or more 'def_param' events for "important" environment variables.
+ *
+ * Use the TR2_SYSENV_ENV_VARS setting to register a comma-separated list of
+ * environment variables considered important.  For example:
+ *     git config --system trace2.envVars 'GIT_HTTP_USER_AGENT,GIT_CONFIG'
+ * or:
+ *     GIT_TRACE2_ENV_VARS="GIT_HTTP_USER_AGENT,GIT_CONFIG"
+ */
+void trace2_cmd_list_env_vars_fl(const char *file, int line);
+
+#define trace2_cmd_list_env_vars() trace2_cmd_list_env_vars_fl(__FILE__, __LINE__)
+
 /*
  * Emit a "def_param" event for the given config key/value pair IF
  * we consider the key to be "important".
index caa7f06948ab97c09ee9adf82a9588fcc07d6e52..ec9ac1a6efd3f62b4ccae9db8a9d3ac3ceb1c64a 100644 (file)
@@ -7,6 +7,10 @@ static struct strbuf **tr2_cfg_patterns;
 static int tr2_cfg_count_patterns;
 static int tr2_cfg_loaded;
 
+static struct strbuf **tr2_cfg_env_vars;
+static int tr2_cfg_env_vars_count;
+static int tr2_cfg_env_vars_loaded;
+
 /*
  * Parse a string containing a comma-delimited list of config keys
  * or wildcard patterns into a list of strbufs.
@@ -46,6 +50,45 @@ void tr2_cfg_free_patterns(void)
        tr2_cfg_loaded = 0;
 }
 
+/*
+ * Parse a string containing a comma-delimited list of environment variable
+ * names into a list of strbufs.
+ */
+static int tr2_load_env_vars(void)
+{
+       struct strbuf **s;
+       const char *varlist;
+
+       if (tr2_cfg_env_vars_loaded)
+               return tr2_cfg_env_vars_count;
+       tr2_cfg_env_vars_loaded = 1;
+
+       varlist = tr2_sysenv_get(TR2_SYSENV_ENV_VARS);
+       if (!varlist || !*varlist)
+               return tr2_cfg_env_vars_count;
+
+       tr2_cfg_env_vars = strbuf_split_buf(varlist, strlen(varlist), ',', -1);
+       for (s = tr2_cfg_env_vars; *s; s++) {
+               struct strbuf *buf = *s;
+
+               if (buf->len && buf->buf[buf->len - 1] == ',')
+                       strbuf_setlen(buf, buf->len - 1);
+               strbuf_trim_trailing_newline(*s);
+               strbuf_trim(*s);
+       }
+
+       tr2_cfg_env_vars_count = s - tr2_cfg_env_vars;
+       return tr2_cfg_env_vars_count;
+}
+
+void tr2_cfg_free_env_vars(void)
+{
+       if (tr2_cfg_env_vars)
+               strbuf_list_free(tr2_cfg_env_vars);
+       tr2_cfg_env_vars_count = 0;
+       tr2_cfg_env_vars_loaded = 0;
+}
+
 struct tr2_cfg_data {
        const char *file;
        int line;
@@ -79,6 +122,21 @@ void tr2_cfg_list_config_fl(const char *file, int line)
                read_early_config(tr2_cfg_cb, &data);
 }
 
+void tr2_list_env_vars_fl(const char *file, int line)
+{
+       struct strbuf **s;
+
+       if (tr2_load_env_vars() <= 0)
+               return;
+
+       for (s = tr2_cfg_env_vars; *s; s++) {
+               struct strbuf *buf = *s;
+               const char *val = getenv(buf->buf);
+               if (val && *val)
+                       trace2_def_param_fl(file, line, buf->buf, val);
+       }
+}
+
 void tr2_cfg_set_fl(const char *file, int line, const char *key,
                    const char *value)
 {
index d9c98f64ddf2523e764c857302f88ce7fad31c39..a11d71feb5bfb1c0e044ece4dcba6e47d560be2f 100644 (file)
@@ -7,6 +7,12 @@
  */
 void tr2_cfg_list_config_fl(const char *file, int line);
 
+/*
+ * Iterate over all "interesting" environment variables and emit 'def_param'
+ * events for them to TRACE2.
+ */
+void tr2_list_env_vars_fl(const char *file, int line);
+
 /*
  * Emit a "def_param" event for the given key/value pair IF we consider
  * the key to be "interesting".
@@ -16,4 +22,6 @@ void tr2_cfg_set_fl(const char *file, int line, const char *key,
 
 void tr2_cfg_free_patterns(void);
 
+void tr2_cfg_free_env_vars(void);
+
 #endif /* TR2_CFG_H */
index 3c3792eca2e254499d30f6e7184e6cc6b12238c4..a380dcf9105e8d7b0022b34508ca384923b2fcd6 100644 (file)
@@ -29,6 +29,8 @@ struct tr2_sysenv_entry {
 static struct tr2_sysenv_entry tr2_sysenv_settings[] = {
        [TR2_SYSENV_CFG_PARAM]     = { "GIT_TRACE2_CONFIG_PARAMS",
                                       "trace2.configparams" },
+       [TR2_SYSENV_ENV_VARS]      = { "GIT_TRACE2_ENV_VARS",
+                                      "trace2.envvars" },
 
        [TR2_SYSENV_DST_DEBUG]     = { "GIT_TRACE2_DST_DEBUG",
                                       "trace2.destinationdebug" },
index d4364a7b85a51744b302389f6af937e8c492aaef..3292ee15bc9676ec5c1ffe79a0db041b39ee9eac 100644 (file)
@@ -11,6 +11,7 @@
  */
 enum tr2_sysenv_variable {
        TR2_SYSENV_CFG_PARAM = 0,
+       TR2_SYSENV_ENV_VARS,
 
        TR2_SYSENV_DST_DEBUG,
 
index 20a7185ec40e1cf4612c259019f325208632373b..a46afcb69db615d2f8917f852db28c3dca501de0 100644 (file)
@@ -894,6 +894,7 @@ static int push_refs_with_push(struct transport *transport,
                case REF_STATUS_REJECT_STALE:
                case REF_STATUS_REJECT_ALREADY_EXISTS:
                        if (atomic) {
+                               reject_atomic_push(remote_refs, mirror);
                                string_list_clear(&cas_options, 0);
                                return 0;
                        } else
@@ -1488,3 +1489,25 @@ int bidirectional_transfer_loop(int input, int output)
 
        return tloop_spawnwait_tasks(&state);
 }
+
+void reject_atomic_push(struct ref *remote_refs, int mirror_mode)
+{
+       struct ref *ref;
+
+       /* Mark other refs as failed */
+       for (ref = remote_refs; ref; ref = ref->next) {
+               if (!ref->peer_ref && !mirror_mode)
+                       continue;
+
+               switch (ref->status) {
+               case REF_STATUS_NONE:
+               case REF_STATUS_OK:
+               case REF_STATUS_EXPECTING_REPORT:
+                       ref->status = REF_STATUS_ATOMIC_PUSH_FAILED;
+                       continue;
+               default:
+                       break; /* do nothing */
+               }
+       }
+       return;
+}
index 1fdc7dac1a6230bdf3492cbebd6642fc825dac5f..15f5ba4e8f22c69959357bc8a7fe073678fe8862 100644 (file)
@@ -16,7 +16,7 @@
 #include "url.h"
 #include "submodule.h"
 #include "string-list.h"
-#include "sha1-array.h"
+#include "oid-array.h"
 #include "sigchain.h"
 #include "transport-internal.h"
 #include "protocol.h"
@@ -715,7 +715,15 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re
 
        close(data->fd[1]);
        close(data->fd[0]);
-       ret |= finish_connect(data->conn);
+       /*
+        * Atomic push may abort the connection early and close the pipe,
+        * which may cause an error for `finish_connect()`. Ignore this error
+        * for atomic git-push.
+        */
+       if (ret || args.atomic)
+               finish_connect(data->conn);
+       else
+               ret = finish_connect(data->conn);
        data->conn = NULL;
        data->got_remote_heads = 0;
 
@@ -1240,20 +1248,6 @@ int transport_push(struct repository *r,
                err = push_had_errors(remote_refs);
                ret = push_ret | err;
 
-               if ((flags & TRANSPORT_PUSH_ATOMIC) && err) {
-                       struct ref *it;
-                       for (it = remote_refs; it; it = it->next)
-                               switch (it->status) {
-                               case REF_STATUS_NONE:
-                               case REF_STATUS_UPTODATE:
-                               case REF_STATUS_OK:
-                                       it->status = REF_STATUS_ATOMIC_PUSH_FAILED;
-                                       break;
-                               default:
-                                       break;
-                               }
-               }
-
                if (!quiet || err)
                        transport_print_push_status(transport->url, remote_refs,
                                        verbose | porcelain, porcelain,
index e0131daab987f582801972fe84aa412d235c89f7..4298c855be66bb41c41053a1ca9fced7b077a389 100644 (file)
@@ -265,4 +265,7 @@ int transport_refs_pushed(struct ref *ref);
 void transport_print_push_status(const char *dest, struct ref *refs,
                  int verbose, int porcelain, unsigned int *reject_reasons);
 
+/* common method used by transport-helper.c and send-pack.c */
+void reject_atomic_push(struct ref *refs, int mirror_mode);
+
 #endif
index 1ecdab330408a1cca7f703b6f2dc8d9b11ae0261..4c3191b9473c3ec9ea48b8a584d631f8c7c27511 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);
@@ -422,9 +423,8 @@ static int check_updates(struct unpack_trees_options *o)
                                continue;
                        oid_array_append(&to_fetch, &ce->oid);
                }
-               if (to_fetch.nr)
-                       promisor_remote_get_direct(the_repository,
-                                                  to_fetch.oid, to_fetch.nr);
+               promisor_remote_get_direct(the_repository,
+                                          to_fetch.oid, to_fetch.nr);
                oid_array_clear(&to_fetch);
        }
        for (i = 0; i < index->cache_nr; i++) {
@@ -1815,9 +1815,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 +2064,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 +2093,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 c53249cac19a33351f4f747782b71f877fc0692f..902d0ad5e157fde33ec013476086740946e285df 100644 (file)
@@ -1252,7 +1252,7 @@ static void process_args(struct packet_reader *request,
                         struct upload_pack_data *data,
                         struct object_array *want_obj)
 {
-       while (packet_reader_read(request) != PACKET_READ_FLUSH) {
+       while (packet_reader_read(request) == PACKET_READ_NORMAL) {
                const char *arg = request->line;
                const char *p;
 
@@ -1321,6 +1321,9 @@ static void process_args(struct packet_reader *request,
                /* ignore unknown lines maybe? */
                die("unexpected line: '%s'", arg);
        }
+
+       if (request->status != PACKET_READ_FLUSH)
+               die(_("expected flush after fetch arguments"));
 }
 
 static int process_haves(struct oid_array *haves, struct oid_array *common,
index efbe05e5a5b6f1aa9a6b92365a4f987f6078dc04..30ab42df8ef2b0a93b007b5afb484ccd4102c3b0 100644 (file)
@@ -222,7 +222,7 @@ static struct userdiff_driver driver_false = {
        { NULL, 0 }
 };
 
-static struct userdiff_driver *userdiff_find_by_namelen(const char *k, int len)
+static struct userdiff_driver *userdiff_find_by_namelen(const char *k, size_t len)
 {
        int i;
        for (i = 0; i < ndrivers; i++) {
@@ -266,7 +266,7 @@ int userdiff_config(const char *k, const char *v)
 {
        struct userdiff_driver *drv;
        const char *name, *type;
-       int namelen;
+       size_t namelen;
 
        if (parse_config_key(k, "diff", &name, &namelen, &type) || !name)
                return 0;
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 e1eaef2e1641457fabc8ab00b68d3b6b3f0e2354..3a1c0e052677dca8d1bc19121d609e0c226e88ee 100644 (file)
--- a/wrapper.c
+++ b/wrapper.c
@@ -218,7 +218,7 @@ ssize_t xread(int fd, void *buf, size_t len)
 {
        ssize_t nr;
        if (len > MAX_IO_SIZE)
-           len = MAX_IO_SIZE;
+               len = MAX_IO_SIZE;
        while (1) {
                nr = read(fd, buf, len);
                if (nr < 0) {
@@ -240,7 +240,7 @@ ssize_t xwrite(int fd, const void *buf, size_t len)
 {
        ssize_t nr;
        if (len > MAX_IO_SIZE)
-           len = MAX_IO_SIZE;
+               len = MAX_IO_SIZE;
        while (1) {
                nr = write(fd, buf, len);
                if (nr < 0) {
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;