about summary refs log tree commit diff
path: root/third_party/git/t
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/git/t')
-rw-r--r--third_party/git/t/README84
-rw-r--r--third_party/git/t/chainlint.sed66
-rwxr-xr-xthird_party/git/t/check-non-portable-shell.pl2
-rw-r--r--third_party/git/t/gitweb-lib.sh7
-rw-r--r--third_party/git/t/helper/.gitignore7
-rw-r--r--third_party/git/t/helper/test-advise.c22
-rw-r--r--third_party/git/t/helper/test-bloom.c97
-rw-r--r--third_party/git/t/helper/test-config.c20
-rw-r--r--third_party/git/t/helper/test-date.c27
-rw-r--r--third_party/git/t/helper/test-drop-caches.c11
-rw-r--r--third_party/git/t/helper/test-dump-fsmonitor.c2
-rw-r--r--third_party/git/t/helper/test-dump-split-index.c2
-rw-r--r--third_party/git/t/helper/test-hashmap.c50
-rw-r--r--third_party/git/t/helper/test-lazy-init-name-hash.c12
-rw-r--r--third_party/git/t/helper/test-line-buffer.c81
-rw-r--r--third_party/git/t/helper/test-oid-array.c (renamed from third_party/git/t/helper/test-sha1-array.c)11
-rw-r--r--third_party/git/t/helper/test-parse-options.c2
-rw-r--r--third_party/git/t/helper/test-parse-pathspec-file.c33
-rw-r--r--third_party/git/t/helper/test-path-utils.c118
-rw-r--r--third_party/git/t/helper/test-pkt-line.c6
-rw-r--r--third_party/git/t/helper/test-proc-receive.c176
-rw-r--r--third_party/git/t/helper/test-progress.c74
-rw-r--r--third_party/git/t/helper/test-reach.c6
-rw-r--r--third_party/git/t/helper/test-read-cache.c5
-rw-r--r--third_party/git/t/helper/test-read-graph.c47
-rw-r--r--third_party/git/t/helper/test-read-midx.c8
-rw-r--r--third_party/git/t/helper/test-ref-store.c2
-rw-r--r--third_party/git/t/helper/test-regex.c94
-rw-r--r--third_party/git/t/helper/test-repository.c14
-rw-r--r--third_party/git/t/helper/test-run-command.c341
-rw-r--r--third_party/git/t/helper/test-submodule-nested-repo-config.c6
-rw-r--r--third_party/git/t/helper/test-svn-fe.c52
-rw-r--r--third_party/git/t/helper/test-tool.c9
-rw-r--r--third_party/git/t/helper/test-tool.h8
-rw-r--r--third_party/git/t/helper/test-trace2.c2
-rw-r--r--third_party/git/t/helper/test-windows-named-pipe.c2
-rw-r--r--third_party/git/t/lib-bash.sh6
-rw-r--r--[-rwxr-xr-x]third_party/git/t/lib-credential.sh5
-rw-r--r--third_party/git/t/lib-git-daemon.sh2
-rw-r--r--third_party/git/t/lib-git-p4.sh2
-rw-r--r--third_party/git/t/lib-git-svn.sh21
-rw-r--r--[-rwxr-xr-x]third_party/git/t/lib-gpg.sh110
-rw-r--r--third_party/git/t/lib-httpd.sh6
-rw-r--r--third_party/git/t/lib-httpd/apache.conf14
-rw-r--r--third_party/git/t/lib-httpd/apply-one-time-perl.sh27
-rw-r--r--third_party/git/t/lib-httpd/apply-one-time-sed.sh22
-rw-r--r--third_party/git/t/lib-httpd/incomplete-body-upload-pack-v2-http.sh3
-rw-r--r--third_party/git/t/lib-httpd/incomplete-length-upload-pack-v2-http.sh3
-rw-r--r--third_party/git/t/lib-log-graph.sh28
-rw-r--r--third_party/git/t/lib-pack.sh40
-rw-r--r--third_party/git/t/lib-rebase.sh37
-rw-r--r--[-rwxr-xr-x]third_party/git/t/lib-submodule-update.sh144
-rw-r--r--third_party/git/t/lib-t6000.sh5
-rw-r--r--third_party/git/t/oid-info/hash-info9
-rw-r--r--third_party/git/t/perf/README9
-rwxr-xr-xthird_party/git/t/perf/aggregate.perl21
-rwxr-xr-xthird_party/git/t/perf/bisect_regression2
-rwxr-xr-xthird_party/git/t/perf/p1400-update-ref.sh39
-rwxr-xr-xthird_party/git/t/perf/p5302-pack-index.sh47
-rwxr-xr-xthird_party/git/t/perf/p5303-many-packs.sh23
-rwxr-xr-xthird_party/git/t/perf/p5310-pack-bitmaps.sh36
-rwxr-xr-xthird_party/git/t/perf/p5601-clone-reference.sh (renamed from third_party/git/t/perf/p5600-clone-reference.sh)0
-rwxr-xr-xthird_party/git/t/perf/p9300-fast-import-export.sh23
-rw-r--r--third_party/git/t/perf/perf-lib.sh6
-rwxr-xr-xthird_party/git/t/t0000-basic.sh177
-rwxr-xr-xthird_party/git/t/t0001-init.sh105
-rwxr-xr-xthird_party/git/t/t0002-gitfile.sh2
-rwxr-xr-xthird_party/git/t/t0003-attributes.sh47
-rwxr-xr-xthird_party/git/t/t0006-date.sh6
-rwxr-xr-xthird_party/git/t/t0007-git-var.sh2
-rwxr-xr-xthird_party/git/t/t0008-ignores.sh39
-rwxr-xr-xthird_party/git/t/t0014-alias.sh7
-rwxr-xr-xthird_party/git/t/t0018-advice.sh32
-rwxr-xr-xthird_party/git/t/t0020-crlf.sh18
-rwxr-xr-xthird_party/git/t/t0021-conversion.sh206
-rw-r--r--third_party/git/t/t0021/rot13-filter.pl6
-rwxr-xr-xthird_party/git/t/t0027-auto-crlf.sh2
-rwxr-xr-xthird_party/git/t/t0028-working-tree-encoding.sh45
-rwxr-xr-xthird_party/git/t/t0040-parse-options.sh33
-rwxr-xr-xthird_party/git/t/t0050-filesystem.sh20
-rwxr-xr-xthird_party/git/t/t0060-path-utils.sh47
-rwxr-xr-xthird_party/git/t/t0061-run-command.sh25
-rwxr-xr-xthird_party/git/t/t0064-sha1-array.sh16
-rwxr-xr-xthird_party/git/t/t0067-parse_pathspec_file.sh108
-rwxr-xr-xthird_party/git/t/t0081-line-buffer.sh90
-rwxr-xr-xthird_party/git/t/t0090-cache-tree.sh5
-rwxr-xr-xthird_party/git/t/t0091-bugreport.sh76
-rwxr-xr-xthird_party/git/t/t0095-bloom.sh117
-rwxr-xr-xthird_party/git/t/t0211-trace2-perf.sh4
-rwxr-xr-xthird_party/git/t/t0212-trace2-event.sh56
-rwxr-xr-xthird_party/git/t/t0300-credentials.sh396
-rwxr-xr-xthird_party/git/t/t0302-credential-store.sh91
-rwxr-xr-xthird_party/git/t/t0410-partial-clone.sh136
-rwxr-xr-xthird_party/git/t/t0500-progress-display.sh312
-rwxr-xr-xthird_party/git/t/t1006-cat-file.sh2
-rwxr-xr-xthird_party/git/t/t1011-read-tree-sparse-checkout.sh26
-rwxr-xr-xthird_party/git/t/t1013-read-tree-submodule.sh4
-rwxr-xr-xthird_party/git/t/t1014-read-tree-confusing.sh1
-rwxr-xr-xthird_party/git/t/t1050-large.sh21
-rwxr-xr-xthird_party/git/t/t1090-sparse-checkout-scope.sh1
-rwxr-xr-xthird_party/git/t/t1091-sparse-checkout-builtin.sh629
-rwxr-xr-xthird_party/git/t/t1300-config.sh266
-rwxr-xr-xthird_party/git/t/t1302-repo-version.sh9
-rwxr-xr-xthird_party/git/t/t1305-config-include.sh2
-rwxr-xr-xthird_party/git/t/t1306-xdg-files.sh8
-rwxr-xr-xthird_party/git/t/t1307-config-blob.sh2
-rwxr-xr-xthird_party/git/t/t1308-config-set.sh26
-rwxr-xr-xthird_party/git/t/t1309-early-config.sh9
-rwxr-xr-xthird_party/git/t/t1400-update-ref.sh266
-rwxr-xr-xthird_party/git/t/t1404-update-ref-errors.sh64
-rwxr-xr-xthird_party/git/t/t1405-main-ref-store.sh5
-rwxr-xr-xthird_party/git/t/t1406-submodule-ref-store.sh2
-rwxr-xr-xthird_party/git/t/t1409-avoid-packing-refs.sh16
-rwxr-xr-xthird_party/git/t/t1410-reflog.sh5
-rwxr-xr-xthird_party/git/t/t1414-reflog-walk.sh3
-rwxr-xr-xthird_party/git/t/t1415-worktree-refs.sh18
-rwxr-xr-xthird_party/git/t/t1416-ref-transaction-hooks.sh136
-rwxr-xr-xthird_party/git/t/t1450-fsck.sh76
-rwxr-xr-xthird_party/git/t/t1500-rev-parse.sh24
-rwxr-xr-xthird_party/git/t/t1501-work-tree.sh2
-rwxr-xr-xthird_party/git/t/t1506-rev-parse-diagnosis.sh99
-rwxr-xr-xthird_party/git/t/t1507-rev-parse-upstream.sh116
-rwxr-xr-xthird_party/git/t/t1509-root-work-tree.sh4
-rwxr-xr-xthird_party/git/t/t1512-rev-parse-disambiguation.sh4
-rwxr-xr-xthird_party/git/t/t1600-index.sh35
-rwxr-xr-xthird_party/git/t/t2013-checkout-submodule.sh4
-rwxr-xr-xthird_party/git/t/t2018-checkout-branch.sh107
-rwxr-xr-xthird_party/git/t/t2022-checkout-paths.sh11
-rwxr-xr-xthird_party/git/t/t2024-checkout-dwim.sh28
-rwxr-xr-xthird_party/git/t/t2025-checkout-no-overlay.sh12
-rwxr-xr-xthird_party/git/t/t2026-checkout-pathspec-file.sh163
-rwxr-xr-xthird_party/git/t/t2027-checkout-track.sh24
-rwxr-xr-xthird_party/git/t/t2060-switch.sh8
-rwxr-xr-xthird_party/git/t/t2070-restore.sh39
-rwxr-xr-xthird_party/git/t/t2072-restore-pathspec-file.sh177
-rwxr-xr-xthird_party/git/t/t2107-update-index-basic.sh1
-rwxr-xr-xthird_party/git/t/t2203-add-intent.sh52
-rwxr-xr-xthird_party/git/t/t2400-worktree-add.sh37
-rwxr-xr-xthird_party/git/t/t2401-worktree-prune.sh24
-rwxr-xr-xthird_party/git/t/t2402-worktree-list.sh6
-rwxr-xr-xthird_party/git/t/t2403-worktree-move.sh21
-rwxr-xr-xthird_party/git/t/t2404-worktree-config.sh4
-rwxr-xr-xthird_party/git/t/t2405-worktree-submodule.sh90
-rwxr-xr-xthird_party/git/t/t2406-worktree-repair.sh179
-rwxr-xr-xthird_party/git/t/t3000-ls-files-others.sh137
-rwxr-xr-xthird_party/git/t/t3005-ls-files-relative.sh12
-rwxr-xr-xthird_party/git/t/t3007-ls-files-recurse-submodules.sh1
-rwxr-xr-xthird_party/git/t/t3008-ls-files-lazy-init-name-hash.sh2
-rwxr-xr-xthird_party/git/t/t3011-common-prefixes-and-directory-traversal.sh209
-rwxr-xr-xthird_party/git/t/t3060-ls-files-with-tree.sh4
-rwxr-xr-xthird_party/git/t/t3200-branch.sh108
-rwxr-xr-xthird_party/git/t/t3201-branch-contains.sh82
-rwxr-xr-xthird_party/git/t/t3206-range-diff.sh559
-rw-r--r--third_party/git/t/t3206/history.export31
-rwxr-xr-xthird_party/git/t/t3210-pack-refs.sh2
-rwxr-xr-xthird_party/git/t/t3301-notes.sh400
-rwxr-xr-xthird_party/git/t/t3305-notes-fanout.sh117
-rwxr-xr-xthird_party/git/t/t3306-notes-prune.sh45
-rwxr-xr-xthird_party/git/t/t3308-notes-merge.sh82
-rwxr-xr-xthird_party/git/t/t3309-notes-merge-auto-resolve.sh228
-rwxr-xr-xthird_party/git/t/t3310-notes-merge-manual-resolve.sh106
-rwxr-xr-xthird_party/git/t/t3311-notes-merge-fanout.sh60
-rwxr-xr-xthird_party/git/t/t3400-rebase.sh102
-rwxr-xr-xthird_party/git/t/t3401-rebase-and-am-rename.sh4
-rwxr-xr-xthird_party/git/t/t3402-rebase-merge.sh77
-rwxr-xr-xthird_party/git/t/t3403-rebase-skip.sh111
-rwxr-xr-xthird_party/git/t/t3404-rebase-interactive.sh889
-rwxr-xr-xthird_party/git/t/t3406-rebase-message.sh27
-rwxr-xr-xthird_party/git/t/t3407-rebase-abort.sh6
-rwxr-xr-xthird_party/git/t/t3415-rebase-autosquash.sh169
-rwxr-xr-xthird_party/git/t/t3416-rebase-onto-threedots.sh57
-rwxr-xr-xthird_party/git/t/t3417-rebase-whitespace-fix.sh4
-rwxr-xr-xthird_party/git/t/t3418-rebase-continue.sh14
-rwxr-xr-xthird_party/git/t/t3419-rebase-patch-id.sh113
-rwxr-xr-xthird_party/git/t/t3420-rebase-autostash.sh54
-rwxr-xr-xthird_party/git/t/t3421-rebase-topology-linear.sh85
-rwxr-xr-xthird_party/git/t/t3422-rebase-incompatible-options.sh12
-rwxr-xr-xthird_party/git/t/t3424-rebase-empty.sh170
-rwxr-xr-xthird_party/git/t/t3425-rebase-topology-merges.sh8
-rwxr-xr-xthird_party/git/t/t3426-rebase-submodule.sh10
-rwxr-xr-xthird_party/git/t/t3427-rebase-subtree.sh162
-rwxr-xr-xthird_party/git/t/t3429-rebase-edit-todo.sh53
-rwxr-xr-xthird_party/git/t/t3430-rebase-merges.sh83
-rwxr-xr-xthird_party/git/t/t3431-rebase-fork-point.sh77
-rwxr-xr-xthird_party/git/t/t3432-rebase-fast-forward.sh138
-rwxr-xr-xthird_party/git/t/t3433-rebase-across-mode-change.sh48
-rwxr-xr-xthird_party/git/t/t3434-rebase-i18n.sh84
-rw-r--r--third_party/git/t/t3434/ISO8859-1.txt3
-rw-r--r--third_party/git/t/t3434/eucJP.txt4
-rwxr-xr-xthird_party/git/t/t3435-rebase-gpg-sign.sh71
-rwxr-xr-xthird_party/git/t/t3436-rebase-more-options.sh180
-rwxr-xr-xthird_party/git/t/t3500-cherry.sh23
-rwxr-xr-xthird_party/git/t/t3501-revert-cherry-pick.sh8
-rwxr-xr-xthird_party/git/t/t3504-cherry-pick-rerere.sh6
-rwxr-xr-xthird_party/git/t/t3506-cherry-pick-ff.sh8
-rwxr-xr-xthird_party/git/t/t3507-cherry-pick-conflict.sh75
-rwxr-xr-xthird_party/git/t/t3508-cherry-pick-many-commits.sh2
-rwxr-xr-xthird_party/git/t/t3510-cherry-pick-sequence.sh3
-rwxr-xr-xthird_party/git/t/t3512-cherry-pick-submodule.sh2
-rwxr-xr-xthird_party/git/t/t3513-revert-submodule.sh9
-rwxr-xr-xthird_party/git/t/t3514-cherry-pick-revert-gpg.sh86
-rwxr-xr-xthird_party/git/t/t3600-rm.sh24
-rwxr-xr-xthird_party/git/t/t3601-rm-pathspec-file.sh79
-rwxr-xr-xthird_party/git/t/t3700-add.sh6
-rwxr-xr-xthird_party/git/t/t3701-add-interactive.sh257
-rwxr-xr-xthird_party/git/t/t3704-add-pathspec-file.sh159
-rwxr-xr-xthird_party/git/t/t3800-mktag.sh48
-rwxr-xr-xthird_party/git/t/t3900-i18n-commit.sh37
-rwxr-xr-xthird_party/git/t/t3903-stash.sh85
-rwxr-xr-xthird_party/git/t/t3904-stash-patch.sh6
-rwxr-xr-xthird_party/git/t/t3905-stash-include-untracked.sh4
-rwxr-xr-xthird_party/git/t/t3906-stash-submodule.sh51
-rwxr-xr-xthird_party/git/t/t3908-stash-in-worktree.sh27
-rwxr-xr-xthird_party/git/t/t3909-stash-pathspec-file.sh100
-rwxr-xr-xthird_party/git/t/t4000-diff-format.sh2
-rwxr-xr-xthird_party/git/t/t4002-diff-basic.sh365
-rwxr-xr-xthird_party/git/t/t4005-diff-rename-2.sh4
-rwxr-xr-xthird_party/git/t/t4009-diff-rename-4.sh19
-rwxr-xr-xthird_party/git/t/t4010-diff-pathspec.sh24
-rwxr-xr-xthird_party/git/t/t4011-diff-symlink.sh40
-rwxr-xr-xthird_party/git/t/t4013-diff-various.sh83
-rw-r--r--third_party/git/t/t4013/diff.diff-tree_--format=%N_note6
-rw-r--r--third_party/git/t/t4013/diff.diff-tree_--pretty_--notes_note12
-rw-r--r--third_party/git/t/t4013/diff.diff-tree_--pretty_note9
-rw-r--r--third_party/git/t/t4013/diff.diff-tree_--root_-p_--abbrev=10_initial29
-rw-r--r--third_party/git/t/t4013/diff.diff-tree_--root_-p_--full-index_--abbrev=10_initial29
-rw-r--r--third_party/git/t/t4013/diff.diff-tree_--root_-p_--full-index_initial29
-rw-r--r--third_party/git/t/t4013/diff.log_--decorate=full_--all15
-rw-r--r--third_party/git/t/t4013/diff.log_--decorate_--all15
-rw-r--r--third_party/git/t/t4013/diff.log_--diff-merges=off_-p_--first-parent_master78
-rw-r--r--third_party/git/t/t4013/diff.log_--first-parent_--diff-merges=off_-p_master78
-rw-r--r--third_party/git/t/t4013/diff.log_--no-diff-merges_-p_--first-parent_master78
-rw-r--r--third_party/git/t/t4013/diff.log_-p_--first-parent_master22
-rwxr-xr-xthird_party/git/t/t4014-format-patch.sh1155
-rwxr-xr-xthird_party/git/t/t4015-diff-whitespace.sh251
-rwxr-xr-xthird_party/git/t/t4018-diff-funcname.sh4
-rw-r--r--third_party/git/t/t4018/dts-labels9
-rw-r--r--third_party/git/t/t4018/dts-node-unitless8
-rw-r--r--third_party/git/t/t4018/dts-nodes8
-rw-r--r--third_party/git/t/t4018/dts-nodes-boolean-prop9
-rw-r--r--third_party/git/t/t4018/dts-nodes-comment18
-rw-r--r--third_party/git/t/t4018/dts-nodes-comment28
-rw-r--r--third_party/git/t/t4018/dts-nodes-multiline-prop13
-rw-r--r--third_party/git/t/t4018/dts-reference9
-rw-r--r--third_party/git/t/t4018/dts-root5
-rw-r--r--third_party/git/t/t4018/dts-root-comment8
-rw-r--r--third_party/git/t/t4018/elixir-do-not-pick-end5
-rw-r--r--third_party/git/t/t4018/elixir-ex-unit-test6
-rw-r--r--third_party/git/t/t4018/elixir-function5
-rw-r--r--third_party/git/t/t4018/elixir-macro5
-rw-r--r--third_party/git/t/t4018/elixir-module9
-rw-r--r--third_party/git/t/t4018/elixir-module-func8
-rw-r--r--third_party/git/t/t4018/elixir-nested-module9
-rw-r--r--third_party/git/t/t4018/elixir-private-function5
-rw-r--r--third_party/git/t/t4018/elixir-protocol6
-rw-r--r--third_party/git/t/t4018/elixir-protocol-implementation5
-rw-r--r--third_party/git/t/t4018/fortran-block-data5
-rw-r--r--third_party/git/t/t4018/fortran-comment13
-rw-r--r--third_party/git/t/t4018/fortran-comment-keyword14
-rw-r--r--third_party/git/t/t4018/fortran-comment-legacy13
-rw-r--r--third_party/git/t/t4018/fortran-comment-legacy-star13
-rw-r--r--third_party/git/t/t4018/fortran-external-function9
-rw-r--r--third_party/git/t/t4018/fortran-external-subroutine5
-rw-r--r--third_party/git/t/t4018/fortran-module5
-rw-r--r--third_party/git/t/t4018/fortran-module-procedure13
-rw-r--r--third_party/git/t/t4018/fortran-program5
-rw-r--r--third_party/git/t/t4018/markdown-heading-indented6
-rw-r--r--third_party/git/t/t4018/markdown-heading-non-headings17
-rw-r--r--third_party/git/t/t4018/python-async-def4
-rw-r--r--third_party/git/t/t4018/python-class4
-rw-r--r--third_party/git/t/t4018/python-def4
-rw-r--r--third_party/git/t/t4018/python-indented-async-def7
-rw-r--r--third_party/git/t/t4018/python-indented-class5
-rw-r--r--third_party/git/t/t4018/python-indented-def7
-rwxr-xr-xthird_party/git/t/t4026-color.sh12
-rwxr-xr-xthird_party/git/t/t4027-diff-submodule.sh15
-rwxr-xr-xthird_party/git/t/t4034-diff-words.sh94
-rw-r--r--third_party/git/t/t4034/dts/expect37
-rw-r--r--third_party/git/t/t4034/dts/post32
-rw-r--r--third_party/git/t/t4034/dts/pre32
-rwxr-xr-xthird_party/git/t/t4038-diff-combined.sh23
-rwxr-xr-xthird_party/git/t/t4039-diff-assume-unchanged.sh3
-rwxr-xr-xthird_party/git/t/t4041-diff-submodule-option.sh20
-rwxr-xr-xthird_party/git/t/t4044-diff-index-unique-abbrev.sh46
-rwxr-xr-xthird_party/git/t/t4045-diff-relative.sh82
-rwxr-xr-xthird_party/git/t/t4048-diff-combined-binary.sh58
-rwxr-xr-xthird_party/git/t/t4054-diff-bogus-tree.sh3
-rwxr-xr-xthird_party/git/t/t4057-diff-combined-paths.sh4
-rwxr-xr-xthird_party/git/t/t4060-diff-submodule-option-diff-format.sh126
-rwxr-xr-xthird_party/git/t/t4061-diff-indent.sh2
-rwxr-xr-xthird_party/git/t/t4064-diff-oidfind.sh55
-rwxr-xr-xthird_party/git/t/t4066-diff-emit-delay.sh10
-rwxr-xr-xthird_party/git/t/t4067-diff-partial-clone.sh89
-rwxr-xr-xthird_party/git/t/t4068-diff-symmetric.sh91
-rw-r--r--third_party/git/t/t4100/t-apply-1.patch4
-rw-r--r--third_party/git/t/t4100/t-apply-3.patch2
-rw-r--r--third_party/git/t/t4100/t-apply-5.patch4
-rw-r--r--third_party/git/t/t4100/t-apply-7.patch2
-rwxr-xr-xthird_party/git/t/t4104-apply-boundary.sh57
-rwxr-xr-xthird_party/git/t/t4108-apply-threeway.sh55
-rwxr-xr-xthird_party/git/t/t4117-apply-reject.sh6
-rwxr-xr-xthird_party/git/t/t4124-apply-ws-rule.sh17
-rwxr-xr-xthird_party/git/t/t4134-apply-submodule.sh4
-rwxr-xr-xthird_party/git/t/t4137-apply-submodule.sh12
-rwxr-xr-xthird_party/git/t/t4138-apply-ws-expansion.sh16
-rwxr-xr-xthird_party/git/t/t4140-apply-ita.sh56
-rwxr-xr-xthird_party/git/t/t4150-am.sh106
-rwxr-xr-xthird_party/git/t/t4200-rerere.sh4
-rwxr-xr-xthird_party/git/t/t4201-shortlog.sh141
-rwxr-xr-xthird_party/git/t/t4202-log.sh410
-rwxr-xr-xthird_party/git/t/t4203-mailmap.sh122
-rwxr-xr-xthird_party/git/t/t4204-patch-id.sh2
-rwxr-xr-xthird_party/git/t/t4205-log-pretty-formats.sh81
-rwxr-xr-xthird_party/git/t/t4208-log-magic-pathspec.sh4
-rwxr-xr-xthird_party/git/t/t4210-log-i18n.sh80
-rwxr-xr-xthird_party/git/t/t4211-line-log.sh160
-rw-r--r--third_party/git/t/t4211/sha1/expect.beginning-of-file (renamed from third_party/git/t/t4211/expect.beginning-of-file)0
-rw-r--r--third_party/git/t/t4211/sha1/expect.end-of-file (renamed from third_party/git/t/t4211/expect.end-of-file)0
-rw-r--r--third_party/git/t/t4211/sha1/expect.move-support-f (renamed from third_party/git/t/t4211/expect.move-support-f)0
-rw-r--r--third_party/git/t/t4211/sha1/expect.multiple (renamed from third_party/git/t/t4211/expect.multiple)0
-rw-r--r--third_party/git/t/t4211/sha1/expect.multiple-overlapping (renamed from third_party/git/t/t4211/expect.multiple-overlapping)0
-rw-r--r--third_party/git/t/t4211/sha1/expect.multiple-superset (renamed from third_party/git/t/t4211/expect.multiple-superset)0
-rw-r--r--third_party/git/t/t4211/sha1/expect.parallel-change-f-to-main (renamed from third_party/git/t/t4211/expect.parallel-change-f-to-main)0
-rw-r--r--third_party/git/t/t4211/sha1/expect.simple-f (renamed from third_party/git/t/t4211/expect.simple-f)0
-rw-r--r--third_party/git/t/t4211/sha1/expect.simple-f-to-main (renamed from third_party/git/t/t4211/expect.simple-f-to-main)0
-rw-r--r--third_party/git/t/t4211/sha1/expect.simple-main (renamed from third_party/git/t/t4211/expect.simple-main)0
-rw-r--r--third_party/git/t/t4211/sha1/expect.simple-main-to-end (renamed from third_party/git/t/t4211/expect.simple-main-to-end)0
-rw-r--r--third_party/git/t/t4211/sha1/expect.two-ranges (renamed from third_party/git/t/t4211/expect.two-ranges)0
-rw-r--r--third_party/git/t/t4211/sha1/expect.vanishes-early (renamed from third_party/git/t/t4211/expect.vanishes-early)0
-rw-r--r--third_party/git/t/t4211/sha256/expect.beginning-of-file43
-rw-r--r--third_party/git/t/t4211/sha256/expect.end-of-file62
-rw-r--r--third_party/git/t/t4211/sha256/expect.move-support-f80
-rw-r--r--third_party/git/t/t4211/sha256/expect.multiple104
-rw-r--r--third_party/git/t/t4211/sha256/expect.multiple-overlapping187
-rw-r--r--third_party/git/t/t4211/sha256/expect.multiple-superset187
-rw-r--r--third_party/git/t/t4211/sha256/expect.parallel-change-f-to-main160
-rw-r--r--third_party/git/t/t4211/sha256/expect.simple-f59
-rw-r--r--third_party/git/t/t4211/sha256/expect.simple-f-to-main100
-rw-r--r--third_party/git/t/t4211/sha256/expect.simple-main68
-rw-r--r--third_party/git/t/t4211/sha256/expect.simple-main-to-end70
-rw-r--r--third_party/git/t/t4211/sha256/expect.two-ranges102
-rw-r--r--third_party/git/t/t4211/sha256/expect.vanishes-early39
-rwxr-xr-xthird_party/git/t/t4213-log-tabexpand.sh2
-rwxr-xr-xthird_party/git/t/t4214-log-graph-octopus.sh339
-rwxr-xr-xthird_party/git/t/t4215-log-skewed-merges.sh373
-rwxr-xr-xthird_party/git/t/t4216-log-bloom.sh405
-rwxr-xr-xthird_party/git/t/t4254-am-corrupt.sh53
-rwxr-xr-xthird_party/git/t/t4255-am-submodule.sh12
-rwxr-xr-xthird_party/git/t/t4256-am-format-flowed.sh2
-rwxr-xr-xthird_party/git/t/t4300-merge-tree.sh188
-rwxr-xr-xthird_party/git/t/t5000-tar-tree.sh29
-rwxr-xr-xthird_party/git/t/t5003-archive-zip.sh52
-rwxr-xr-xthird_party/git/t/t5004-archive-corner-cases.sh19
-rwxr-xr-xthird_party/git/t/t5100-mailinfo.sh15
-rwxr-xr-xthird_party/git/t/t5150-request-pull.sh8
-rwxr-xr-xthird_party/git/t/t5300-pack-object.sh42
-rwxr-xr-xthird_party/git/t/t5302-pack-index.sh363
-rwxr-xr-xthird_party/git/t/t5307-pack-missing-commit.sh4
-rwxr-xr-xthird_party/git/t/t5308-pack-detect-duplicates.sh20
-rwxr-xr-xthird_party/git/t/t5309-pack-delta-cycles.sh18
-rwxr-xr-xthird_party/git/t/t5310-pack-bitmaps.sh36
-rwxr-xr-xthird_party/git/t/t5313-pack-bounds-checks.sh18
-rwxr-xr-xthird_party/git/t/t5314-pack-cycle-detection.sh2
-rwxr-xr-xthird_party/git/t/t5317-pack-objects-filter-objects.sh34
-rwxr-xr-xthird_party/git/t/t5318-commit-graph.sh190
-rwxr-xr-xthird_party/git/t/t5319-multi-pack-index.sh243
-rw-r--r--third_party/git/t/t5319/no-objects.midxbin0 -> 1116 bytes
-rwxr-xr-xthird_party/git/t/t5321-pack-large-objects.sh4
-rwxr-xr-xthird_party/git/t/t5322-pack-objects-sparse.sh4
-rwxr-xr-xthird_party/git/t/t5324-split-commit-graph.sh112
-rwxr-xr-xthird_party/git/t/t5400-send-pack.sh2
-rwxr-xr-xthird_party/git/t/t5407-post-rewrite-hook.sh16
-rwxr-xr-xthird_party/git/t/t5409-colorize-remote-messages.sh3
-rwxr-xr-xthird_party/git/t/t5411-proc-receive-hook.sh117
-rw-r--r--third_party/git/t/t5411/common-functions.sh56
-rw-r--r--third_party/git/t/t5411/once-0010-report-status-v1.sh94
-rw-r--r--third_party/git/t/t5411/test-0000-standard-git-push.sh143
-rw-r--r--third_party/git/t/t5411/test-0001-standard-git-push--porcelain.sh147
-rw-r--r--third_party/git/t/t5411/test-0002-pre-receive-declined.sh33
-rw-r--r--third_party/git/t/t5411/test-0003-pre-receive-declined--porcelain.sh34
-rw-r--r--third_party/git/t/t5411/test-0010-proc-receive-settings.sh7
-rw-r--r--third_party/git/t/t5411/test-0011-no-hook-error.sh64
-rw-r--r--third_party/git/t/t5411/test-0012-no-hook-error--porcelain.sh66
-rw-r--r--third_party/git/t/t5411/test-0013-bad-protocol.sh217
-rw-r--r--third_party/git/t/t5411/test-0014-bad-protocol--porcelain.sh160
-rw-r--r--third_party/git/t/t5411/test-0020-report-ng.sh67
-rw-r--r--third_party/git/t/t5411/test-0021-report-ng--porcelain.sh69
-rw-r--r--third_party/git/t/t5411/test-0022-report-unexpect-ref.sh45
-rw-r--r--third_party/git/t/t5411/test-0023-report-unexpect-ref--porcelain.sh46
-rw-r--r--third_party/git/t/t5411/test-0024-report-unknown-ref.sh34
-rw-r--r--third_party/git/t/t5411/test-0025-report-unknown-ref--porcelain.sh35
-rw-r--r--third_party/git/t/t5411/test-0026-push-options.sh79
-rw-r--r--third_party/git/t/t5411/test-0027-push-options--porcelain.sh82
-rw-r--r--third_party/git/t/t5411/test-0030-report-ok.sh35
-rw-r--r--third_party/git/t/t5411/test-0031-report-ok--porcelain.sh36
-rw-r--r--third_party/git/t/t5411/test-0032-report-with-options.sh256
-rw-r--r--third_party/git/t/t5411/test-0033-report-with-options--porcelain.sh265
-rw-r--r--third_party/git/t/t5411/test-0034-report-ft.sh44
-rw-r--r--third_party/git/t/t5411/test-0035-report-ft--porcelain.sh45
-rw-r--r--third_party/git/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh227
-rw-r--r--third_party/git/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh172
-rw-r--r--third_party/git/t/t5411/test-0038-report-mixed-refs.sh89
-rw-r--r--third_party/git/t/t5411/test-0039-report-mixed-refs--porcelain.sh91
-rw-r--r--third_party/git/t/t5411/test-0040-process-all-refs.sh113
-rw-r--r--third_party/git/t/t5411/test-0041-process-all-refs--porcelain.sh114
-rw-r--r--third_party/git/t/t5411/test-0050-proc-receive-refs-with-modifiers.sh135
-rwxr-xr-xthird_party/git/t/t5500-fetch-pack.sh127
-rwxr-xr-xthird_party/git/t/t5504-fetch-receive-strict.sh17
-rwxr-xr-xthird_party/git/t/t5505-remote.sh96
-rwxr-xr-xthird_party/git/t/t5509-fetch-push-namespaces.sh13
-rwxr-xr-xthird_party/git/t/t5510-fetch.sh145
-rwxr-xr-xthird_party/git/t/t5512-ls-remote.sh109
-rwxr-xr-xthird_party/git/t/t5514-fetch-multiple.sh13
-rwxr-xr-xthird_party/git/t/t5515-fetch-merge-logic.sh63
-rwxr-xr-xthird_party/git/t/t5516-fetch-push.sh42
-rwxr-xr-xthird_party/git/t/t5517-push-mirror.sh10
-rwxr-xr-xthird_party/git/t/t5520-pull.sh423
-rwxr-xr-xthird_party/git/t/t5521-pull-options.sh29
-rwxr-xr-xthird_party/git/t/t5523-push-upstream.sh20
-rwxr-xr-xthird_party/git/t/t5528-push-default.sh8
-rwxr-xr-xthird_party/git/t/t5530-upload-pack-error.sh25
-rwxr-xr-xthird_party/git/t/t5534-push-signed.sh22
-rwxr-xr-xthird_party/git/t/t5535-fetch-push-symref.sh2
-rwxr-xr-xthird_party/git/t/t5537-fetch-shallow.sh134
-rwxr-xr-xthird_party/git/t/t5539-fetch-http-shallow.sh6
-rwxr-xr-xthird_party/git/t/t5540-http-push-webdav.sh22
-rwxr-xr-xthird_party/git/t/t5541-http-push-smart.sh90
-rwxr-xr-xthird_party/git/t/t5543-atomic-push.sh89
-rwxr-xr-xthird_party/git/t/t5545-push-options.sh5
-rwxr-xr-xthird_party/git/t/t5548-push-porcelain.sh279
-rwxr-xr-xthird_party/git/t/t5550-http-fetch-dumb.sh68
-rwxr-xr-xthird_party/git/t/t5551-http-fetch-smart.sh68
-rwxr-xr-xthird_party/git/t/t5552-skipping-fetch-negotiator.sh41
-rwxr-xr-xthird_party/git/t/t5553-set-upstream.sh178
-rwxr-xr-xthird_party/git/t/t5554-noop-fetch-negotiator.sh22
-rwxr-xr-xthird_party/git/t/t5562-http-backend-content-length.sh19
-rwxr-xr-xthird_party/git/t/t5572-pull-submodule.sh20
-rwxr-xr-xthird_party/git/t/t5573-pull-verify-signatures.sh64
-rwxr-xr-xthird_party/git/t/t5580-unc-paths.sh (renamed from third_party/git/t/t5580-clone-push-unc.sh)14
-rwxr-xr-xthird_party/git/t/t5581-http-curl-verbose.sh2
-rwxr-xr-xthird_party/git/t/t5582-fetch-negative-refspec.sh189
-rwxr-xr-xthird_party/git/t/t5601-clone.sh34
-rwxr-xr-xthird_party/git/t/t5604-clone-reference.sh7
-rwxr-xr-xthird_party/git/t/t5606-clone-options.sh24
-rwxr-xr-xthird_party/git/t/t5607-clone-bundle.sh46
-rwxr-xr-xthird_party/git/t/t5608-clone-2gb.sh13
-rwxr-xr-xthird_party/git/t/t5611-clone-config.sh13
-rwxr-xr-xthird_party/git/t/t5612-clone-refspec.sh26
-rwxr-xr-xthird_party/git/t/t5616-partial-clone.sh333
-rwxr-xr-xthird_party/git/t/t5617-clone-submodules-remote.sh13
-rwxr-xr-xthird_party/git/t/t5700-protocol-v1.sh6
-rwxr-xr-xthird_party/git/t/t5701-git-serve.sh25
-rwxr-xr-xthird_party/git/t/t5702-protocol-v2.sh240
-rwxr-xr-xthird_party/git/t/t5703-upload-pack-ref-in-want.sh282
-rwxr-xr-xthird_party/git/t/t5704-protocol-violations.sh35
-rwxr-xr-xthird_party/git/t/t5801-remote-helpers.sh11
-rwxr-xr-xthird_party/git/t/t5801/git-remote-testgit6
-rwxr-xr-xthird_party/git/t/t6000-rev-list-misc.sh44
-rwxr-xr-xthird_party/git/t/t6002-rev-list-bisect.sh45
-rwxr-xr-xthird_party/git/t/t6006-rev-list-format.sh36
-rwxr-xr-xthird_party/git/t/t6011-rev-list-with-bad-commit.sh2
-rwxr-xr-xthird_party/git/t/t6012-rev-list-simplify.sh120
-rwxr-xr-xthird_party/git/t/t6016-rev-list-graph-simplify-history.sh30
-rwxr-xr-xthird_party/git/t/t6018-rev-list-glob.sh11
-rwxr-xr-xthird_party/git/t/t6019-rev-list-ancestry-path.sh4
-rwxr-xr-xthird_party/git/t/t6021-merge-criss-cross.sh96
-rwxr-xr-xthird_party/git/t/t6023-merge-file.sh362
-rwxr-xr-xthird_party/git/t/t6024-recursive-merge.sh122
-rwxr-xr-xthird_party/git/t/t6025-merge-symlinks.sh61
-rwxr-xr-xthird_party/git/t/t6030-bisect-porcelain.sh130
-rwxr-xr-xthird_party/git/t/t6034-merge-rename-nocruft.sh99
-rwxr-xr-xthird_party/git/t/t6040-tracking-info.sh20
-rwxr-xr-xthird_party/git/t/t6041-bisect-submodule.sh9
-rwxr-xr-xthird_party/git/t/t6050-replace.sh2
-rwxr-xr-xthird_party/git/t/t6100-rev-list-in-order.sh4
-rwxr-xr-xthird_party/git/t/t6101-rev-parse-parents.sh2
-rwxr-xr-xthird_party/git/t/t6102-rev-list-unexpected-objects.sh2
-rwxr-xr-xthird_party/git/t/t6112-rev-list-filters-objects.sh194
-rwxr-xr-xthird_party/git/t/t6113-rev-list-bitmap-filters.sh77
-rwxr-xr-xthird_party/git/t/t6120-describe.sh158
-rwxr-xr-xthird_party/git/t/t6130-pathspec-noglob.sh1
-rwxr-xr-xthird_party/git/t/t6132-pathspec-exclude.sh33
-rwxr-xr-xthird_party/git/t/t6136-pathspec-in-bare.sh38
-rwxr-xr-xthird_party/git/t/t6200-fmt-merge-msg.sh43
-rwxr-xr-xthird_party/git/t/t6300-for-each-ref.sh267
-rwxr-xr-xthird_party/git/t/t6301-for-each-ref-errors.sh2
-rwxr-xr-xthird_party/git/t/t6302-for-each-ref-filter.sh4
-rwxr-xr-xthird_party/git/t/t6400-merge-df.sh (renamed from third_party/git/t/t6020-merge-df.sh)55
-rwxr-xr-xthird_party/git/t/t6401-merge-criss-cross.sh67
-rwxr-xr-xthird_party/git/t/t6402-merge-rename.sh (renamed from third_party/git/t/t6022-merge-rename.sh)393
-rwxr-xr-xthird_party/git/t/t6403-merge-file.sh390
-rwxr-xr-xthird_party/git/t/t6404-recursive-merge.sh131
-rwxr-xr-xthird_party/git/t/t6405-merge-symlinks.sh62
-rwxr-xr-xthird_party/git/t/t6406-merge-attr.sh (renamed from third_party/git/t/t6026-merge-attr.sh)50
-rwxr-xr-xthird_party/git/t/t6407-merge-binary.sh (renamed from third_party/git/t/t6027-merge-binary.sh)0
-rwxr-xr-xthird_party/git/t/t6408-merge-up-to-date.sh (renamed from third_party/git/t/t6028-merge-up-to-date.sh)0
-rwxr-xr-xthird_party/git/t/t6409-merge-subtree.sh (renamed from third_party/git/t/t6029-merge-subtree.sh)26
-rwxr-xr-xthird_party/git/t/t6411-merge-filemode.sh (renamed from third_party/git/t/t6031-merge-filemode.sh)0
-rwxr-xr-xthird_party/git/t/t6412-merge-large-rename.sh (renamed from third_party/git/t/t6032-merge-large-rename.sh)0
-rwxr-xr-xthird_party/git/t/t6413-merge-crlf.sh (renamed from third_party/git/t/t6033-merge-crlf.sh)0
-rwxr-xr-xthird_party/git/t/t6414-merge-rename-nocruft.sh97
-rwxr-xr-xthird_party/git/t/t6415-merge-dir-to-symlink.sh (renamed from third_party/git/t/t6035-merge-dir-to-symlink.sh)28
-rwxr-xr-xthird_party/git/t/t6416-recursive-corner-cases.sh (renamed from third_party/git/t/t6036-recursive-corner-cases.sh)55
-rwxr-xr-xthird_party/git/t/t6417-merge-ours-theirs.sh (renamed from third_party/git/t/t6037-merge-ours-theirs.sh)0
-rwxr-xr-xthird_party/git/t/t6418-merge-text-auto.sh (renamed from third_party/git/t/t6038-merge-text-auto.sh)29
-rwxr-xr-xthird_party/git/t/t6419-merge-ignorecase.sh (renamed from third_party/git/t/t6039-merge-ignorecase.sh)0
-rwxr-xr-xthird_party/git/t/t6422-merge-rename-corner-cases.sh (renamed from third_party/git/t/t6042-merge-rename-corner-cases.sh)234
-rwxr-xr-xthird_party/git/t/t6423-merge-rename-directories.sh (renamed from third_party/git/t/t6043-merge-rename-directories.sh)615
-rwxr-xr-xthird_party/git/t/t6424-merge-unrelated-index-changes.sh (renamed from third_party/git/t/t6044-merge-unrelated-index-changes.sh)0
-rwxr-xr-xthird_party/git/t/t6425-merge-rename-delete.sh (renamed from third_party/git/t/t6045-merge-rename-delete.sh)3
-rwxr-xr-xthird_party/git/t/t6426-merge-skip-unneeded-updates.sh (renamed from third_party/git/t/t6046-merge-skip-unneeded-updates.sh)221
-rwxr-xr-xthird_party/git/t/t6427-diff3-conflict-markers.sh211
-rwxr-xr-xthird_party/git/t/t6430-merge-recursive.sh (renamed from third_party/git/t/t3030-merge-recursive.sh)43
-rwxr-xr-xthird_party/git/t/t6431-merge-criscross.sh (renamed from third_party/git/t/t3031-merge-criscross.sh)0
-rwxr-xr-xthird_party/git/t/t6432-merge-recursive-space-options.sh (renamed from third_party/git/t/t3032-merge-recursive-space-options.sh)0
-rwxr-xr-xthird_party/git/t/t6433-merge-toplevel.sh (renamed from third_party/git/t/t3033-merge-toplevel.sh)22
-rwxr-xr-xthird_party/git/t/t6434-merge-recursive-rename-options.sh (renamed from third_party/git/t/t3034-merge-recursive-rename-options.sh)0
-rwxr-xr-xthird_party/git/t/t6435-merge-sparse.sh (renamed from third_party/git/t/t3035-merge-sparse.sh)4
-rwxr-xr-xthird_party/git/t/t6436-merge-overwrite.sh (renamed from third_party/git/t/t7607-merge-overwrite.sh)0
-rwxr-xr-xthird_party/git/t/t6437-submodule-merge.sh (renamed from third_party/git/t/t7405-submodule-merge.sh)4
-rwxr-xr-xthird_party/git/t/t6438-submodule-directory-file-conflicts.sh (renamed from third_party/git/t/t7613-merge-submodule.sh)8
-rwxr-xr-xthird_party/git/t/t6439-merge-co-error-msgs.sh (renamed from third_party/git/t/t7609-merge-co-error-msgs.sh)2
-rwxr-xr-xthird_party/git/t/t6500-gc.sh33
-rwxr-xr-xthird_party/git/t/t6501-freshen-objects.sh20
-rwxr-xr-xthird_party/git/t/t6600-test-reach.sh32
-rwxr-xr-xthird_party/git/t/t7001-mv.sh19
-rwxr-xr-xthird_party/git/t/t7003-filter-branch.sh3
-rwxr-xr-xthird_party/git/t/t7004-tag.sh14
-rwxr-xr-xthird_party/git/t/t7008-filter-branch-null-sha1.sh (renamed from third_party/git/t/t7009-filter-branch-null-sha1.sh)0
-rwxr-xr-xthird_party/git/t/t7012-skip-worktree-writing.sh15
-rwxr-xr-xthird_party/git/t/t7030-verify-tag.sh38
-rwxr-xr-xthird_party/git/t/t7061-wtstatus-ignore.sh34
-rwxr-xr-xthird_party/git/t/t7063-status-untracked-cache.sh204
-rwxr-xr-xthird_party/git/t/t7102-reset.sh93
-rwxr-xr-xthird_party/git/t/t7105-reset-patch.sh21
-rwxr-xr-xthird_party/git/t/t7107-reset-pathspec-file.sh178
-rwxr-xr-xthird_party/git/t/t7112-reset-submodule.sh7
-rwxr-xr-xthird_party/git/t/t7201-co.sh6
-rwxr-xr-xthird_party/git/t/t7300-clean.sh73
-rwxr-xr-xthird_party/git/t/t7400-submodule-basic.sh94
-rwxr-xr-xthird_party/git/t/t7401-submodule-summary.sh155
-rwxr-xr-xthird_party/git/t/t7406-submodule-update.sh61
-rwxr-xr-xthird_party/git/t/t7408-submodule-reference.sh8
-rwxr-xr-xthird_party/git/t/t7410-submodule-checkout-to.sh77
-rwxr-xr-xthird_party/git/t/t7415-submodule-names.sh57
-rwxr-xr-xthird_party/git/t/t7416-submodule-dash-url.sh157
-rwxr-xr-xthird_party/git/t/t7417-submodule-path-url.sh17
-rwxr-xr-xthird_party/git/t/t7419-submodule-set-branch.sh13
-rwxr-xr-xthird_party/git/t/t7420-submodule-set-url.sh55
-rwxr-xr-xthird_party/git/t/t7421-submodule-summary-add.sh69
-rwxr-xr-xthird_party/git/t/t7500-commit-template-squash-signoff.sh9
-rwxr-xr-xthird_party/git/t/t7501-commit-basic-functionality.sh83
-rwxr-xr-xthird_party/git/t/t7503-pre-commit-and-pre-merge-commit-hooks.sh281
-rwxr-xr-xthird_party/git/t/t7503-pre-commit-hook.sh139
-rwxr-xr-xthird_party/git/t/t7505-prepare-commit-msg-hook.sh8
-rw-r--r--third_party/git/t/t7505/expected-rebase-i3
-rwxr-xr-xthird_party/git/t/t7506-status-submodule.sh12
-rwxr-xr-xthird_party/git/t/t7508-status.sh45
-rwxr-xr-xthird_party/git/t/t7510-signed-commit.sh55
-rwxr-xr-xthird_party/git/t/t7512-status-help.sh18
-rwxr-xr-xthird_party/git/t/t7513-interpret-trailers.sh2
-rwxr-xr-xthird_party/git/t/t7518-ident-corner-cases.sh13
-rwxr-xr-xthird_party/git/t/t7519-status-fsmonitor.sh37
-rwxr-xr-xthird_party/git/t/t7519/fsmonitor-all1
-rwxr-xr-xthird_party/git/t/t7519/fsmonitor-all-v221
-rwxr-xr-xthird_party/git/t/t7519/fsmonitor-env24
-rwxr-xr-xthird_party/git/t/t7519/fsmonitor-watchman16
-rwxr-xr-xthird_party/git/t/t7519/fsmonitor-watchman-v2173
-rwxr-xr-xthird_party/git/t/t7526-commit-pathspec-file.sh164
-rwxr-xr-xthird_party/git/t/t7600-merge.sh160
-rwxr-xr-xthird_party/git/t/t7601-merge-pull-config.sh57
-rwxr-xr-xthird_party/git/t/t7612-merge-verify-signatures.sh22
-rwxr-xr-xthird_party/git/t/t7700-repack.sh172
-rwxr-xr-xthird_party/git/t/t7800-difftool.sh13
-rwxr-xr-xthird_party/git/t/t7810-grep.sh47
-rwxr-xr-xthird_party/git/t/t7811-grep-open.sh1
-rwxr-xr-xthird_party/git/t/t7812-grep-icase-non-ascii.sh31
-rwxr-xr-xthird_party/git/t/t7814-grep-recurse-submodules.sh32
-rwxr-xr-xthird_party/git/t/t7815-grep-binary.sh (renamed from third_party/git/t/t7008-grep-binary.sh)101
-rwxr-xr-xthird_party/git/t/t7816-grep-binary-pattern.sh127
-rwxr-xr-xthird_party/git/t/t7900-maintenance.sh65
-rwxr-xr-xthird_party/git/t/t8002-blame.sh29
-rwxr-xr-xthird_party/git/t/t8003-blame-corner-cases.sh30
-rwxr-xr-xthird_party/git/t/t8011-blame-split-file.sh2
-rwxr-xr-xthird_party/git/t/t8013-blame-ignore-revs.sh61
-rwxr-xr-xthird_party/git/t/t8014-blame-ignore-fuzzy.sh2
-rwxr-xr-xthird_party/git/t/t9001-send-email.sh61
-rwxr-xr-xthird_party/git/t/t9010-svn-fe.sh1107
-rwxr-xr-xthird_party/git/t/t9011-svn-da.sh248
-rwxr-xr-xthird_party/git/t/t9020-remote-svn.sh89
-rwxr-xr-xthird_party/git/t/t9100-git-svn-basic.sh30
-rwxr-xr-xthird_party/git/t/t9101-git-svn-props.sh12
-rwxr-xr-xthird_party/git/t/t9104-git-svn-follow-parent.sh3
-rwxr-xr-xthird_party/git/t/t9106-git-svn-commit-diff-clobber.sh3
-rwxr-xr-xthird_party/git/t/t9108-git-svn-glob.sh4
-rwxr-xr-xthird_party/git/t/t9109-git-svn-multi-glob.sh6
-rwxr-xr-xthird_party/git/t/t9116-git-svn-log.sh12
-rwxr-xr-xthird_party/git/t/t9141-git-svn-multiple-branches.sh8
-rwxr-xr-xthird_party/git/t/t9160-git-svn-preserve-empty-dirs.sh4
-rwxr-xr-xthird_party/git/t/t9164-git-svn-dcommit-concurrent.sh4
-rwxr-xr-xthird_party/git/t/t9168-git-svn-partially-globbed-names.sh8
-rwxr-xr-xthird_party/git/t/t9300-fast-import.sh383
-rwxr-xr-xthird_party/git/t/t9301-fast-import-notes.sh14
-rwxr-xr-xthird_party/git/t/t9350-fast-export.sh85
-rwxr-xr-xthird_party/git/t/t9351-fast-export-anonymize.sh54
-rwxr-xr-xthird_party/git/t/t9400-git-cvsserver-server.sh2
-rwxr-xr-xthird_party/git/t/t9401-git-cvsserver-crlf.sh8
-rwxr-xr-xthird_party/git/t/t9402-git-cvsserver-refs.sh2
-rwxr-xr-xthird_party/git/t/t9500-gitweb-standalone-no-errors.sh92
-rwxr-xr-xthird_party/git/t/t9502-gitweb-standalone-parse-output.sh7
-rwxr-xr-xthird_party/git/t/t9700/test.pl12
-rwxr-xr-xthird_party/git/t/t9800-git-p4-basic.sh1
-rwxr-xr-xthird_party/git/t/t9809-git-p4-client-view.sh2
-rwxr-xr-xthird_party/git/t/t9810-git-p4-rcs.sh1
-rwxr-xr-xthird_party/git/t/t9819-git-p4-case-folding.sh2
-rwxr-xr-xthird_party/git/t/t9831-git-p4-triggers.sh2
-rwxr-xr-xthird_party/git/t/t9832-unshelve.sh5
-rwxr-xr-xthird_party/git/t/t9834-git-p4-file-dir-bug.sh70
-rwxr-xr-xthird_party/git/t/t9902-completion.sh619
-rwxr-xr-xthird_party/git/t/t9903-bash-prompt.sh8
-rw-r--r--third_party/git/t/test-lib-functions.sh232
-rw-r--r--third_party/git/t/test-lib.sh207
-rwxr-xr-xthird_party/git/t/test-terminal.perl32
623 files changed, 31865 insertions, 8843 deletions
diff --git a/third_party/git/t/README b/third_party/git/t/README
index 60d5b77bcc..2adaf7c2d2 100644
--- a/third_party/git/t/README
+++ b/third_party/git/t/README
@@ -1,7 +1,7 @@
-Core GIT Tests
+Core Git Tests
 ==============
 
-This directory holds many test scripts for core GIT tools.  The
+This directory holds many test scripts for core Git tools.  The
 first part of this short document describes how to run the tests
 and read their output.
 
@@ -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::
@@ -352,8 +353,8 @@ details.
 GIT_TEST_SPLIT_INDEX=<boolean> forces split-index mode on the whole
 test suite. Accept any boolean values that are accepted by git-config.
 
-GIT_TEST_PROTOCOL_VERSION=<n>, when set, overrides the
-'protocol.version' setting to n if it is less than n.
+GIT_TEST_PROTOCOL_VERSION=<n>, when set, makes 'protocol.version'
+default to n.
 
 GIT_TEST_FULL_IN_PACK_ARRAY=<boolean> exercises the uncommon
 pack-objects code path where there are more than 1024 packs even if
@@ -378,6 +379,11 @@ GIT_TEST_COMMIT_GRAPH=<boolean>, when true, forces the commit-graph to
 be written after every 'git commit' command, and overrides the
 'core.commitGraph' setting to true.
 
+GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS=<boolean>, when true, forces
+commit-graph write to compute and write changed path Bloom filters for
+every 'git commit-graph write', as if the `--changed-paths` option was
+passed in.
+
 GIT_TEST_FSMONITOR=$PWD/t7519/fsmonitor-all exercises the fsmonitor
 code path for utilizing a file system monitor to speed up detecting
 new or changed files.
@@ -386,15 +392,15 @@ 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_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).
 
 GIT_TEST_INDEX_THREADS=<n> enables exercising the multi-threaded loading
@@ -415,6 +421,10 @@ GIT_TEST_DISALLOW_ABBREVIATED_OPTIONS=<boolean>, when true (which is
 the default when running tests), errors out when an abbreviated option
 is used.
 
+GIT_TEST_DEFAULT_HASH=<hash-algo> specifies which hash algorithm to
+use in the test scripts. Recognized values for <hash-algo> are "sha1"
+and "sha256".
+
 Naming Tests
 ------------
 
@@ -546,6 +556,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.
@@ -978,6 +1023,15 @@ library for your script to use.
    output to the downstream---unlike the real version, it generates
    only up to 99 lines.
 
+ - test_bool_env <env-variable-name> <default-value>
+
+   Given the name of an environment variable with a bool value,
+   normalize its value to a 0 (true) or 1 (false or empty string)
+   return code.  Return with code corresponding to the given default
+   value if the variable is unset.
+   Abort the test script if either the value of the variable or the
+   default are not valid bool values.
+
 
 Prerequisites
 -------------
@@ -1067,21 +1121,21 @@ Tips for Writing Tests
 As with any programming projects, existing programs are the best
 source of the information.  However, do _not_ emulate
 t0000-basic.sh when writing your tests.  The test is special in
-that it tries to validate the very core of GIT.  For example, it
+that it tries to validate the very core of Git.  For example, it
 knows that there will be 256 subdirectories under .git/objects/,
 and it knows that the object ID of an empty tree is a certain
 40-byte string.  This is deliberately done so in t0000-basic.sh
 because the things the very basic core test tries to achieve is
-to serve as a basis for people who are changing the GIT internal
+to serve as a basis for people who are changing the Git internals
 drastically.  For these people, after making certain changes,
 not seeing failures from the basic test _is_ a failure.  And
-such drastic changes to the core GIT that even changes these
+such drastic changes to the core Git that even changes these
 otherwise supposedly stable object IDs should be accompanied by
 an update to t0000-basic.sh.
 
 However, other tests that simply rely on basic parts of the core
-GIT working properly should not have that level of intimate
-knowledge of the core GIT internals.  If all the test scripts
+Git working properly should not have that level of intimate
+knowledge of the core Git internals.  If all the test scripts
 hardcoded the object IDs like t0000-basic.sh does, that defeats
 the purpose of t0000-basic.sh, which is to isolate that level of
 validation in one place.  Your test also ends up needing
diff --git a/third_party/git/t/chainlint.sed b/third_party/git/t/chainlint.sed
index 70df40e34b..8a25c5b855 100644
--- a/third_party/git/t/chainlint.sed
+++ b/third_party/git/t/chainlint.sed
@@ -117,7 +117,7 @@
 /^[ 	]*!*[ 	]*(..*)[ 	]*[0-9]*[<>|&]/boneline
 
 # multi-line "(...\n...)"
-/^[ 	]*(/bsubshell
+/^[ 	]*(/bsubsh
 
 # innocuous line -- print it and advance to next line
 b
@@ -130,11 +130,11 @@ b
 }
 b
 
-:subshell
+:subsh
 # bare "(" line? -- stash for later printing
 /^[ 	]*([	]*$/ {
 	h
-	bnextline
+	bnextln
 }
 # "(..." line -- split off and stash "(", then process "..." as its own line
 x
@@ -143,7 +143,7 @@ x
 s/(//
 bslurp
 
-:nextline
+:nextln
 N
 s/.*\n//
 
@@ -151,10 +151,10 @@ s/.*\n//
 # incomplete line "...\"
 /\\$/bicmplte
 # multi-line quoted string "...\n..."?
-/"/bdqstring
+/"/bdqstr
 # multi-line quoted string '...\n...'? (but not contraction in string "it's")
 /'/{
-	/"[^'"]*'[^'"]*"/!bsqstring
+	/"[^'"]*'[^'"]*"/!bsqstr
 }
 :folded
 # here-doc -- swallow it
@@ -163,8 +163,8 @@ s/.*\n//
 # before closing ")", "done", "elsif", "else", or "fi" will need to be
 # re-visited to drop "suspect" marking since final line of those constructs
 # legitimately lacks "&&", so "suspect" mark must be removed
-/^[ 	]*#/bnextline
-/^[ 	]*$/bnextline
+/^[ 	]*#/bnextln
+/^[ 	]*$/bnextln
 # in-line comment -- strip it (but not "#" in a string, Bash ${#...} array
 # length, or Perforce "//depot/path#42" revision in filespec)
 /[ 	]#/{
@@ -175,22 +175,22 @@ s/.*\n//
 # multi-line "case ... esac"
 /^[ 	]*case[ 	]..*[ 	]in/bcase
 # multi-line "for ... done" or "while ... done"
-/^[ 	]*for[ 	]..*[ 	]in/bcontinue
-/^[ 	]*while[ 	]/bcontinue
-/^[ 	]*do[ 	]/bcontinue
-/^[ 	]*do[ 	]*$/bcontinue
-/;[ 	]*do/bcontinue
+/^[ 	]*for[ 	]..*[ 	]in/bcont
+/^[ 	]*while[ 	]/bcont
+/^[ 	]*do[ 	]/bcont
+/^[ 	]*do[ 	]*$/bcont
+/;[ 	]*do/bcont
 /^[ 	]*done[ 	]*&&[ 	]*$/bdone
 /^[ 	]*done[ 	]*$/bdone
 /^[ 	]*done[ 	]*[<>|]/bdone
 /^[ 	]*done[ 	]*)/bdone
-/||[ 	]*exit[ 	]/bcontinue
-/||[ 	]*exit[ 	]*$/bcontinue
+/||[ 	]*exit[ 	]/bcont
+/||[ 	]*exit[ 	]*$/bcont
 # multi-line "if...elsif...else...fi"
-/^[ 	]*if[ 	]/bcontinue
-/^[ 	]*then[ 	]/bcontinue
-/^[ 	]*then[ 	]*$/bcontinue
-/;[ 	]*then/bcontinue
+/^[ 	]*if[ 	]/bcont
+/^[ 	]*then[ 	]/bcont
+/^[ 	]*then[ 	]*$/bcont
+/;[ 	]*then/bcont
 /^[ 	]*elif[ 	]/belse
 /^[ 	]*elif[ 	]*$/belse
 /^[ 	]*else[ 	]/belse
@@ -234,10 +234,10 @@ s/.*\n//
 	}
 }
 # line ends with pipe "...|" -- valid; not missing "&&"
-/|[ 	]*$/bcontinue
+/|[ 	]*$/bcont
 # missing end-of-line "&&" -- mark suspect
 /&&[ 	]*$/!s/^/?!AMP?!/
-:continue
+:cont
 # retrieve and print previous line
 x
 n
@@ -250,7 +250,7 @@ s/\\\n//
 bslurp
 
 # check for multi-line double-quoted string "...\n..." -- fold to one line
-:dqstring
+:dqstr
 # remove all quote pairs
 s/"\([^"]*\)"/@!\1@!/g
 # done if no dangling quote
@@ -258,13 +258,13 @@ s/"\([^"]*\)"/@!\1@!/g
 # otherwise, slurp next line and try again
 N
 s/\n//
-bdqstring
+bdqstr
 :dqdone
 s/@!/"/g
 bfolded
 
 # check for multi-line single-quoted string '...\n...' -- fold to one line
-:sqstring
+:sqstr
 # remove all quote pairs
 s/'\([^']*\)'/@!\1@!/g
 # done if no dangling quote
@@ -272,7 +272,7 @@ s/'\([^']*\)'/@!\1@!/g
 # otherwise, slurp next line and try again
 N
 s/\n//
-bsqstring
+bsqstr
 :sqdone
 s/@!/'/g
 bfolded
@@ -282,11 +282,11 @@ bfolded
 :heredoc
 s/^\(.*\)<<[ 	]*[-\\'"]*\([A-Za-z0-9_][A-Za-z0-9_]*\)['"]*/<\2>\1<</
 s/[ 	]*<<//
-:heredsub
+:hdocsub
 N
 /^<\([^>]*\)>.*\n[ 	]*\1[ 	]*$/!{
 	s/\n.*$//
-	bheredsub
+	bhdocsub
 }
 s/^<[^>]*>//
 s/\n.*$//
@@ -305,7 +305,7 @@ bcase
 x
 s/?!AMP?!//
 x
-bcontinue
+bcont
 
 # found "done" closing for-loop or while-loop, or "fi" closing if-then -- drop
 # "suspect" from final contained line since that line legitimately lacks "&&"
@@ -321,10 +321,10 @@ bchkchn
 # found nested multi-line "(...\n...)" -- pass through untouched
 :nest
 x
-:nstslurp
+:nstslrp
 n
 # closing ")" on own line -- stop nested slurp
-/^[ 	]*)/bnstclose
+/^[ 	]*)/bnstcl
 # comment -- not closing ")" if in comment
 /^[ 	]*#/bnstcnt
 # "$((...))" -- arithmetic expansion; not closing ")"
@@ -332,11 +332,11 @@ n
 # "$(...)" -- command substitution; not closing ")"
 /\$([^)][^)]*)[^)]*$/bnstcnt
 # closing "...)" -- stop nested slurp
-/)/bnstclose
+/)/bnstcl
 :nstcnt
 x
-bnstslurp
-:nstclose
+bnstslrp
+:nstcl
 s/^/>>/
 # is it "))" which closes nested and parent subshells?
 /)[ 	]*)/bslurp
diff --git a/third_party/git/t/check-non-portable-shell.pl b/third_party/git/t/check-non-portable-shell.pl
index 38bfeebd88..fd3303552b 100755
--- a/third_party/git/t/check-non-portable-shell.pl
+++ b/third_party/git/t/check-non-portable-shell.pl
@@ -46,7 +46,7 @@ while (<>) {
 	/(?:\$\(seq|^\s*seq\b)/ and err 'seq is not portable (use test_seq)';
 	/\bgrep\b.*--file\b/ and err 'grep --file FILE is not portable (use grep -f FILE)';
 	/\bexport\s+[A-Za-z0-9_]*=/ and err '"export FOO=bar" is not portable (use FOO=bar && export FOO)';
-	/^\s*([A-Z0-9_]+=(\w+|(["']).*?\3)\s+)+(\w+)/ and exists($func{$4}) and
+	/^\s*([A-Z0-9_]+=(\w*|(["']).*?\3)\s+)+(\w+)/ and exists($func{$4}) and
 		err '"FOO=bar shell_func" assignment extends beyond "shell_func"';
 	$line = '';
 	# this resets our $. for each file
diff --git a/third_party/git/t/gitweb-lib.sh b/third_party/git/t/gitweb-lib.sh
index 006d2a8152..1f32ca66ea 100644
--- a/third_party/git/t/gitweb-lib.sh
+++ b/third_party/git/t/gitweb-lib.sh
@@ -58,10 +58,11 @@ gitweb_run () {
 	GATEWAY_INTERFACE='CGI/1.1'
 	HTTP_ACCEPT='*/*'
 	REQUEST_METHOD='GET'
-	QUERY_STRING=""$1""
-	PATH_INFO=""$2""
+	QUERY_STRING=$1
+	PATH_INFO=$2
+	REQUEST_URI=/gitweb.cgi$PATH_INFO
 	export GATEWAY_INTERFACE HTTP_ACCEPT REQUEST_METHOD \
-		QUERY_STRING PATH_INFO
+		QUERY_STRING PATH_INFO REQUEST_URI
 
 	GITWEB_CONFIG=$(pwd)/gitweb_config.perl
 	export GITWEB_CONFIG
diff --git a/third_party/git/t/helper/.gitignore b/third_party/git/t/helper/.gitignore
index 2bad28af92..8c2ddcce95 100644
--- a/third_party/git/t/helper/.gitignore
+++ b/third_party/git/t/helper/.gitignore
@@ -1,5 +1,2 @@
-*
-!*.sh
-!*.[ch]
-!*.gitignore
-
+/test-tool
+/test-fake-ssh
diff --git a/third_party/git/t/helper/test-advise.c b/third_party/git/t/helper/test-advise.c
new file mode 100644
index 0000000000..a7043df1d3
--- /dev/null
+++ b/third_party/git/t/helper/test-advise.c
@@ -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 (argc != 2)
+		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;
+}
diff --git a/third_party/git/t/helper/test-bloom.c b/third_party/git/t/helper/test-bloom.c
new file mode 100644
index 0000000000..46e97b04eb
--- /dev/null
+++ b/third_party/git/t/helper/test-bloom.c
@@ -0,0 +1,97 @@
+#include "git-compat-util.h"
+#include "bloom.h"
+#include "test-tool.h"
+#include "commit.h"
+
+static struct bloom_filter_settings settings = DEFAULT_BLOOM_FILTER_SETTINGS;
+
+static void add_string_to_filter(const char *data, struct bloom_filter *filter) {
+		struct bloom_key key;
+		int i;
+
+		fill_bloom_key(data, strlen(data), &key, &settings);
+		printf("Hashes:");
+		for (i = 0; i < settings.num_hashes; i++){
+			printf("0x%08x|", key.hashes[i]);
+		}
+		printf("\n");
+		add_key_to_filter(&key, filter, &settings);
+}
+
+static void print_bloom_filter(struct bloom_filter *filter) {
+	int i;
+
+	if (!filter) {
+		printf("No filter.\n");
+		return;
+	}
+	printf("Filter_Length:%d\n", (int)filter->len);
+	printf("Filter_Data:");
+	for (i = 0; i < filter->len; i++) {
+		printf("%02x|", filter->data[i]);
+	}
+	printf("\n");
+}
+
+static void get_bloom_filter_for_commit(const struct object_id *commit_oid)
+{
+	struct commit *c;
+	struct bloom_filter *filter;
+	setup_git_directory();
+	c = lookup_commit(the_repository, commit_oid);
+	filter = get_or_compute_bloom_filter(the_repository, c, 1,
+					     &settings,
+					     NULL);
+	print_bloom_filter(filter);
+}
+
+static const char *bloom_usage = "\n"
+"  test-tool bloom get_murmur3 <string>\n"
+"  test-tool bloom generate_filter <string> [<string>...]\n"
+"  test-tool get_filter_for_commit <commit-hex>\n";
+
+int cmd__bloom(int argc, const char **argv)
+{
+	setup_git_directory();
+
+	if (argc < 2)
+		usage(bloom_usage);
+
+	if (!strcmp(argv[1], "get_murmur3")) {
+		uint32_t hashed;
+		if (argc < 3)
+			usage(bloom_usage);
+		hashed = murmur3_seeded(0, argv[2], strlen(argv[2]));
+		printf("Murmur3 Hash with seed=0:0x%08x\n", hashed);
+	}
+
+	if (!strcmp(argv[1], "generate_filter")) {
+		struct bloom_filter filter;
+		int i = 2;
+		filter.len =  (settings.bits_per_entry + BITS_PER_WORD - 1) / BITS_PER_WORD;
+		filter.data = xcalloc(filter.len, sizeof(unsigned char));
+
+		if (argc - 1 < i)
+			usage(bloom_usage);
+
+		while (argv[i]) {
+			add_string_to_filter(argv[i], &filter);
+			i++;
+		}
+
+		print_bloom_filter(&filter);
+	}
+
+	if (!strcmp(argv[1], "get_filter_for_commit")) {
+		struct object_id oid;
+		const char *end;
+		if (argc < 3)
+			usage(bloom_usage);
+		if (parse_oid_hex(argv[2], &oid, &end))
+			die("cannot parse oid '%s'", argv[2]);
+		init_bloom_filters();
+		get_bloom_filter_for_commit(&oid);
+	}
+
+	return 0;
+}
diff --git a/third_party/git/t/helper/test-config.c b/third_party/git/t/helper/test-config.c
index 214003d5b2..a6e936721f 100644
--- a/third_party/git/t/helper/test-config.c
+++ b/third_party/git/t/helper/test-config.c
@@ -37,21 +37,6 @@
  *
  */
 
-static const char *scope_name(enum config_scope scope)
-{
-	switch (scope) {
-	case CONFIG_SCOPE_SYSTEM:
-		return "system";
-	case CONFIG_SCOPE_GLOBAL:
-		return "global";
-	case CONFIG_SCOPE_REPO:
-		return "repo";
-	case CONFIG_SCOPE_CMDLINE:
-		return "cmdline";
-	default:
-		return "unknown";
-	}
-}
 static int iterate_cb(const char *var, const char *value, void *data)
 {
 	static int nr;
@@ -63,7 +48,8 @@ static int iterate_cb(const char *var, const char *value, void *data)
 	printf("value=%s\n", value ? value : "(null)");
 	printf("origin=%s\n", current_config_origin_type());
 	printf("name=%s\n", current_config_name());
-	printf("scope=%s\n", scope_name(current_config_scope()));
+	printf("lno=%d\n", current_config_line());
+	printf("scope=%s\n", config_scope_name(current_config_scope()));
 
 	return 0;
 }
@@ -140,7 +126,7 @@ int cmd__config(int argc, const char **argv)
 			goto exit1;
 		}
 	} else if (argc == 3 && !strcmp(argv[1], "get_string")) {
-		if (!git_config_get_string_const(argv[2], &v)) {
+		if (!git_config_get_string_tmp(argv[2], &v)) {
 			printf("%s\n", v);
 			goto exit0;
 		} else {
diff --git a/third_party/git/t/helper/test-date.c b/third_party/git/t/helper/test-date.c
index 585347ea48..099eff4f0f 100644
--- a/third_party/git/t/helper/test-date.c
+++ b/third_party/git/t/helper/test-date.c
@@ -12,13 +12,13 @@ static const char *usage_msg = "\n"
 "  test-tool date is64bit\n"
 "  test-tool date time_t-is64bit\n";
 
-static void show_relative_dates(const char **argv, struct timeval *now)
+static void show_relative_dates(const char **argv)
 {
 	struct strbuf buf = STRBUF_INIT;
 
 	for (; *argv; argv++) {
 		time_t t = atoi(*argv);
-		show_date_relative(t, now, &buf);
+		show_date_relative(t, &buf);
 		printf("%s -> %s\n", *argv, buf.buf);
 	}
 	strbuf_release(&buf);
@@ -74,20 +74,20 @@ static void parse_dates(const char **argv)
 	strbuf_release(&result);
 }
 
-static void parse_approxidate(const char **argv, struct timeval *now)
+static void parse_approxidate(const char **argv)
 {
 	for (; *argv; argv++) {
 		timestamp_t t;
-		t = approxidate_relative(*argv, now);
+		t = approxidate_relative(*argv);
 		printf("%s -> %s\n", *argv, show_date(t, 0, DATE_MODE(ISO8601)));
 	}
 }
 
-static void parse_approx_timestamp(const char **argv, struct timeval *now)
+static void parse_approx_timestamp(const char **argv)
 {
 	for (; *argv; argv++) {
 		timestamp_t t;
-		t = approxidate_relative(*argv, now);
+		t = approxidate_relative(*argv);
 		printf("%s -> %"PRItime"\n", *argv, t);
 	}
 }
@@ -103,22 +103,13 @@ static void getnanos(const char **argv)
 
 int cmd__date(int argc, const char **argv)
 {
-	struct timeval now;
 	const char *x;
 
-	x = getenv("GIT_TEST_DATE_NOW");
-	if (x) {
-		now.tv_sec = atoi(x);
-		now.tv_usec = 0;
-	}
-	else
-		gettimeofday(&now, NULL);
-
 	argv++;
 	if (!*argv)
 		usage(usage_msg);
 	if (!strcmp(*argv, "relative"))
-		show_relative_dates(argv+1, &now);
+		show_relative_dates(argv+1);
 	else if (!strcmp(*argv, "human"))
 		show_human_dates(argv+1);
 	else if (skip_prefix(*argv, "show:", &x))
@@ -126,9 +117,9 @@ int cmd__date(int argc, const char **argv)
 	else if (!strcmp(*argv, "parse"))
 		parse_dates(argv+1);
 	else if (!strcmp(*argv, "approxidate"))
-		parse_approxidate(argv+1, &now);
+		parse_approxidate(argv+1);
 	else if (!strcmp(*argv, "timestamp"))
-		parse_approx_timestamp(argv+1, &now);
+		parse_approx_timestamp(argv+1);
 	else if (!strcmp(*argv, "getnanos"))
 		getnanos(argv+1);
 	else if (!strcmp(*argv, "is64bit"))
diff --git a/third_party/git/t/helper/test-drop-caches.c b/third_party/git/t/helper/test-drop-caches.c
index f65e301f9d..7b4278462b 100644
--- a/third_party/git/t/helper/test-drop-caches.c
+++ b/third_party/git/t/helper/test-drop-caches.c
@@ -8,18 +8,21 @@ static int cmd_sync(void)
 {
 	char Buffer[MAX_PATH];
 	DWORD dwRet;
-	char szVolumeAccessPath[] = "\\\\.\\X:";
+	char szVolumeAccessPath[] = "\\\\.\\XXXX:";
 	HANDLE hVolWrite;
-	int success = 0;
+	int success = 0, dos_drive_prefix;
 
 	dwRet = GetCurrentDirectory(MAX_PATH, Buffer);
 	if ((0 == dwRet) || (dwRet > MAX_PATH))
 		return error("Error getting current directory");
 
-	if (!has_dos_drive_prefix(Buffer))
+	dos_drive_prefix = has_dos_drive_prefix(Buffer);
+	if (!dos_drive_prefix)
 		return error("'%s': invalid drive letter", Buffer);
 
-	szVolumeAccessPath[4] = Buffer[0];
+	memcpy(szVolumeAccessPath, Buffer, dos_drive_prefix);
+	szVolumeAccessPath[dos_drive_prefix] = '\0';
+
 	hVolWrite = CreateFile(szVolumeAccessPath, GENERIC_READ | GENERIC_WRITE,
 		FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
 	if (INVALID_HANDLE_VALUE == hVolWrite)
diff --git a/third_party/git/t/helper/test-dump-fsmonitor.c b/third_party/git/t/helper/test-dump-fsmonitor.c
index 2786f47088..975f0ac890 100644
--- a/third_party/git/t/helper/test-dump-fsmonitor.c
+++ b/third_party/git/t/helper/test-dump-fsmonitor.c
@@ -13,7 +13,7 @@ int cmd__dump_fsmonitor(int ac, const char **av)
 		printf("no fsmonitor\n");
 		return 0;
 	}
-	printf("fsmonitor last update %"PRIuMAX"\n", (uintmax_t)istate->fsmonitor_last_update);
+	printf("fsmonitor last update %s\n", istate->fsmonitor_last_update);
 
 	for (i = 0; i < istate->cache_nr; i++)
 		printf((istate->cache[i]->ce_flags & CE_FSMONITOR_VALID) ? "+" : "-");
diff --git a/third_party/git/t/helper/test-dump-split-index.c b/third_party/git/t/helper/test-dump-split-index.c
index 63c689d6ee..a209880eb3 100644
--- a/third_party/git/t/helper/test-dump-split-index.c
+++ b/third_party/git/t/helper/test-dump-split-index.c
@@ -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;
diff --git a/third_party/git/t/helper/test-hashmap.c b/third_party/git/t/helper/test-hashmap.c
index aaf17b0ddf..f38706216f 100644
--- a/third_party/git/t/helper/test-hashmap.c
+++ b/third_party/git/t/helper/test-hashmap.c
@@ -5,6 +5,7 @@
 
 struct test_entry
 {
+	int padding; /* hashmap entry no longer needs to be the first member */
 	struct hashmap_entry ent;
 	/* key and value as two \0-terminated strings */
 	char key[FLEX_ARRAY];
@@ -16,15 +17,17 @@ static const char *get_value(const struct test_entry *e)
 }
 
 static int test_entry_cmp(const void *cmp_data,
-			  const void *entry,
-			  const void *entry_or_key,
+			  const struct hashmap_entry *eptr,
+			  const struct hashmap_entry *entry_or_key,
 			  const void *keydata)
 {
 	const int ignore_case = cmp_data ? *((int *)cmp_data) : 0;
-	const struct test_entry *e1 = entry;
-	const struct test_entry *e2 = entry_or_key;
+	const struct test_entry *e1, *e2;
 	const char *key = keydata;
 
+	e1 = container_of(eptr, const struct test_entry, ent);
+	e2 = container_of(entry_or_key, const struct test_entry, ent);
+
 	if (ignore_case)
 		return strcasecmp(e1->key, key ? key : e2->key);
 	else
@@ -37,7 +40,7 @@ static struct test_entry *alloc_test_entry(unsigned int hash,
 	size_t klen = strlen(key);
 	size_t vlen = strlen(value);
 	struct test_entry *entry = xmalloc(st_add4(sizeof(*entry), klen, vlen, 2));
-	hashmap_entry_init(entry, hash);
+	hashmap_entry_init(&entry->ent, hash);
 	memcpy(entry->key, key, klen + 1);
 	memcpy(entry->key + klen + 1, value, vlen + 1);
 	return entry;
@@ -103,11 +106,11 @@ static void perf_hashmap(unsigned int method, unsigned int rounds)
 
 			/* add entries */
 			for (i = 0; i < TEST_SIZE; i++) {
-				hashmap_entry_init(entries[i], hashes[i]);
-				hashmap_add(&map, entries[i]);
+				hashmap_entry_init(&entries[i]->ent, hashes[i]);
+				hashmap_add(&map, &entries[i]->ent);
 			}
 
-			hashmap_free(&map, 0);
+			hashmap_free(&map);
 		}
 	} else {
 		/* test map lookups */
@@ -116,8 +119,8 @@ static void perf_hashmap(unsigned int method, unsigned int rounds)
 		/* fill the map (sparsely if specified) */
 		j = (method & TEST_SPARSE) ? TEST_SIZE / 10 : TEST_SIZE;
 		for (i = 0; i < j; i++) {
-			hashmap_entry_init(entries[i], hashes[i]);
-			hashmap_add(&map, entries[i]);
+			hashmap_entry_init(&entries[i]->ent, hashes[i]);
+			hashmap_add(&map, &entries[i]->ent);
 		}
 
 		for (j = 0; j < rounds; j++) {
@@ -127,7 +130,7 @@ static void perf_hashmap(unsigned int method, unsigned int rounds)
 			}
 		}
 
-		hashmap_free(&map, 0);
+		hashmap_free(&map);
 	}
 }
 
@@ -179,7 +182,7 @@ int cmd__hashmap(int argc, const char **argv)
 			entry = alloc_test_entry(hash, p1, p2);
 
 			/* add to hashmap */
-			hashmap_add(&map, entry);
+			hashmap_add(&map, &entry->ent);
 
 		} else if (!strcmp("put", cmd) && p1 && p2) {
 
@@ -187,43 +190,44 @@ int cmd__hashmap(int argc, const char **argv)
 			entry = alloc_test_entry(hash, p1, p2);
 
 			/* add / replace entry */
-			entry = hashmap_put(&map, entry);
+			entry = hashmap_put_entry(&map, entry, ent);
 
 			/* print and free replaced entry, if any */
 			puts(entry ? get_value(entry) : "NULL");
 			free(entry);
 
 		} else if (!strcmp("get", cmd) && p1) {
-
 			/* lookup entry in hashmap */
-			entry = hashmap_get_from_hash(&map, hash, p1);
+			entry = hashmap_get_entry_from_hash(&map, hash, p1,
+							struct test_entry, ent);
 
 			/* print result */
 			if (!entry)
 				puts("NULL");
-			while (entry) {
+			hashmap_for_each_entry_from(&map, entry, ent)
 				puts(get_value(entry));
-				entry = hashmap_get_next(&map, entry);
-			}
 
 		} else if (!strcmp("remove", cmd) && p1) {
 
 			/* setup static key */
 			struct hashmap_entry key;
+			struct hashmap_entry *rm;
 			hashmap_entry_init(&key, hash);
 
 			/* remove entry from hashmap */
-			entry = hashmap_remove(&map, &key, p1);
+			rm = hashmap_remove(&map, &key, p1);
+			entry = rm ? container_of(rm, struct test_entry, ent)
+					: NULL;
 
 			/* print result and free entry*/
 			puts(entry ? get_value(entry) : "NULL");
 			free(entry);
 
 		} else if (!strcmp("iterate", cmd)) {
-
 			struct hashmap_iter iter;
-			hashmap_iter_init(&map, &iter);
-			while ((entry = hashmap_iter_next(&iter)))
+
+			hashmap_for_each_entry(&map, &iter, entry,
+						ent /* member name */)
 				printf("%s %s\n", entry->key, get_value(entry));
 
 		} else if (!strcmp("size", cmd)) {
@@ -258,6 +262,6 @@ int cmd__hashmap(int argc, const char **argv)
 	}
 
 	strbuf_release(&line);
-	hashmap_free(&map, 1);
+	hashmap_free_entries(&map, struct test_entry, ent);
 	return 0;
 }
diff --git a/third_party/git/t/helper/test-lazy-init-name-hash.c b/third_party/git/t/helper/test-lazy-init-name-hash.c
index b99a37080d..cd1b4c9736 100644
--- a/third_party/git/t/helper/test-lazy-init-name-hash.c
+++ b/third_party/git/t/helper/test-lazy-init-name-hash.c
@@ -41,17 +41,13 @@ static void dump_run(void)
 			die("non-threaded code path used");
 	}
 
-	dir = hashmap_iter_first(&the_index.dir_hash, &iter_dir);
-	while (dir) {
+	hashmap_for_each_entry(&the_index.dir_hash, &iter_dir, dir,
+				ent /* member name */)
 		printf("dir %08x %7d %s\n", dir->ent.hash, dir->nr, dir->name);
-		dir = hashmap_iter_next(&iter_dir);
-	}
 
-	ce = hashmap_iter_first(&the_index.name_hash, &iter_cache);
-	while (ce) {
+	hashmap_for_each_entry(&the_index.name_hash, &iter_cache, ce,
+				ent /* member name */)
 		printf("name %08x %s\n", ce->ent.hash, ce->name);
-		ce = hashmap_iter_next(&iter_cache);
-	}
 
 	discard_cache();
 }
diff --git a/third_party/git/t/helper/test-line-buffer.c b/third_party/git/t/helper/test-line-buffer.c
deleted file mode 100644
index 078dd7e29d..0000000000
--- a/third_party/git/t/helper/test-line-buffer.c
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * test-line-buffer.c: code to exercise the svn importer's input helper
- */
-
-#include "git-compat-util.h"
-#include "strbuf.h"
-#include "vcs-svn/line_buffer.h"
-
-static uint32_t strtouint32(const char *s)
-{
-	char *end;
-	uintmax_t n = strtoumax(s, &end, 10);
-	if (*s == '\0' || *end != '\0')
-		die("invalid count: %s", s);
-	return (uint32_t) n;
-}
-
-static void handle_command(const char *command, const char *arg, struct line_buffer *buf)
-{
-	if (starts_with(command, "binary ")) {
-		struct strbuf sb = STRBUF_INIT;
-		strbuf_addch(&sb, '>');
-		buffer_read_binary(buf, &sb, strtouint32(arg));
-		fwrite(sb.buf, 1, sb.len, stdout);
-		strbuf_release(&sb);
-	} else if (starts_with(command, "copy ")) {
-		buffer_copy_bytes(buf, strtouint32(arg));
-	} else if (starts_with(command, "skip ")) {
-		buffer_skip_bytes(buf, strtouint32(arg));
-	} else {
-		die("unrecognized command: %s", command);
-	}
-}
-
-static void handle_line(const char *line, struct line_buffer *stdin_buf)
-{
-	const char *arg = strchr(line, ' ');
-	if (!arg)
-		die("no argument in line: %s", line);
-	handle_command(line, arg + 1, stdin_buf);
-}
-
-int cmd_main(int argc, const char **argv)
-{
-	struct line_buffer stdin_buf = LINE_BUFFER_INIT;
-	struct line_buffer file_buf = LINE_BUFFER_INIT;
-	struct line_buffer *input = &stdin_buf;
-	const char *filename;
-	char *s;
-
-	if (argc == 1)
-		filename = NULL;
-	else if (argc == 2)
-		filename = argv[1];
-	else
-		usage("test-line-buffer [file | &fd] < script");
-
-	if (buffer_init(&stdin_buf, NULL))
-		die_errno("open error");
-	if (filename) {
-		if (*filename == '&') {
-			if (buffer_fdinit(&file_buf, strtouint32(filename + 1)))
-				die_errno("error opening fd %s", filename + 1);
-		} else {
-			if (buffer_init(&file_buf, filename))
-				die_errno("error opening %s", filename);
-		}
-		input = &file_buf;
-	}
-
-	while ((s = buffer_read_line(&stdin_buf)))
-		handle_line(s, input);
-
-	if (filename && buffer_deinit(&file_buf))
-		die("error reading from %s", filename);
-	if (buffer_deinit(&stdin_buf))
-		die("input error");
-	if (ferror(stdout))
-		die("output error");
-	return 0;
-}
diff --git a/third_party/git/t/helper/test-sha1-array.c b/third_party/git/t/helper/test-oid-array.c
index ad5e69f9d3..b16cd0b11b 100644
--- a/third_party/git/t/helper/test-sha1-array.c
+++ b/third_party/git/t/helper/test-oid-array.c
@@ -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,10 +8,13 @@ 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;
+	int nongit_ok;
+
+	setup_git_directory_gently(&nongit_ok);
 
 	while (strbuf_getline(&line, stdin) != EOF) {
 		const char *arg;
@@ -19,11 +22,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);
diff --git a/third_party/git/t/helper/test-parse-options.c b/third_party/git/t/helper/test-parse-options.c
index af82db06ac..2051ce57db 100644
--- a/third_party/git/t/helper/test-parse-options.c
+++ b/third_party/git/t/helper/test-parse-options.c
@@ -121,6 +121,8 @@ int cmd__parse_options(int argc, const char **argv)
 		OPT_INTEGER('j', NULL, &integer, "get a integer, too"),
 		OPT_MAGNITUDE('m', "magnitude", &magnitude, "get a magnitude"),
 		OPT_SET_INT(0, "set23", &integer, "set integer to 23", 23),
+		OPT_CMDMODE(0, "mode1", &integer, "set integer to 1 (cmdmode option)", 1),
+		OPT_CMDMODE(0, "mode2", &integer, "set integer to 2 (cmdmode option)", 2),
 		OPT_CALLBACK('L', "length", &integer, "str",
 			"get length of <str>", length_callback),
 		OPT_FILENAME('F', "file", &file, "set file to <file>"),
diff --git a/third_party/git/t/helper/test-parse-pathspec-file.c b/third_party/git/t/helper/test-parse-pathspec-file.c
new file mode 100644
index 0000000000..b3e08cef4b
--- /dev/null
+++ b/third_party/git/t/helper/test-parse-pathspec-file.c
@@ -0,0 +1,33 @@
+#include "test-tool.h"
+#include "parse-options.h"
+#include "pathspec.h"
+#include "gettext.h"
+
+int cmd__parse_pathspec_file(int argc, const char **argv)
+{
+	struct pathspec pathspec;
+	const char *pathspec_from_file = NULL;
+	int pathspec_file_nul = 0, i;
+
+	static const char *const usage[] = {
+		"test-tool parse-pathspec-file --pathspec-from-file [--pathspec-file-nul]",
+		NULL
+	};
+
+	struct option options[] = {
+		OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
+		OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul),
+		OPT_END()
+	};
+
+	parse_options(argc, argv, NULL, options, usage, 0);
+
+	parse_pathspec_file(&pathspec, 0, 0, NULL, pathspec_from_file,
+			    pathspec_file_nul);
+
+	for (i = 0; i < pathspec.nr; i++)
+		printf("%s\n", pathspec.items[i].original);
+
+	clear_pathspec(&pathspec);
+	return 0;
+}
diff --git a/third_party/git/t/helper/test-path-utils.c b/third_party/git/t/helper/test-path-utils.c
index 5d543ad21f..313a153209 100644
--- a/third_party/git/t/helper/test-path-utils.c
+++ b/third_party/git/t/helper/test-path-utils.c
@@ -185,6 +185,99 @@ static int cmp_by_st_size(const void *a, const void *b)
 	return x > y ? -1 : (x < y ? +1 : 0);
 }
 
+/*
+ * A very simple, reproducible pseudo-random generator. Copied from
+ * `test-genrandom.c`.
+ */
+static uint64_t my_random_value = 1234;
+
+static uint64_t my_random(void)
+{
+	my_random_value = my_random_value * 1103515245 + 12345;
+	return my_random_value;
+}
+
+/*
+ * A fast approximation of the square root, without requiring math.h.
+ *
+ * It uses Newton's method to approximate the solution of 0 = x^2 - value.
+ */
+static double my_sqrt(double value)
+{
+	const double epsilon = 1e-6;
+	double x = value;
+
+	if (value == 0)
+		return 0;
+
+	for (;;) {
+		double delta = (value / x - x) / 2;
+		if (delta < epsilon && delta > -epsilon)
+			return x + delta;
+		x += delta;
+	}
+}
+
+static int protect_ntfs_hfs_benchmark(int argc, const char **argv)
+{
+	size_t i, j, nr, min_len = 3, max_len = 20;
+	char **names;
+	int repetitions = 15, file_mode = 0100644;
+	uint64_t begin, end;
+	double m[3][2], v[3][2];
+	uint64_t cumul;
+	double cumul2;
+
+	if (argc > 1 && !strcmp(argv[1], "--with-symlink-mode")) {
+		file_mode = 0120000;
+		argc--;
+		argv++;
+	}
+
+	nr = argc > 1 ? strtoul(argv[1], NULL, 0) : 1000000;
+	ALLOC_ARRAY(names, nr);
+
+	if (argc > 2) {
+		min_len = strtoul(argv[2], NULL, 0);
+		if (argc > 3)
+			max_len = strtoul(argv[3], NULL, 0);
+		if (min_len > max_len)
+			die("min_len > max_len");
+	}
+
+	for (i = 0; i < nr; i++) {
+		size_t len = min_len + (my_random() % (max_len + 1 - min_len));
+
+		names[i] = xmallocz(len);
+		while (len > 0)
+			names[i][--len] = (char)(' ' + (my_random() % ('\x7f' - ' ')));
+	}
+
+	for (protect_ntfs = 0; protect_ntfs < 2; protect_ntfs++)
+		for (protect_hfs = 0; protect_hfs < 2; protect_hfs++) {
+			cumul = 0;
+			cumul2 = 0;
+			for (i = 0; i < repetitions; i++) {
+				begin = getnanotime();
+				for (j = 0; j < nr; j++)
+					verify_path(names[j], file_mode);
+				end = getnanotime();
+				printf("protect_ntfs = %d, protect_hfs = %d: %lfms\n", protect_ntfs, protect_hfs, (end-begin) / (double)1e6);
+				cumul += end - begin;
+				cumul2 += (end - begin) * (end - begin);
+			}
+			m[protect_ntfs][protect_hfs] = cumul / (double)repetitions;
+			v[protect_ntfs][protect_hfs] = my_sqrt(cumul2 / (double)repetitions - m[protect_ntfs][protect_hfs] * m[protect_ntfs][protect_hfs]);
+			printf("mean: %lfms, stddev: %lfms\n", m[protect_ntfs][protect_hfs] / (double)1e6, v[protect_ntfs][protect_hfs] / (double)1e6);
+		}
+
+	for (protect_ntfs = 0; protect_ntfs < 2; protect_ntfs++)
+		for (protect_hfs = 0; protect_hfs < 2; protect_hfs++)
+			printf("ntfs=%d/hfs=%d: %lf%% slower\n", protect_ntfs, protect_hfs, (m[protect_ntfs][protect_hfs] - m[0][0]) * 100 / m[0][0]);
+
+	return 0;
+}
+
 int cmd__path_utils(int argc, const char **argv)
 {
 	if (argc == 3 && !strcmp(argv[1], "normalize_path_copy")) {
@@ -197,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;
 	}
 
@@ -355,6 +451,26 @@ int cmd__path_utils(int argc, const char **argv)
 		return !!res;
 	}
 
+	if (argc > 1 && !strcmp(argv[1], "protect_ntfs_hfs"))
+		return !!protect_ntfs_hfs_benchmark(argc - 1, argv + 1);
+
+	if (argc > 1 && !strcmp(argv[1], "is_valid_path")) {
+		int res = 0, expect = 1, i;
+
+		for (i = 2; i < argc; i++)
+			if (!strcmp("--not", argv[i]))
+				expect = 0;
+			else if (expect != is_valid_path(argv[i]))
+				res = error("'%s' is%s a valid path",
+					    argv[i], expect ? " not" : "");
+			else
+				fprintf(stderr,
+					"'%s' is%s a valid path\n",
+					argv[i], expect ? "" : " not");
+
+		return !!res;
+	}
+
 	fprintf(stderr, "%s: unknown function name: %s\n", argv[0],
 		argv[1] ? argv[1] : "(there was none)");
 	return 1;
diff --git a/third_party/git/t/helper/test-pkt-line.c b/third_party/git/t/helper/test-pkt-line.c
index 282d536384..69152958e5 100644
--- a/third_party/git/t/helper/test-pkt-line.c
+++ b/third_party/git/t/helper/test-pkt-line.c
@@ -46,6 +46,9 @@ static void unpack(void)
 		case PACKET_READ_DELIM:
 			printf("0001\n");
 			break;
+		case PACKET_READ_RESPONSE_END:
+			printf("0002\n");
+			break;
 		}
 	}
 }
@@ -67,7 +70,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);
@@ -75,6 +78,7 @@ static void unpack_sideband(void)
 		case PACKET_READ_FLUSH:
 			return;
 		case PACKET_READ_DELIM:
+		case PACKET_READ_RESPONSE_END:
 			break;
 		}
 	}
diff --git a/third_party/git/t/helper/test-proc-receive.c b/third_party/git/t/helper/test-proc-receive.c
new file mode 100644
index 0000000000..42164d9898
--- /dev/null
+++ b/third_party/git/t/helper/test-proc-receive.c
@@ -0,0 +1,176 @@
+#include "cache.h"
+#include "connect.h"
+#include "parse-options.h"
+#include "pkt-line.h"
+#include "sigchain.h"
+#include "test-tool.h"
+
+static const char *proc_receive_usage[] = {
+	"test-tool proc-receive [<options>...]",
+	NULL
+};
+
+static int die_version;
+static int die_readline;
+static int no_push_options;
+static int use_atomic;
+static int use_push_options;
+static int verbose;
+static int version = 1;
+static struct string_list returns = STRING_LIST_INIT_NODUP;
+
+struct command {
+	struct command *next;
+	const char *error_string;
+	unsigned int skip_update:1,
+		     did_not_exist:1;
+	int index;
+	struct object_id old_oid;
+	struct object_id new_oid;
+	char ref_name[FLEX_ARRAY]; /* more */
+};
+
+static void proc_receive_verison(struct packet_reader *reader) {
+	int server_version = 0;
+
+	for (;;) {
+		int linelen;
+
+		if (packet_reader_read(reader) != PACKET_READ_NORMAL)
+			break;
+
+		if (reader->pktlen > 8 && starts_with(reader->line, "version=")) {
+			server_version = atoi(reader->line+8);
+			linelen = strlen(reader->line);
+			if (linelen < reader->pktlen) {
+				const char *feature_list = reader->line + linelen + 1;
+				if (parse_feature_request(feature_list, "atomic"))
+					use_atomic= 1;
+				if (parse_feature_request(feature_list, "push-options"))
+					use_push_options = 1;
+			}
+		}
+	}
+
+	if (server_version != 1 || die_version)
+		die("bad protocol version: %d", server_version);
+
+	packet_write_fmt(1, "version=%d%c%s\n",
+			 version, '\0',
+			 use_push_options && !no_push_options ? "push-options": "");
+	packet_flush(1);
+}
+
+static void proc_receive_read_commands(struct packet_reader *reader,
+				       struct command **commands)
+{
+	struct command **tail = commands;
+
+	for (;;) {
+		struct object_id old_oid, new_oid;
+		struct command *cmd;
+		const char *refname;
+		const char *p;
+
+		if (packet_reader_read(reader) != PACKET_READ_NORMAL)
+			break;
+
+		if (parse_oid_hex(reader->line, &old_oid, &p) ||
+		    *p++ != ' ' ||
+		    parse_oid_hex(p, &new_oid, &p) ||
+		    *p++ != ' ' ||
+		    die_readline)
+			die("protocol error: expected 'old new ref', got '%s'",
+			    reader->line);
+		refname = p;
+		FLEX_ALLOC_STR(cmd, ref_name, refname);
+		oidcpy(&cmd->old_oid, &old_oid);
+		oidcpy(&cmd->new_oid, &new_oid);
+
+		*tail = cmd;
+		tail = &cmd->next;
+	}
+}
+
+static void proc_receive_read_push_options(struct packet_reader *reader,
+					   struct string_list *options)
+{
+
+	if (no_push_options || !use_push_options)
+	       return;
+
+	while (1) {
+		if (packet_reader_read(reader) != PACKET_READ_NORMAL)
+			break;
+
+		string_list_append(options, reader->line);
+	}
+}
+
+int cmd__proc_receive(int argc, const char **argv)
+{
+	int nongit_ok = 0;
+	struct packet_reader reader;
+	struct command *commands = NULL;
+	struct string_list push_options = STRING_LIST_INIT_DUP;
+	struct string_list_item *item;
+	struct option options[] = {
+		OPT_BOOL(0, "no-push-options", &no_push_options,
+			 "disable push options"),
+		OPT_BOOL(0, "die-version", &die_version,
+			 "die during version negotiation"),
+		OPT_BOOL(0, "die-readline", &die_readline,
+			 "die when readline"),
+		OPT_STRING_LIST('r', "return", &returns, "old/new/ref/status/msg",
+				"return of results"),
+		OPT__VERBOSE(&verbose, "be verbose"),
+		OPT_INTEGER('V', "version", &version,
+			    "use this protocol version number"),
+		OPT_END()
+	};
+
+	setup_git_directory_gently(&nongit_ok);
+
+	argc = parse_options(argc, argv, "test-tools", options, proc_receive_usage, 0);
+	if (argc > 0)
+		usage_msg_opt("Too many arguments.", proc_receive_usage, options);
+	packet_reader_init(&reader, 0, NULL, 0,
+			   PACKET_READ_CHOMP_NEWLINE |
+			   PACKET_READ_DIE_ON_ERR_PACKET);
+
+	sigchain_push(SIGPIPE, SIG_IGN);
+	proc_receive_verison(&reader);
+	proc_receive_read_commands(&reader, &commands);
+	proc_receive_read_push_options(&reader, &push_options);
+
+	if (verbose) {
+		struct command *cmd;
+
+		if (use_push_options || use_atomic)
+			fprintf(stderr, "proc-receive:%s%s\n",
+				use_atomic? " atomic": "",
+				use_push_options ? " push_options": "");
+
+		for (cmd = commands; cmd; cmd = cmd->next)
+			fprintf(stderr, "proc-receive< %s %s %s\n",
+				oid_to_hex(&cmd->old_oid),
+				oid_to_hex(&cmd->new_oid),
+				cmd->ref_name);
+
+		if (push_options.nr > 0)
+			for_each_string_list_item(item, &push_options)
+				fprintf(stderr, "proc-receive< %s\n", item->string);
+
+		if (returns.nr)
+			for_each_string_list_item(item, &returns)
+				fprintf(stderr, "proc-receive> %s\n", item->string);
+	}
+
+	if (returns.nr)
+		for_each_string_list_item(item, &returns)
+			packet_write_fmt(1, "%s\n", item->string);
+	packet_flush(1);
+	sigchain_pop(SIGPIPE);
+
+	return 0;
+}
diff --git a/third_party/git/t/helper/test-progress.c b/third_party/git/t/helper/test-progress.c
new file mode 100644
index 0000000000..5d05cbe789
--- /dev/null
+++ b/third_party/git/t/helper/test-progress.c
@@ -0,0 +1,74 @@
+/*
+ * A test helper to exercise the progress display.
+ *
+ * Reads instructions from standard input, one instruction per line:
+ *
+ *   "progress <items>" - Call display_progress() with the given item count
+ *                        as parameter.
+ *   "throughput <bytes> <millis> - Call display_throughput() with the given
+ *                                  byte count as parameter.  The 'millis'
+ *                                  specify the time elapsed since the
+ *                                  start_progress() call.
+ *   "update" - Set the 'progress_update' flag.
+ *
+ * See 't0500-progress-display.sh' for examples.
+ */
+#define GIT_TEST_PROGRESS_ONLY
+#include "test-tool.h"
+#include "gettext.h"
+#include "parse-options.h"
+#include "progress.h"
+#include "strbuf.h"
+
+int cmd__progress(int argc, const char **argv)
+{
+	int total = 0;
+	const char *title;
+	struct strbuf line = STRBUF_INIT;
+	struct progress *progress;
+
+	const char *usage[] = {
+		"test-tool progress [--total=<n>] <progress-title>",
+		NULL
+	};
+	struct option options[] = {
+		OPT_INTEGER(0, "total", &total, "total number of items"),
+		OPT_END(),
+	};
+
+	argc = parse_options(argc, argv, NULL, options, usage, 0);
+	if (argc != 1)
+		die("need a title for the progress output");
+	title = argv[0];
+
+	progress_testing = 1;
+	progress = start_progress(title, total);
+	while (strbuf_getline(&line, stdin) != EOF) {
+		char *end;
+
+		if (skip_prefix(line.buf, "progress ", (const char **) &end)) {
+			uint64_t item_count = strtoull(end, &end, 10);
+			if (*end != '\0')
+				die("invalid input: '%s'\n", line.buf);
+			display_progress(progress, item_count);
+		} else if (skip_prefix(line.buf, "throughput ",
+				       (const char **) &end)) {
+			uint64_t byte_count, test_ms;
+
+			byte_count = strtoull(end, &end, 10);
+			if (*end != ' ')
+				die("invalid input: '%s'\n", line.buf);
+			test_ms = strtoull(end + 1, &end, 10);
+			if (*end != '\0')
+				die("invalid input: '%s'\n", line.buf);
+			progress_test_ns = test_ms * 1000 * 1000;
+			display_throughput(progress, byte_count);
+		} else if (!strcmp(line.buf, "update"))
+			progress_test_force_update();
+		else
+			die("invalid input: '%s'\n", line.buf);
+	}
+	stop_progress(&progress);
+
+	return 0;
+}
diff --git a/third_party/git/t/helper/test-reach.c b/third_party/git/t/helper/test-reach.c
index a0272178b7..cda804ed79 100644
--- a/third_party/git/t/helper/test-reach.c
+++ b/third_party/git/t/helper/test-reach.c
@@ -67,7 +67,7 @@ int cmd__reach(int ac, const char **av)
 			die("failed to load commit for input %s resulting in oid %s\n",
 			    buf.buf, oid_to_hex(&oid));
 
-		c = object_as_type(r, peeled, OBJ_COMMIT, 0);
+		c = object_as_type(peeled, OBJ_COMMIT, 0);
 
 		if (!c)
 			die("failed to load commit for input %s resulting in oid %s\n",
@@ -107,8 +107,10 @@ int cmd__reach(int ac, const char **av)
 		printf("%s(A,B):%d\n", av[1], ref_newer(&oid_A, &oid_B));
 	else if (!strcmp(av[1], "in_merge_bases"))
 		printf("%s(A,B):%d\n", av[1], in_merge_bases(A, B));
+	else if (!strcmp(av[1], "in_merge_bases_many"))
+		printf("%s(A,X):%d\n", av[1], in_merge_bases_many(A, X_nr, X_array));
 	else if (!strcmp(av[1], "is_descendant_of"))
-		printf("%s(A,X):%d\n", av[1], is_descendant_of(A, X));
+		printf("%s(A,X):%d\n", av[1], repo_is_descendant_of(r, A, X));
 	else if (!strcmp(av[1], "get_merge_bases_many")) {
 		struct commit_list *list = get_merge_bases_many(A, X_nr, X_array);
 		printf("%s(A,X):\n", av[1]);
diff --git a/third_party/git/t/helper/test-read-cache.c b/third_party/git/t/helper/test-read-cache.c
index 7e79b555de..244977a29b 100644
--- a/third_party/git/t/helper/test-read-cache.c
+++ b/third_party/git/t/helper/test-read-cache.c
@@ -4,11 +4,10 @@
 
 int cmd__read_cache(int argc, const char **argv)
 {
-	int i, cnt = 1, namelen;
+	int i, cnt = 1;
 	const char *name = NULL;
 
 	if (argc > 1 && skip_prefix(argv[1], "--print-and-refresh=", &name)) {
-		namelen = strlen(name);
 		argc--;
 		argv++;
 	}
@@ -24,7 +23,7 @@ int cmd__read_cache(int argc, const char **argv)
 
 			refresh_index(&the_index, REFRESH_QUIET,
 				      NULL, NULL, NULL);
-			pos = index_name_pos(&the_index, name, namelen);
+			pos = index_name_pos(&the_index, name, strlen(name));
 			if (pos < 0)
 				die("%s not in index", name);
 			printf("%s is%s up to date\n", name,
diff --git a/third_party/git/t/helper/test-read-graph.c b/third_party/git/t/helper/test-read-graph.c
new file mode 100644
index 0000000000..5f585a1725
--- /dev/null
+++ b/third_party/git/t/helper/test-read-graph.c
@@ -0,0 +1,47 @@
+#include "test-tool.h"
+#include "cache.h"
+#include "commit-graph.h"
+#include "repository.h"
+#include "object-store.h"
+
+int cmd__read_graph(int argc, const char **argv)
+{
+	struct commit_graph *graph = NULL;
+	struct object_directory *odb;
+
+	setup_git_directory();
+	odb = the_repository->objects->odb;
+
+	prepare_repo_settings(the_repository);
+
+	graph = read_commit_graph_one(the_repository, odb);
+	if (!graph)
+		return 1;
+
+	printf("header: %08x %d %d %d %d\n",
+		ntohl(*(uint32_t*)graph->data),
+		*(unsigned char*)(graph->data + 4),
+		*(unsigned char*)(graph->data + 5),
+		*(unsigned char*)(graph->data + 6),
+		*(unsigned char*)(graph->data + 7));
+	printf("num_commits: %u\n", graph->num_commits);
+	printf("chunks:");
+
+	if (graph->chunk_oid_fanout)
+		printf(" oid_fanout");
+	if (graph->chunk_oid_lookup)
+		printf(" oid_lookup");
+	if (graph->chunk_commit_data)
+		printf(" commit_metadata");
+	if (graph->chunk_extra_edges)
+		printf(" extra_edges");
+	if (graph->chunk_bloom_indexes)
+		printf(" bloom_indexes");
+	if (graph->chunk_bloom_data)
+		printf(" bloom_data");
+	printf("\n");
+
+	UNLEAK(graph);
+
+	return 0;
+}
diff --git a/third_party/git/t/helper/test-read-midx.c b/third_party/git/t/helper/test-read-midx.c
index 831b586d02..2430880f78 100644
--- a/third_party/git/t/helper/test-read-midx.c
+++ b/third_party/git/t/helper/test-read-midx.c
@@ -7,14 +7,18 @@
 static int read_midx_file(const char *object_dir)
 {
 	uint32_t i;
-	struct multi_pack_index *m = load_multi_pack_index(object_dir, 1);
+	struct multi_pack_index *m;
+
+	setup_git_directory();
+	m = load_multi_pack_index(object_dir, 1);
 
 	if (!m)
 		return 1;
 
-	printf("header: %08x %d %d %d\n",
+	printf("header: %08x %d %d %d %d\n",
 	       m->signature,
 	       m->version,
+	       m->hash_len,
 	       m->num_chunks,
 	       m->num_packs);
 
diff --git a/third_party/git/t/helper/test-ref-store.c b/third_party/git/t/helper/test-ref-store.c
index 799fc00aa1..759e69dc54 100644
--- a/third_party/git/t/helper/test-ref-store.c
+++ b/third_party/git/t/helper/test-ref-store.c
@@ -37,7 +37,7 @@ static const char **get_store(const char **argv, struct ref_store **refs)
 
 		*refs = get_submodule_ref_store(gitdir);
 	} else if (skip_prefix(argv[0], "worktree:", &gitdir)) {
-		struct worktree **p, **worktrees = get_worktrees(0);
+		struct worktree **p, **worktrees = get_worktrees();
 
 		for (p = worktrees; *p; p++) {
 			struct worktree *wt = *p;
diff --git a/third_party/git/t/helper/test-regex.c b/third_party/git/t/helper/test-regex.c
index 10284cc56f..d6f28ca8d1 100644
--- a/third_party/git/t/helper/test-regex.c
+++ b/third_party/git/t/helper/test-regex.c
@@ -1,5 +1,4 @@
 #include "test-tool.h"
-#include "git-compat-util.h"
 #include "gettext.h"
 
 struct reg_flag {
@@ -8,12 +7,13 @@ struct reg_flag {
 };
 
 static struct reg_flag reg_flags[] = {
-	{ "EXTENDED",	 REG_EXTENDED	},
-	{ "NEWLINE",	 REG_NEWLINE	},
-	{ "ICASE",	 REG_ICASE	},
-	{ "NOTBOL",	 REG_NOTBOL	},
+	{ "EXTENDED",	REG_EXTENDED	},
+	{ "NEWLINE",	REG_NEWLINE	},
+	{ "ICASE",	REG_ICASE	},
+	{ "NOTBOL",	REG_NOTBOL	},
+	{ "NOTEOL",	REG_NOTEOL	},
 #ifdef REG_STARTEND
-	{ "STARTEND",	 REG_STARTEND	},
+	{ "STARTEND",	REG_STARTEND	},
 #endif
 	{ NULL, 0 }
 };
@@ -41,36 +41,74 @@ int cmd__regex(int argc, const char **argv)
 {
 	const char *pat;
 	const char *str;
-	int flags = 0;
+	int ret, silent = 0, flags = 0;
 	regex_t r;
 	regmatch_t m[1];
-
-	if (argc == 2 && !strcmp(argv[1], "--bug"))
-		return test_regex_bug();
-	else if (argc < 3)
-		usage("test-tool regex --bug\n"
-		      "test-tool regex <pattern> <string> [<options>]");
+	char errbuf[64];
 
 	argv++;
-	pat = *argv++;
-	str = *argv++;
-	while (*argv) {
-		struct reg_flag *rf;
-		for (rf = reg_flags; rf->name; rf++)
-			if (!strcmp(*argv, rf->name)) {
-				flags |= rf->flag;
-				break;
-			}
-		if (!rf->name)
-			die("do not recognize %s", *argv);
+	argc--;
+
+	if (!argc)
+		goto usage;
+
+	if (!strcmp(*argv, "--bug")) {
+		if (argc == 1)
+			return test_regex_bug();
+		else
+			goto usage;
+	}
+	if (!strcmp(*argv, "--silent")) {
+		silent = 1;
 		argv++;
+		argc--;
+	}
+	if (!argc)
+		goto usage;
+
+	pat = *argv++;
+	if (argc == 1)
+		str = NULL;
+	else {
+		str = *argv++;
+		while (*argv) {
+			struct reg_flag *rf;
+			for (rf = reg_flags; rf->name; rf++)
+				if (!strcmp(*argv, rf->name)) {
+					flags |= rf->flag;
+					break;
+				}
+			if (!rf->name)
+				die("do not recognize flag %s", *argv);
+			argv++;
+		}
 	}
 	git_setup_gettext();
 
-	if (regcomp(&r, pat, flags))
-		die("failed regcomp() for pattern '%s'", pat);
-	if (regexec(&r, str, 1, m, 0))
-		return 1;
+	ret = regcomp(&r, pat, flags);
+	if (ret) {
+		if (silent)
+			return ret;
+
+		regerror(ret, &r, errbuf, sizeof(errbuf));
+		die("failed regcomp() for pattern '%s' (%s)", pat, errbuf);
+	}
+	if (!str)
+		return 0;
+
+	ret = regexec(&r, str, 1, m, 0);
+	if (ret) {
+		if (silent || ret == REG_NOMATCH)
+			return ret;
+
+		regerror(ret, &r, errbuf, sizeof(errbuf));
+		die("failed regexec() for subject '%s' (%s)", str, errbuf);
+	}
 
 	return 0;
+usage:
+	usage("\ttest-tool regex --bug\n"
+	      "\ttest-tool regex [--silent] <pattern>\n"
+	      "\ttest-tool regex [--silent] <pattern> <string> [<options>]");
+	return -1;
 }
diff --git a/third_party/git/t/helper/test-repository.c b/third_party/git/t/helper/test-repository.c
index f7f8618445..56f0e3c1be 100644
--- a/third_party/git/t/helper/test-repository.c
+++ b/third_party/git/t/helper/test-repository.c
@@ -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")) {
diff --git a/third_party/git/t/helper/test-run-command.c b/third_party/git/t/helper/test-run-command.c
index 2cc93bb69c..7ae03dc712 100644
--- a/third_party/git/t/helper/test-run-command.c
+++ b/third_party/git/t/helper/test-run-command.c
@@ -10,11 +10,16 @@
 
 #include "test-tool.h"
 #include "git-compat-util.h"
+#include "cache.h"
 #include "run-command.h"
-#include "argv-array.h"
+#include "strvec.h"
 #include "strbuf.h"
-#include <string.h>
-#include <errno.h>
+#include "parse-options.h"
+#include "string-list.h"
+#include "thread-utils.h"
+#include "wildmatch.h"
+#include "gettext.h"
+#include "parse-options.h"
 
 static int number_callbacks;
 static int parallel_next(struct child_process *cp,
@@ -26,7 +31,7 @@ static int parallel_next(struct child_process *cp,
 	if (number_callbacks >= 4)
 		return 0;
 
-	argv_array_pushv(&cp->args, d->argv);
+	strvec_pushv(&cp->args, d->argv);
 	strbuf_addstr(err, "preloaded output of a child\n");
 	number_callbacks++;
 	return 1;
@@ -50,17 +55,343 @@ static int task_finished(int result,
 	return 1;
 }
 
+struct testsuite {
+	struct string_list tests, failed;
+	int next;
+	int quiet, immediate, verbose, verbose_log, trace, write_junit_xml;
+};
+#define TESTSUITE_INIT \
+	{ STRING_LIST_INIT_DUP, STRING_LIST_INIT_DUP, -1, 0, 0, 0, 0, 0, 0 }
+
+static int next_test(struct child_process *cp, struct strbuf *err, void *cb,
+		     void **task_cb)
+{
+	struct testsuite *suite = cb;
+	const char *test;
+	if (suite->next >= suite->tests.nr)
+		return 0;
+
+	test = suite->tests.items[suite->next++].string;
+	strvec_pushl(&cp->args, "sh", test, NULL);
+	if (suite->quiet)
+		strvec_push(&cp->args, "--quiet");
+	if (suite->immediate)
+		strvec_push(&cp->args, "-i");
+	if (suite->verbose)
+		strvec_push(&cp->args, "-v");
+	if (suite->verbose_log)
+		strvec_push(&cp->args, "-V");
+	if (suite->trace)
+		strvec_push(&cp->args, "-x");
+	if (suite->write_junit_xml)
+		strvec_push(&cp->args, "--write-junit-xml");
+
+	strbuf_addf(err, "Output of '%s':\n", test);
+	*task_cb = (void *)test;
+
+	return 1;
+}
+
+static int test_finished(int result, struct strbuf *err, void *cb,
+			 void *task_cb)
+{
+	struct testsuite *suite = cb;
+	const char *name = (const char *)task_cb;
+
+	if (result)
+		string_list_append(&suite->failed, name);
+
+	strbuf_addf(err, "%s: '%s'\n", result ? "FAIL" : "SUCCESS", name);
+
+	return 0;
+}
+
+static int test_failed(struct strbuf *out, void *cb, void *task_cb)
+{
+	struct testsuite *suite = cb;
+	const char *name = (const char *)task_cb;
+
+	string_list_append(&suite->failed, name);
+	strbuf_addf(out, "FAILED TO START: '%s'\n", name);
+
+	return 0;
+}
+
+static const char * const testsuite_usage[] = {
+	"test-run-command testsuite [<options>] [<pattern>...]",
+	NULL
+};
+
+static int testsuite(int argc, const char **argv)
+{
+	struct testsuite suite = TESTSUITE_INIT;
+	int max_jobs = 1, i, ret;
+	DIR *dir;
+	struct dirent *d;
+	struct option options[] = {
+		OPT_BOOL('i', "immediate", &suite.immediate,
+			 "stop at first failed test case(s)"),
+		OPT_INTEGER('j', "jobs", &max_jobs, "run <N> jobs in parallel"),
+		OPT_BOOL('q', "quiet", &suite.quiet, "be terse"),
+		OPT_BOOL('v', "verbose", &suite.verbose, "be verbose"),
+		OPT_BOOL('V', "verbose-log", &suite.verbose_log,
+			 "be verbose, redirected to a file"),
+		OPT_BOOL('x', "trace", &suite.trace, "trace shell commands"),
+		OPT_BOOL(0, "write-junit-xml", &suite.write_junit_xml,
+			 "write JUnit-style XML files"),
+		OPT_END()
+	};
+
+	memset(&suite, 0, sizeof(suite));
+	suite.tests.strdup_strings = suite.failed.strdup_strings = 1;
+
+	argc = parse_options(argc, argv, NULL, options,
+			testsuite_usage, PARSE_OPT_STOP_AT_NON_OPTION);
+
+	if (max_jobs <= 0)
+		max_jobs = online_cpus();
+
+	dir = opendir(".");
+	if (!dir)
+		die("Could not open the current directory");
+	while ((d = readdir(dir))) {
+		const char *p = d->d_name;
+
+		if (*p != 't' || !isdigit(p[1]) || !isdigit(p[2]) ||
+		    !isdigit(p[3]) || !isdigit(p[4]) || p[5] != '-' ||
+		    !ends_with(p, ".sh"))
+			continue;
+
+		/* No pattern: match all */
+		if (!argc) {
+			string_list_append(&suite.tests, p);
+			continue;
+		}
+
+		for (i = 0; i < argc; i++)
+			if (!wildmatch(argv[i], p, 0)) {
+				string_list_append(&suite.tests, p);
+				break;
+			}
+	}
+	closedir(dir);
+
+	if (!suite.tests.nr)
+		die("No tests match!");
+	if (max_jobs > suite.tests.nr)
+		max_jobs = suite.tests.nr;
+
+	fprintf(stderr, "Running %d tests (%d at a time)\n",
+		suite.tests.nr, max_jobs);
+
+	ret = run_processes_parallel(max_jobs, next_test, test_failed,
+				     test_finished, &suite);
+
+	if (suite.failed.nr > 0) {
+		ret = 1;
+		fprintf(stderr, "%d tests failed:\n\n", suite.failed.nr);
+		for (i = 0; i < suite.failed.nr; i++)
+			fprintf(stderr, "\t%s\n", suite.failed.items[i].string);
+	}
+
+	string_list_clear(&suite.tests, 0);
+	string_list_clear(&suite.failed, 0);
+
+	return !!ret;
+}
+
+static uint64_t my_random_next = 1234;
+
+static uint64_t my_random(void)
+{
+	uint64_t res = my_random_next;
+	my_random_next = my_random_next * 1103515245 + 12345;
+	return res;
+}
+
+static int quote_stress_test(int argc, const char **argv)
+{
+	/*
+	 * We are running a quote-stress test.
+	 * spawn a subprocess that runs quote-stress with a
+	 * special option that echoes back the arguments that
+	 * were passed in.
+	 */
+	char special[] = ".?*\\^_\"'`{}()[]<>@~&+:;$%"; // \t\r\n\a";
+	int i, j, k, trials = 100, skip = 0, msys2 = 0;
+	struct strbuf out = STRBUF_INIT;
+	struct strvec args = STRVEC_INIT;
+	struct option options[] = {
+		OPT_INTEGER('n', "trials", &trials, "Number of trials"),
+		OPT_INTEGER('s', "skip", &skip, "Skip <n> trials"),
+		OPT_BOOL('m', "msys2", &msys2, "Test quoting for MSYS2's sh"),
+		OPT_END()
+	};
+	const char * const usage[] = {
+		"test-tool run-command quote-stress-test <options>",
+		NULL
+	};
+
+	argc = parse_options(argc, argv, NULL, options, usage, 0);
+
+	setenv("MSYS_NO_PATHCONV", "1", 0);
+
+	for (i = 0; i < trials; i++) {
+		struct child_process cp = CHILD_PROCESS_INIT;
+		size_t arg_count, arg_offset;
+		int ret = 0;
+
+		strvec_clear(&args);
+		if (msys2)
+			strvec_pushl(&args, "sh", "-c",
+				     "printf %s\\\\0 \"$@\"", "skip", NULL);
+		else
+			strvec_pushl(&args, "test-tool", "run-command",
+				     "quote-echo", NULL);
+		arg_offset = args.nr;
+
+		if (argc > 0) {
+			trials = 1;
+			arg_count = argc;
+			for (j = 0; j < arg_count; j++)
+				strvec_push(&args, argv[j]);
+		} else {
+			arg_count = 1 + (my_random() % 5);
+			for (j = 0; j < arg_count; j++) {
+				char buf[20];
+				size_t min_len = 1;
+				size_t arg_len = min_len +
+					(my_random() % (ARRAY_SIZE(buf) - min_len));
+
+				for (k = 0; k < arg_len; k++)
+					buf[k] = special[my_random() %
+						ARRAY_SIZE(special)];
+				buf[arg_len] = '\0';
+
+				strvec_push(&args, buf);
+			}
+		}
+
+		if (i < skip)
+			continue;
+
+		cp.argv = args.v;
+		strbuf_reset(&out);
+		if (pipe_command(&cp, NULL, 0, &out, 0, NULL, 0) < 0)
+			return error("Failed to spawn child process");
+
+		for (j = 0, k = 0; j < arg_count; j++) {
+			const char *arg = args.v[j + arg_offset];
+
+			if (strcmp(arg, out.buf + k))
+				ret = error("incorrectly quoted arg: '%s', "
+					    "echoed back as '%s'",
+					     arg, out.buf + k);
+			k += strlen(out.buf + k) + 1;
+		}
+
+		if (k != out.len)
+			ret = error("got %d bytes, but consumed only %d",
+				     (int)out.len, (int)k);
+
+		if (ret) {
+			fprintf(stderr, "Trial #%d failed. Arguments:\n", i);
+			for (j = 0; j < arg_count; j++)
+				fprintf(stderr, "arg #%d: '%s'\n",
+					(int)j, args.v[j + arg_offset]);
+
+			strbuf_release(&out);
+			strvec_clear(&args);
+
+			return ret;
+		}
+
+		if (i && (i % 100) == 0)
+			fprintf(stderr, "Trials completed: %d\n", (int)i);
+	}
+
+	strbuf_release(&out);
+	strvec_clear(&args);
+
+	return 0;
+}
+
+static int quote_echo(int argc, const char **argv)
+{
+	while (argc > 1) {
+		fwrite(argv[1], strlen(argv[1]), 1, stdout);
+		fputc('\0', stdout);
+		argv++;
+		argc--;
+	}
+
+	return 0;
+}
+
+static int inherit_handle(const char *argv0)
+{
+	struct child_process cp = CHILD_PROCESS_INIT;
+	char path[PATH_MAX];
+	int tmp;
+
+	/* First, open an inheritable handle */
+	xsnprintf(path, sizeof(path), "out-XXXXXX");
+	tmp = xmkstemp(path);
+
+	strvec_pushl(&cp.args,
+		     "test-tool", argv0, "inherited-handle-child", NULL);
+	cp.in = -1;
+	cp.no_stdout = cp.no_stderr = 1;
+	if (start_command(&cp) < 0)
+		die("Could not start child process");
+
+	/* Then close it, and try to delete it. */
+	close(tmp);
+	if (unlink(path))
+		die("Could not delete '%s'", path);
+
+	if (close(cp.in) < 0 || finish_command(&cp) < 0)
+		die("Child did not finish");
+
+	return 0;
+}
+
+static int inherit_handle_child(void)
+{
+	struct strbuf buf = STRBUF_INIT;
+
+	if (strbuf_read(&buf, 0, 0) < 0)
+		die("Could not read stdin");
+	printf("Received %s\n", buf.buf);
+	strbuf_release(&buf);
+
+	return 0;
+}
+
 int cmd__run_command(int argc, const char **argv)
 {
 	struct child_process proc = CHILD_PROCESS_INIT;
 	int jobs;
 
+	if (argc > 1 && !strcmp(argv[1], "testsuite"))
+		exit(testsuite(argc - 1, argv + 1));
+	if (!strcmp(argv[1], "inherited-handle"))
+		exit(inherit_handle(argv[0]));
+	if (!strcmp(argv[1], "inherited-handle-child"))
+		exit(inherit_handle_child());
+
+	if (argc >= 2 && !strcmp(argv[1], "quote-stress-test"))
+		return !!quote_stress_test(argc - 1, argv + 1);
+
+	if (argc >= 2 && !strcmp(argv[1], "quote-echo"))
+		return !!quote_echo(argc - 1, argv + 1);
+
 	if (argc < 3)
 		return 1;
 	while (!strcmp(argv[1], "env")) {
 		if (!argv[2])
 			die("env specifier without a value");
-		argv_array_push(&proc.env_array, argv[2]);
+		strvec_push(&proc.env_array, argv[2]);
 		argv += 2;
 		argc -= 2;
 	}
diff --git a/third_party/git/t/helper/test-submodule-nested-repo-config.c b/third_party/git/t/helper/test-submodule-nested-repo-config.c
index bc97929bbc..c5fd4527dc 100644
--- a/third_party/git/t/helper/test-submodule-nested-repo-config.c
+++ b/third_party/git/t/helper/test-submodule-nested-repo-config.c
@@ -1,7 +1,7 @@
 #include "test-tool.h"
 #include "submodule-config.h"
 
-static void die_usage(int argc, const char **argv, const char *msg)
+static void die_usage(const char **argv, const char *msg)
 {
 	fprintf(stderr, "%s\n", msg);
 	fprintf(stderr, "Usage: %s <submodulepath> <config name>\n", argv[0]);
@@ -14,13 +14,13 @@ int cmd__submodule_nested_repo_config(int argc, const char **argv)
 	const struct submodule *sub;
 
 	if (argc < 3)
-		die_usage(argc, argv, "Wrong number of arguments.");
+		die_usage(argv, "Wrong number of arguments.");
 
 	setup_git_directory();
 
 	sub = submodule_from_path(the_repository, &null_oid, argv[1]);
 	if (repo_submodule_init(&subrepo, the_repository, sub)) {
-		die_usage(argc, argv, "Submodule not found.");
+		die_usage(argv, "Submodule not found.");
 	}
 
 	/* Read the config of _child_ submodules. */
diff --git a/third_party/git/t/helper/test-svn-fe.c b/third_party/git/t/helper/test-svn-fe.c
deleted file mode 100644
index 7667c0803f..0000000000
--- a/third_party/git/t/helper/test-svn-fe.c
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * test-svn-fe: Code to exercise the svn import lib
- */
-
-#include "git-compat-util.h"
-#include "vcs-svn/svndump.h"
-#include "vcs-svn/svndiff.h"
-#include "vcs-svn/sliding_window.h"
-#include "vcs-svn/line_buffer.h"
-
-static const char test_svnfe_usage[] =
-	"test-svn-fe (<dumpfile> | [-d] <preimage> <delta> <len>)";
-
-static int apply_delta(int argc, const char **argv)
-{
-	struct line_buffer preimage = LINE_BUFFER_INIT;
-	struct line_buffer delta = LINE_BUFFER_INIT;
-	struct sliding_view preimage_view = SLIDING_VIEW_INIT(&preimage, -1);
-
-	if (argc != 5)
-		usage(test_svnfe_usage);
-
-	if (buffer_init(&preimage, argv[2]))
-		die_errno("cannot open preimage");
-	if (buffer_init(&delta, argv[3]))
-		die_errno("cannot open delta");
-	if (svndiff0_apply(&delta, (off_t) strtoumax(argv[4], NULL, 0),
-					&preimage_view, stdout))
-		return 1;
-	if (buffer_deinit(&preimage))
-		die_errno("cannot close preimage");
-	if (buffer_deinit(&delta))
-		die_errno("cannot close delta");
-	strbuf_release(&preimage_view.buf);
-	return 0;
-}
-
-int cmd_main(int argc, const char **argv)
-{
-	if (argc == 2) {
-		if (svndump_init(argv[1]))
-			return 1;
-		svndump_read(NULL, "refs/heads/master", "refs/notes/svn/revs");
-		svndump_deinit();
-		svndump_reset();
-		return 0;
-	}
-
-	if (argc >= 2 && !strcmp(argv[1], "-d"))
-		return apply_delta(argc, argv);
-	usage(test_svnfe_usage);
-}
diff --git a/third_party/git/t/helper/test-tool.c b/third_party/git/t/helper/test-tool.c
index ce7e89028c..a0d3966b29 100644
--- a/third_party/git/t/helper/test-tool.c
+++ b/third_party/git/t/helper/test-tool.c
@@ -14,6 +14,8 @@ struct test_cmd {
 };
 
 static struct test_cmd cmds[] = {
+	{ "advise", cmd__advise_if_enabled },
+	{ "bloom", cmd__bloom },
 	{ "chmtime", cmd__chmtime },
 	{ "config", cmd__config },
 	{ "ctype", cmd__ctype },
@@ -36,14 +38,19 @@ 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 },
+	{ "parse-pathspec-file", cmd__parse_pathspec_file },
 	{ "path-utils", cmd__path_utils },
 	{ "pkt-line", cmd__pkt_line },
 	{ "prio-queue", cmd__prio_queue },
+	{ "proc-receive", cmd__proc_receive},
+	{ "progress", cmd__progress },
 	{ "reach", cmd__reach },
 	{ "read-cache", cmd__read_cache },
+	{ "read-graph", cmd__read_graph },
 	{ "read-midx", cmd__read_midx },
 	{ "ref-store", cmd__ref_store },
 	{ "regex", cmd__regex },
@@ -53,7 +60,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 },
@@ -108,6 +114,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);
 		}
 	}
diff --git a/third_party/git/t/helper/test-tool.h b/third_party/git/t/helper/test-tool.h
index f805bb39ae..07034d3f38 100644
--- a/third_party/git/t/helper/test-tool.h
+++ b/third_party/git/t/helper/test-tool.h
@@ -4,6 +4,8 @@
 #define USE_THE_INDEX_COMPATIBILITY_MACROS
 #include "git-compat-util.h"
 
+int cmd__advise_if_enabled(int argc, const char **argv);
+int cmd__bloom(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);
@@ -29,11 +31,15 @@ int cmd__mktemp(int argc, const char **argv);
 int cmd__oidmap(int argc, const char **argv);
 int cmd__online_cpus(int argc, const char **argv);
 int cmd__parse_options(int argc, const char **argv);
+int cmd__parse_pathspec_file(int argc, const char** argv);
 int cmd__path_utils(int argc, const char **argv);
 int cmd__pkt_line(int argc, const char **argv);
 int cmd__prio_queue(int argc, const char **argv);
+int cmd__proc_receive(int argc, const char **argv);
+int cmd__progress(int argc, const char **argv);
 int cmd__reach(int argc, const char **argv);
 int cmd__read_cache(int argc, const char **argv);
+int cmd__read_graph(int argc, const char **argv);
 int cmd__read_midx(int argc, const char **argv);
 int cmd__ref_store(int argc, const char **argv);
 int cmd__regex(int argc, const char **argv);
@@ -43,7 +49,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);
diff --git a/third_party/git/t/helper/test-trace2.c b/third_party/git/t/helper/test-trace2.c
index 197819c872..823f33ceff 100644
--- a/third_party/git/t/helper/test-trace2.c
+++ b/third_party/git/t/helper/test-trace2.c
@@ -1,6 +1,6 @@
 #include "test-tool.h"
 #include "cache.h"
-#include "argv-array.h"
+#include "strvec.h"
 #include "run-command.h"
 #include "exec-cmd.h"
 #include "config.h"
diff --git a/third_party/git/t/helper/test-windows-named-pipe.c b/third_party/git/t/helper/test-windows-named-pipe.c
index b4b752b01a..ae52183e63 100644
--- a/third_party/git/t/helper/test-windows-named-pipe.c
+++ b/third_party/git/t/helper/test-windows-named-pipe.c
@@ -19,7 +19,7 @@ int cmd__windows_named_pipe(int argc, const char **argv)
 	if (argc < 2)
 		goto print_usage;
 	filename = argv[1];
-	if (strchr(filename, '/') || strchr(filename, '\\'))
+	if (strpbrk(filename, "/\\"))
 		goto print_usage;
 	strbuf_addf(&pathname, "//./pipe/%s", filename);
 
diff --git a/third_party/git/t/lib-bash.sh b/third_party/git/t/lib-bash.sh
index 2be955fafb..b0b6060929 100644
--- a/third_party/git/t/lib-bash.sh
+++ b/third_party/git/t/lib-bash.sh
@@ -2,10 +2,12 @@
 # to run under Bash; primarily intended for tests of the completion
 # script.
 
-if test -n "$BASH" && test -z "$POSIXLY_CORRECT"; then
+if test -n "$BASH" && test -z "$POSIXLY_CORRECT"
+then
 	# we are in full-on bash mode
 	true
-elif type bash >/dev/null 2>&1; then
+elif type bash >/dev/null 2>&1
+then
 	# execute in full-on bash mode
 	unset POSIXLY_CORRECT
 	exec bash "$0" "$@"
diff --git a/third_party/git/t/lib-credential.sh b/third_party/git/t/lib-credential.sh
index 937b831ea6..dea2cbef51 100755..100644
--- a/third_party/git/t/lib-credential.sh
+++ b/third_party/git/t/lib-credential.sh
@@ -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() {
diff --git a/third_party/git/t/lib-git-daemon.sh b/third_party/git/t/lib-git-daemon.sh
index fb8f887080..e62569222b 100644
--- a/third_party/git/t/lib-git-daemon.sh
+++ b/third_party/git/t/lib-git-daemon.sh
@@ -15,7 +15,7 @@
 #
 #	test_done
 
-if ! git env--helper --type=bool --default=true --exit-code GIT_TEST_GIT_DAEMON
+if ! test_bool_env GIT_TEST_GIT_DAEMON true
 then
 	skip_all="git-daemon testing disabled (unset GIT_TEST_GIT_DAEMON to enable)"
 	test_done
diff --git a/third_party/git/t/lib-git-p4.sh b/third_party/git/t/lib-git-p4.sh
index 547b9f88e1..5aff2abe8b 100644
--- a/third_party/git/t/lib-git-p4.sh
+++ b/third_party/git/t/lib-git-p4.sh
@@ -175,7 +175,7 @@ stop_and_cleanup_p4d () {
 
 cleanup_git () {
 	retry_until_success rm -r "$git"
-	test_must_fail test -d "$git" &&
+	test_path_is_missing "$git" &&
 	retry_until_success mkdir "$git"
 }
 
diff --git a/third_party/git/t/lib-git-svn.sh b/third_party/git/t/lib-git-svn.sh
index 5d4ae629e1..547eb3c31a 100644
--- a/third_party/git/t/lib-git-svn.sh
+++ b/third_party/git/t/lib-git-svn.sh
@@ -69,7 +69,7 @@ svn_cmd () {
 maybe_start_httpd () {
 	loc=${1-svn}
 
-	if git env--helper --type=bool --default=false --exit-code GIT_TEST_HTTPD
+	if test_bool_env GIT_TEST_SVN_HTTPD false
 	then
 		. "$TEST_DIRECTORY"/lib-httpd.sh
 		LIB_HTTPD_SVN="$loc"
@@ -78,21 +78,24 @@ maybe_start_httpd () {
 }
 
 convert_to_rev_db () {
-	perl -w -- - "$@" <<\EOF
+	perl -w -- - "$(test_oid rawsz)" "$@" <<\EOF
 use strict;
+my $oidlen = shift;
 @ARGV == 2 or die "usage: convert_to_rev_db <input> <output>";
+my $record_size = $oidlen + 4;
+my $hexlen = $oidlen * 2;
 open my $wr, '+>', $ARGV[1] or die "$!: couldn't open: $ARGV[1]";
 open my $rd, '<', $ARGV[0] or die "$!: couldn't open: $ARGV[0]";
 my $size = (stat($rd))[7];
-($size % 24) == 0 or die "Inconsistent size: $size";
-while (sysread($rd, my $buf, 24) == 24) {
-	my ($r, $c) = unpack('NH40', $buf);
-	my $offset = $r * 41;
+($size % $record_size) == 0 or die "Inconsistent size: $size";
+while (sysread($rd, my $buf, $record_size) == $record_size) {
+	my ($r, $c) = unpack("NH$hexlen", $buf);
+	my $offset = $r * ($hexlen + 1);
 	seek $wr, 0, 2 or die $!;
 	my $pos = tell $wr;
 	if ($pos < $offset) {
-		for (1 .. (($offset - $pos) / 41)) {
-			print $wr (('0' x 40),"\n") or die $!;
+		for (1 .. (($offset - $pos) / ($hexlen + 1))) {
+			print $wr (('0' x $hexlen),"\n") or die $!;
 		}
 	}
 	seek $wr, $offset, 0 or die $!;
@@ -104,7 +107,7 @@ EOF
 }
 
 require_svnserve () {
-	if ! git env--helper --type=bool --default=false --exit-code GIT_TEST_SVNSERVE
+	if ! test_bool_env GIT_TEST_SVNSERVE false
 	then
 		skip_all='skipping svnserve test. (set $GIT_TEST_SVNSERVE to enable)'
 		test_done
diff --git a/third_party/git/t/lib-gpg.sh b/third_party/git/t/lib-gpg.sh
index 8d28652b72..9fc5241228 100755..100644
--- a/third_party/git/t/lib-gpg.sh
+++ b/third_party/git/t/lib-gpg.sh
@@ -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 '
diff --git a/third_party/git/t/lib-httpd.sh b/third_party/git/t/lib-httpd.sh
index 0d985758c6..d2edfa4c50 100644
--- a/third_party/git/t/lib-httpd.sh
+++ b/third_party/git/t/lib-httpd.sh
@@ -41,7 +41,7 @@ then
 	test_done
 fi
 
-if ! git env--helper --type=bool --default=true --exit-code GIT_TEST_HTTPD
+if ! test_bool_env GIT_TEST_HTTPD true
 then
 	skip_all="Network testing disabled (unset GIT_TEST_HTTPD to enable)"
 	test_done
@@ -129,10 +129,12 @@ install_script () {
 prepare_httpd() {
 	mkdir -p "$HTTPD_DOCUMENT_ROOT_PATH"
 	cp "$TEST_PATH"/passwd "$HTTPD_ROOT_PATH"
+	install_script incomplete-length-upload-pack-v2-http.sh
+	install_script incomplete-body-upload-pack-v2-http.sh
 	install_script broken-smart-http.sh
 	install_script error-smart-http.sh
 	install_script error.sh
-	install_script apply-one-time-sed.sh
+	install_script apply-one-time-perl.sh
 
 	ln -s "$LIB_HTTPD_MODULE_PATH" "$HTTPD_ROOT_PATH/modules"
 
diff --git a/third_party/git/t/lib-httpd/apache.conf b/third_party/git/t/lib-httpd/apache.conf
index 5c1c86c193..afa91e38b0 100644
--- a/third_party/git/t/lib-httpd/apache.conf
+++ b/third_party/git/t/lib-httpd/apache.conf
@@ -113,19 +113,27 @@ Alias /auth/dumb/ www/auth/dumb/
 	SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH}
 	SetEnv GIT_HTTP_EXPORT_ALL
 </LocationMatch>
-<LocationMatch /one_time_sed/>
+<LocationMatch /one_time_perl/>
 	SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH}
 	SetEnv GIT_HTTP_EXPORT_ALL
 </LocationMatch>
+ScriptAlias /smart/incomplete_length/git-upload-pack incomplete-length-upload-pack-v2-http.sh/
+ScriptAlias /smart/incomplete_body/git-upload-pack incomplete-body-upload-pack-v2-http.sh/
 ScriptAliasMatch /error_git_upload_pack/(.*)/git-upload-pack error.sh/
 ScriptAliasMatch /smart_*[^/]*/(.*) ${GIT_EXEC_PATH}/git-http-backend/$1
 ScriptAlias /broken_smart/ broken-smart-http.sh/
 ScriptAlias /error_smart/ error-smart-http.sh/
 ScriptAlias /error/ error.sh/
-ScriptAliasMatch /one_time_sed/(.*) apply-one-time-sed.sh/$1
+ScriptAliasMatch /one_time_perl/(.*) apply-one-time-perl.sh/$1
 <Directory ${GIT_EXEC_PATH}>
 	Options FollowSymlinks
 </Directory>
+<Files incomplete-length-upload-pack-v2-http.sh>
+	Options ExecCGI
+</Files>
+<Files incomplete-body-upload-pack-v2-http.sh>
+	Options ExecCGI
+</Files>
 <Files broken-smart-http.sh>
 	Options ExecCGI
 </Files>
@@ -135,7 +143,7 @@ ScriptAliasMatch /one_time_sed/(.*) apply-one-time-sed.sh/$1
 <Files error.sh>
   Options ExecCGI
 </Files>
-<Files apply-one-time-sed.sh>
+<Files apply-one-time-perl.sh>
 	Options ExecCGI
 </Files>
 <Files ${GIT_EXEC_PATH}/git-http-backend>
diff --git a/third_party/git/t/lib-httpd/apply-one-time-perl.sh b/third_party/git/t/lib-httpd/apply-one-time-perl.sh
new file mode 100644
index 0000000000..09a0abdff7
--- /dev/null
+++ b/third_party/git/t/lib-httpd/apply-one-time-perl.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+# If "one-time-perl" exists in $HTTPD_ROOT_PATH, run perl on the HTTP response,
+# using the contents of "one-time-perl" as the perl command to be run. If the
+# response was modified as a result, delete "one-time-perl" so that subsequent
+# HTTP responses are no longer modified.
+#
+# This can be used to simulate the effects of the repository changing in
+# between HTTP request-response pairs.
+if test -f one-time-perl
+then
+	LC_ALL=C
+	export LC_ALL
+
+	"$GIT_EXEC_PATH/git-http-backend" >out
+	perl -pe "$(cat one-time-perl)" out >out_modified
+
+	if cmp -s out out_modified
+	then
+		cat out
+	else
+		cat out_modified
+		rm one-time-perl
+	fi
+else
+	"$GIT_EXEC_PATH/git-http-backend"
+fi
diff --git a/third_party/git/t/lib-httpd/apply-one-time-sed.sh b/third_party/git/t/lib-httpd/apply-one-time-sed.sh
deleted file mode 100644
index fcef728925..0000000000
--- a/third_party/git/t/lib-httpd/apply-one-time-sed.sh
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/bin/sh
-
-# If "one-time-sed" exists in $HTTPD_ROOT_PATH, run sed on the HTTP response,
-# using the contents of "one-time-sed" as the sed command to be run. If the
-# response was modified as a result, delete "one-time-sed" so that subsequent
-# HTTP responses are no longer modified.
-#
-# This can be used to simulate the effects of the repository changing in
-# between HTTP request-response pairs.
-if [ -e one-time-sed ]; then
-	"$GIT_EXEC_PATH/git-http-backend" >out
-	sed "$(cat one-time-sed)" <out >out_modified
-
-	if diff out out_modified >/dev/null; then
-		cat out
-	else
-		cat out_modified
-		rm one-time-sed
-	fi
-else
-	"$GIT_EXEC_PATH/git-http-backend"
-fi
diff --git a/third_party/git/t/lib-httpd/incomplete-body-upload-pack-v2-http.sh b/third_party/git/t/lib-httpd/incomplete-body-upload-pack-v2-http.sh
new file mode 100644
index 0000000000..90e73ef8d5
--- /dev/null
+++ b/third_party/git/t/lib-httpd/incomplete-body-upload-pack-v2-http.sh
@@ -0,0 +1,3 @@
+printf "Content-Type: text/%s\n" "application/x-git-upload-pack-result"
+echo
+printf "%s%s" "0079" "45"
diff --git a/third_party/git/t/lib-httpd/incomplete-length-upload-pack-v2-http.sh b/third_party/git/t/lib-httpd/incomplete-length-upload-pack-v2-http.sh
new file mode 100644
index 0000000000..dce552e348
--- /dev/null
+++ b/third_party/git/t/lib-httpd/incomplete-length-upload-pack-v2-http.sh
@@ -0,0 +1,3 @@
+printf "Content-Type: text/%s\n" "application/x-git-upload-pack-result"
+echo
+printf "%s" "00"
diff --git a/third_party/git/t/lib-log-graph.sh b/third_party/git/t/lib-log-graph.sh
new file mode 100644
index 0000000000..1184cceef2
--- /dev/null
+++ b/third_party/git/t/lib-log-graph.sh
@@ -0,0 +1,28 @@
+# Helps shared by the test scripts for comparing log graphs.
+
+sanitize_log_output () {
+	sed -e 's/ *$//' \
+	    -e 's/commit [0-9a-f]*$/commit COMMIT_OBJECT_NAME/' \
+	    -e 's/Merge: [ 0-9a-f]*$/Merge: MERGE_PARENTS/' \
+	    -e 's/Merge tag.*/Merge HEADS DESCRIPTION/' \
+	    -e 's/Merge commit.*/Merge HEADS DESCRIPTION/' \
+	    -e 's/index [0-9a-f]*\.\.[0-9a-f]*/index BEFORE..AFTER/'
+}
+
+lib_test_cmp_graph () {
+	git log --graph "$@" >output &&
+	sed 's/ *$//' >output.sanitized <output &&
+	test_i18ncmp expect output.sanitized
+}
+
+lib_test_cmp_short_graph () {
+	git log --graph --pretty=short "$@" >output &&
+	sanitize_log_output >output.sanitized <output &&
+	test_i18ncmp expect output.sanitized
+}
+
+lib_test_cmp_colored_graph () {
+	git log --graph --color=always "$@" >output.colors.raw &&
+	test_decode_color <output.colors.raw | sed "s/ *\$//" >output.colors &&
+	test_cmp expect.colors output.colors
+}
diff --git a/third_party/git/t/lib-pack.sh b/third_party/git/t/lib-pack.sh
index c4d907a450..bb8938ccbe 100644
--- a/third_party/git/t/lib-pack.sh
+++ b/third_party/git/t/lib-pack.sh
@@ -37,7 +37,7 @@ pack_header () {
 pack_obj () {
 	case "$1" in
 	# empty blob
-	e69de29bb2d1d6434b8b29ae775ad8c2e48c5391)
+	$EMPTY_BLOB)
 		case "$2" in
 		'')
 			printf '\060\170\234\003\0\0\0\0\1'
@@ -47,7 +47,7 @@ pack_obj () {
 		;;
 
 	# blob containing "\7\76"
-	e68fe8129b546b101aee9510c5328e7f21ca1d18)
+	$(test_oid packlib_7_76))
 		case "$2" in
 		'')
 			printf '\062\170\234\143\267\3\0\0\116\0\106'
@@ -59,11 +59,18 @@ pack_obj () {
 			printf '\234\143\142\142\142\267\003\0\0\151\0\114'
 			return
 			;;
+		37c8e2c15bb22b912e59b43fd51a4f7e9465ed0b5084c5a1411d991cbe630683)
+			printf '\165\67\310\342\301\133\262\53\221\56\131' &&
+			printf '\264\77\325\32\117\176\224\145\355\13\120' &&
+			printf '\204\305\241\101\35\231\34\276\143\6\203\170' &&
+			printf '\234\143\142\142\142\267\003\0\0\151\0\114'
+			return
+			;;
 		esac
 		;;
 
 	# blob containing "\7\0"
-	01d7713666f4de822776c7622c10f1b07de280dc)
+	$(test_oid packlib_7_0))
 		case "$2" in
 		'')
 			printf '\062\170\234\143\147\0\0\0\20\0\10'
@@ -75,8 +82,23 @@ pack_obj () {
 			printf '\143\142\142\142\147\0\0\0\53\0\16'
 			return
 			;;
+		5d8e6fc40f2dab00e6983a48523fe57e621f46434cb58dbd4422fba03380d886)
+			printf '\165\135\216\157\304\17\55\253\0\346\230\72' &&
+			printf '\110\122\77\345\176\142\37\106\103\114\265' &&
+			printf '\215\275\104\42\373\240\63\200\330\206\170\234' &&
+			printf '\143\142\142\142\147\0\0\0\53\0\16'
+			return
+			;;
 		esac
 		;;
+	# blob containing "\3\326"
+	471819e8c52bf11513f100b2810a8aa0622d5cd3d1c913758a071dd4b3bad8fe)
+		case "$2" in
+		'')
+			printf '\062\170\234\143\276\006\000\000\336\000\332'
+			return
+			;;
+		esac
 	esac
 
 	# If it's not a delta, we can convince pack-objects to generate a pack
@@ -86,7 +108,7 @@ pack_obj () {
 	then
 		echo "$1" | git pack-objects --stdout >pack_obj.tmp &&
 		size=$(wc -c <pack_obj.tmp) &&
-		dd if=pack_obj.tmp bs=1 count=$((size - 20 - 12)) skip=12 &&
+		dd if=pack_obj.tmp bs=1 count=$((size - $(test_oid rawsz) - 12)) skip=12 &&
 		rm -f pack_obj.tmp
 		return
 	fi
@@ -97,7 +119,7 @@ pack_obj () {
 
 # Compute and append pack trailer to "$1"
 pack_trailer () {
-	test-tool sha1 -b <"$1" >trailer.tmp &&
+	test-tool $(test_oid algo) -b <"$1" >trailer.tmp &&
 	cat trailer.tmp >>"$1" &&
 	rm -f trailer.tmp
 }
@@ -108,3 +130,11 @@ pack_trailer () {
 clear_packs () {
 	rm -f .git/objects/pack/*
 }
+
+test_oid_cache <<-EOF
+packlib_7_0 sha1:01d7713666f4de822776c7622c10f1b07de280dc
+packlib_7_0 sha256:37c8e2c15bb22b912e59b43fd51a4f7e9465ed0b5084c5a1411d991cbe630683
+
+packlib_7_76 sha1:e68fe8129b546b101aee9510c5328e7f21ca1d18
+packlib_7_76 sha256:5d8e6fc40f2dab00e6983a48523fe57e621f46434cb58dbd4422fba03380d886
+EOF
diff --git a/third_party/git/t/lib-rebase.sh b/third_party/git/t/lib-rebase.sh
index 7ea30e5006..b72c051f47 100644
--- a/third_party/git/t/lib-rebase.sh
+++ b/third_party/git/t/lib-rebase.sh
@@ -44,10 +44,10 @@ set_fake_editor () {
 	rm -f "$1"
 	echo 'rebase -i script before editing:'
 	cat "$1".tmp
-	action=pick
+	action=\&
 	for line in $FAKE_LINES; do
 		case $line in
-		pick|p|squash|s|fixup|f|edit|e|reword|r|drop|d)
+		pick|p|squash|s|fixup|f|edit|e|reword|r|drop|d|label|l|reset|r|merge|m)
 			action="$line";;
 		exec_*|x_*|break|b)
 			echo "$line" | sed 's/_/ /g' >> "$1";;
@@ -58,11 +58,12 @@ set_fake_editor () {
 		bad)
 			action="badcmd";;
 		fakesha)
+			test \& != "$action" || action=pick
 			echo "$action XXXXXXX False commit" >> "$1"
 			action=pick;;
 		*)
-			sed -n "${line}s/^pick/$action/p" < "$1".tmp >> "$1"
-			action=pick;;
+			sed -n "${line}s/^[a-z][a-z]*/$action/p" < "$1".tmp >> "$1"
+			action=\&;;
 		esac
 	done
 	echo 'rebase -i script after editing:'
@@ -118,3 +119,31 @@ make_empty () {
 	git commit --allow-empty -m "$1" &&
 	git tag "$1"
 }
+
+# Call this (inside test_expect_success) at the end of a test file to
+# check that no tests have changed editor related environment
+# variables or config settings
+test_editor_unchanged () {
+	# We're only interested in exported variables hence 'sh -c'
+	sh -c 'cat >actual <<-EOF
+	EDITOR=$EDITOR
+	FAKE_COMMIT_AMEND=$FAKE_COMMIT_AMEND
+	FAKE_COMMIT_MESSAGE=$FAKE_COMMIT_MESSAGE
+	FAKE_LINES=$FAKE_LINES
+	GIT_EDITOR=$GIT_EDITOR
+	GIT_SEQUENCE_EDITOR=$GIT_SEQUENCE_EDITOR
+	core.editor=$(git config core.editor)
+	sequence.editor=$(git config sequence.editor)
+	EOF'
+	cat >expect <<-\EOF
+	EDITOR=:
+	FAKE_COMMIT_AMEND=
+	FAKE_COMMIT_MESSAGE=
+	FAKE_LINES=
+	GIT_EDITOR=
+	GIT_SEQUENCE_EDITOR=
+	core.editor=
+	sequence.editor=
+	EOF
+	test_cmp expect actual
+}
diff --git a/third_party/git/t/lib-submodule-update.sh b/third_party/git/t/lib-submodule-update.sh
index 1dd17fc03e..87a759149f 100755..100644
--- a/third_party/git/t/lib-submodule-update.sh
+++ b/third_party/git/t/lib-submodule-update.sh
@@ -183,7 +183,7 @@ test_git_directory_is_unchanged () {
 	)
 }
 
-test_git_directory_exists() {
+test_git_directory_exists () {
 	test -e ".git/modules/$1" &&
 	if test -f sub1/.git
 	then
@@ -196,7 +196,6 @@ test_git_directory_exists() {
 # the submodule repo if it doesn't exist and configures the most problematic
 # settings for diff.ignoreSubmodules.
 prolog () {
-	test_oid_init &&
 	(test -d submodule_update_repo || create_lib_submodule_repo) &&
 	test_config_global diff.ignoreSubmodules all &&
 	test_config diff.ignoreSubmodules all
@@ -297,19 +296,23 @@ 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
 # a removed submodule.
 #
+# The first argument of the callback function will be the name of the submodule.
+#
 # Removing a submodule containing a .git directory must fail even when forced
-# to protect the history!
+# to protect the history! If we are testing this case, the second argument of
+# the callback function will be 'test_must_fail', else it will be the empty
+# string.
 #
 
-# Internal function; use test_submodule_switch() or
-# test_submodule_forced_switch() instead.
-test_submodule_switch_common() {
+# Internal function; use test_submodule_switch_func(), test_submodule_switch(),
+# or test_submodule_forced_switch() instead.
+test_submodule_switch_common () {
 	command="$1"
 	######################### Appearing submodule #########################
 	# Switching to a commit letting a submodule appear creates empty dir ...
@@ -443,7 +446,7 @@ test_submodule_switch_common() {
 		(
 			cd submodule_update &&
 			git branch -t replace_sub1_with_directory origin/replace_sub1_with_directory &&
-			test_must_fail $command replace_sub1_with_directory &&
+			$command replace_sub1_with_directory test_must_fail &&
 			test_superproject_content origin/add_sub1 &&
 			test_submodule_content sub1 origin/add_sub1
 		)
@@ -456,7 +459,7 @@ test_submodule_switch_common() {
 			cd submodule_update &&
 			git branch -t replace_sub1_with_directory origin/replace_sub1_with_directory &&
 			replace_gitfile_with_git_dir sub1 &&
-			test_must_fail $command replace_sub1_with_directory &&
+			$command replace_sub1_with_directory test_must_fail &&
 			test_superproject_content origin/add_sub1 &&
 			test_git_directory_is_unchanged sub1 &&
 			test_submodule_content sub1 origin/add_sub1
@@ -470,7 +473,7 @@ test_submodule_switch_common() {
 		(
 			cd submodule_update &&
 			git branch -t replace_sub1_with_file origin/replace_sub1_with_file &&
-			test_must_fail $command replace_sub1_with_file &&
+			$command replace_sub1_with_file test_must_fail &&
 			test_superproject_content origin/add_sub1 &&
 			test_submodule_content sub1 origin/add_sub1
 		)
@@ -484,7 +487,7 @@ test_submodule_switch_common() {
 			cd submodule_update &&
 			git branch -t replace_sub1_with_file origin/replace_sub1_with_file &&
 			replace_gitfile_with_git_dir sub1 &&
-			test_must_fail $command replace_sub1_with_file &&
+			$command replace_sub1_with_file test_must_fail &&
 			test_superproject_content origin/add_sub1 &&
 			test_git_directory_is_unchanged sub1 &&
 			test_submodule_content sub1 origin/add_sub1
@@ -559,15 +562,28 @@ test_submodule_switch_common() {
 # conditions, set the appropriate KNOWN_FAILURE_* variable used in the tests
 # below to 1.
 #
-# Use as follows:
+# The first argument of the callback function will be the name of the submodule.
+#
+# Removing a submodule containing a .git directory must fail even when forced
+# to protect the history! If we are testing this case, the second argument of
+# the callback function will be 'test_must_fail', else it will be the empty
+# string.
+#
+# The following example uses `git some-command` as an example command to be
+# tested. It updates the worktree and index to match a target, but not any
+# submodule directories.
 #
 # my_func () {
-#   target=$1
-#   # Do something here that updates the worktree and index to match target,
-#   # but not any submodule directories.
+#   ...prepare for `git some-command` to be run...
+#   $2 git some-command "$1" &&
+#   if test -n "$2"
+#   then
+#     return
+#   fi &&
+#   ...check the state after git some-command is run...
 # }
-# test_submodule_switch "my_func"
-test_submodule_switch () {
+# test_submodule_switch_func "my_func"
+test_submodule_switch_func () {
 	command="$1"
 	test_submodule_switch_common "$command"
 
@@ -580,17 +596,33 @@ test_submodule_switch () {
 			cd submodule_update &&
 			git branch -t add_sub1 origin/add_sub1 &&
 			>sub1 &&
-			test_must_fail $command add_sub1 &&
+			$command add_sub1 test_must_fail &&
 			test_superproject_content origin/no_submodule &&
 			test_must_be_empty sub1
 		)
 	'
 }
 
+# Ensures that the that the arg either contains "test_must_fail" or is empty.
+may_only_be_test_must_fail () {
+	test -z "$1" || test "$1" = test_must_fail || die
+}
+
+git_test_func () {
+	may_only_be_test_must_fail "$2" &&
+	$2 git $gitcmd "$1"
+}
+
+test_submodule_switch () {
+	gitcmd="$1"
+	test_submodule_switch_func "git_test_func"
+}
+
 # Same as test_submodule_switch(), except that throwing away local changes in
 # the superproject is allowed.
 test_submodule_forced_switch () {
-	command="$1"
+	gitcmd="$1"
+	command="git_test_func"
 	KNOWN_FAILURE_FORCED_SWITCH_TESTS=1
 	test_submodule_switch_common "$command"
 
@@ -621,16 +653,18 @@ 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.
-test_submodule_recursing_with_args_common() {
-	command="$1"
+test_submodule_recursing_with_args_common () {
+	command="$1 --recurse-submodules"
 
 	######################### Appearing submodule #########################
 	# Switching to a commit letting a submodule appear checks it out ...
@@ -658,22 +692,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 +717,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 +793,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 +835,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
@@ -809,7 +872,7 @@ test_submodule_recursing_with_args_common() {
 # test_submodule_switch_recursing_with_args "$GIT_COMMAND"
 test_submodule_switch_recursing_with_args () {
 	cmd_args="$1"
-	command="git $cmd_args --recurse-submodules"
+	command="git $cmd_args"
 	test_submodule_recursing_with_args_common "$command"
 
 	RESULTDS=success
@@ -908,7 +971,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 &&
@@ -927,7 +989,7 @@ test_submodule_switch_recursing_with_args () {
 # away local changes in the superproject is allowed.
 test_submodule_forced_switch_recursing_with_args () {
 	cmd_args="$1"
-	command="git $cmd_args --recurse-submodules"
+	command="git $cmd_args"
 	test_submodule_recursing_with_args_common "$command"
 
 	RESULT=success
diff --git a/third_party/git/t/lib-t6000.sh b/third_party/git/t/lib-t6000.sh
index b0ed4767e3..fba6778ca3 100644
--- a/third_party/git/t/lib-t6000.sh
+++ b/third_party/git/t/lib-t6000.sh
@@ -1,7 +1,5 @@
 : included from 6002 and others
 
-mkdir -p .git/refs/tags
-
 >sed.script
 
 # Answer the sha1 has associated with the tag. The tag must exist under refs/tags
@@ -26,7 +24,8 @@ save_tag () {
 	_tag=$1
 	test -n "$_tag" || error "usage: save_tag tag commit-args ..."
 	shift 1
-	"$@" >".git/refs/tags/$_tag"
+
+	git update-ref "refs/tags/$_tag" $("$@")
 
 	echo "s/$(tag $_tag)/$_tag/g" >sed.script.tmp
 	cat sed.script >>sed.script.tmp
diff --git a/third_party/git/t/oid-info/hash-info b/third_party/git/t/oid-info/hash-info
index ccdbfdf974..d0736dd1a0 100644
--- a/third_party/git/t/oid-info/hash-info
+++ b/third_party/git/t/oid-info/hash-info
@@ -6,3 +6,12 @@ hexsz sha256:64
 
 zero sha1:0000000000000000000000000000000000000000
 zero sha256:0000000000000000000000000000000000000000000000000000000000000000
+
+algo sha1:sha1
+algo sha256:sha256
+
+empty_blob sha1:e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
+empty_blob sha256:473a0f4c3be8a93681a267e3b1e9a7dcda1185436fe141f7749120a303721813
+
+empty_tree sha1:4b825dc642cb6eb9a060e54bf8d69288fbee4904
+empty_tree sha256:6ef19b41225c5369f1c104d45d8d85efa9b057b53b14b4b9b939dd74decc5321
diff --git a/third_party/git/t/perf/README b/third_party/git/t/perf/README
index c7b70e2d28..bd649afa97 100644
--- a/third_party/git/t/perf/README
+++ b/third_party/git/t/perf/README
@@ -84,6 +84,15 @@ You can set the following variables (also in your config.mak):
 	probably be about linux.git size for optimal results.
 	Both default to the git.git you are running from.
 
+    GIT_PERF_EXTRA
+	Boolean to enable additional tests. Most test scripts are
+	written to detect regressions between two versions of Git, and
+	the output will compare timings for individual tests between
+	those versions. Some scripts have additional tests which are not
+	run by default, that show patterns within a single version of
+	Git (e.g., performance of index-pack as the number of threads
+	changes). These can be enabled with GIT_PERF_EXTRA.
+
 You can also pass the options taken by ordinary git tests; the most
 useful one is:
 
diff --git a/third_party/git/t/perf/aggregate.perl b/third_party/git/t/perf/aggregate.perl
index 66554d2161..14e4cda287 100755
--- a/third_party/git/t/perf/aggregate.perl
+++ b/third_party/git/t/perf/aggregate.perl
@@ -4,7 +4,6 @@ use lib '../../perl/build/lib';
 use strict;
 use warnings;
 use Getopt::Long;
-use Git;
 use Cwd qw(realpath);
 
 sub get_times {
@@ -85,6 +84,11 @@ sub format_size {
 	return $out;
 }
 
+sub sane_backticks {
+	open(my $fh, '-|', @_);
+	return <$fh>;
+}
+
 my (@dirs, %dirnames, %dirabbrevs, %prefixes, @tests,
     $codespeed, $sortby, $subsection, $reponame);
 
@@ -102,7 +106,8 @@ while (scalar @ARGV) {
 	my $prefix = '';
 	last if -f $arg or $arg eq "--";
 	if (! -d $arg) {
-		my $rev = Git::command_oneline(qw(rev-parse --verify), $arg);
+		my $rev = sane_backticks(qw(git rev-parse --verify), $arg);
+		chomp $rev;
 		$dir = "build/".$rev;
 	} elsif ($arg eq '.') {
 		$dir = '.';
@@ -219,13 +224,7 @@ sub print_default_results {
 		for my $i (0..$#dirs) {
 			my $d = $dirs[$i];
 			my $base = "$resultsdir/$prefixes{$d}$t";
-			$times{$prefixes{$d}.$t} = [];
-			foreach my $type (qw(times size)) {
-				if (-e "$base.$type") {
-					$times{$prefixes{$d}.$t} = [get_times("$base.$type")];
-					last;
-				}
-			}
+			$times{$prefixes{$d}.$t} = [get_times("$base.result")];
 			my ($r,$u,$s) = @{$times{$prefixes{$d}.$t}};
 			my $w = length format_times($r,$u,$s,$firstr);
 			$colwidth[$i] = $w if $w > $colwidth[$i];
@@ -267,7 +266,7 @@ sub print_sorted_results {
 		my ($prevr, $prevu, $prevs, $prevrev);
 		for my $i (0..$#dirs) {
 			my $d = $dirs[$i];
-			my ($r, $u, $s) = get_times("$resultsdir/$prefixes{$d}$t.times");
+			my ($r, $u, $s) = get_times("$resultsdir/$prefixes{$d}$t.result");
 			if ($i > 0 and defined $r and defined $prevr and $prevr > 0) {
 				my $percent = 100.0 * ($r - $prevr) / $prevr;
 				push @evolutions, { "percent"  => $percent,
@@ -327,7 +326,7 @@ sub print_codespeed_results {
 			my $commitid = $prefixes{$d};
 			$commitid =~ s/^build_//;
 			$commitid =~ s/\.$//;
-			my ($result_value, $u, $s) = get_times("$resultsdir/$prefixes{$d}$t.times");
+			my ($result_value, $u, $s) = get_times("$resultsdir/$prefixes{$d}$t.result");
 
 			my %vals = (
 				"commitid" => $commitid,
diff --git a/third_party/git/t/perf/bisect_regression b/third_party/git/t/perf/bisect_regression
index a94d9955d0..ce47e1662a 100755
--- a/third_party/git/t/perf/bisect_regression
+++ b/third_party/git/t/perf/bisect_regression
@@ -51,7 +51,7 @@ oldtime=$(echo "$oldtime" | sed -e 's/^\([0-9]\+\.[0-9]\+\).*$/\1/')
 newtime=$(echo "$newtime" | sed -e 's/^\([0-9]\+\.[0-9]\+\).*$/\1/')
 
 test $(echo "$newtime" "$oldtime" | awk '{ print ($1 > $2) }') = 1 ||
-	die "New time '$newtime' shoud be greater than old time '$oldtime'"
+	die "New time '$newtime' should be greater than old time '$oldtime'"
 
 tmpdir=$(mktemp -d -t bisect_regression_XXXXXX) || die "Failed to create temp directory"
 echo "$oldtime" >"$tmpdir/oldtime" || die "Failed to write to '$tmpdir/oldtime'"
diff --git a/third_party/git/t/perf/p1400-update-ref.sh b/third_party/git/t/perf/p1400-update-ref.sh
new file mode 100755
index 0000000000..ce5ac3ed85
--- /dev/null
+++ b/third_party/git/t/perf/p1400-update-ref.sh
@@ -0,0 +1,39 @@
+#!/bin/sh
+
+test_description="Tests performance of update-ref"
+
+. ./perf-lib.sh
+
+test_perf_fresh_repo
+
+test_expect_success "setup" '
+	git init --bare target-repo.git &&
+	test_commit PRE &&
+	test_commit POST &&
+	printf "create refs/heads/%d PRE\n" $(test_seq 1000) >create &&
+	printf "update refs/heads/%d POST PRE\n" $(test_seq 1000) >update &&
+	printf "delete refs/heads/%d POST\n" $(test_seq 1000) >delete &&
+	git update-ref --stdin <create
+'
+
+test_perf "update-ref" '
+	for i in $(test_seq 1000)
+	do
+		git update-ref refs/heads/branch PRE &&
+		git update-ref refs/heads/branch POST PRE &&
+		git update-ref -d refs/heads/branch
+	done
+'
+
+test_perf "update-ref --stdin" '
+	git update-ref --stdin <update &&
+	git update-ref --stdin <delete &&
+	git update-ref --stdin <create
+'
+
+test_perf "nonatomic push" '
+	git push ./target-repo.git $(test_seq 1000) &&
+	git push --delete ./target-repo.git $(test_seq 1000)
+'
+
+test_done
diff --git a/third_party/git/t/perf/p5302-pack-index.sh b/third_party/git/t/perf/p5302-pack-index.sh
index a9b3e112d9..228593d9ad 100755
--- a/third_party/git/t/perf/p5302-pack-index.sh
+++ b/third_party/git/t/perf/p5302-pack-index.sh
@@ -13,35 +13,36 @@ test_expect_success 'repack' '
 	export PACK
 '
 
-test_perf 'index-pack 0 threads' '
-	rm -rf repo.git &&
-	git init --bare repo.git &&
-	GIT_DIR=repo.git git index-pack --threads=1 --stdin < $PACK
-'
-
-test_perf 'index-pack 1 thread ' '
-	rm -rf repo.git &&
-	git init --bare repo.git &&
-	GIT_DIR=repo.git GIT_FORCE_THREADS=1 git index-pack --threads=1 --stdin < $PACK
+# Rather than counting up and doubling each time, count down from the endpoint,
+# halving each time. That ensures that our final test uses as many threads as
+# CPUs, even if it isn't a power of 2.
+test_expect_success 'set up thread-counting tests' '
+	t=$(test-tool online-cpus) &&
+	threads= &&
+	while test $t -gt 0
+	do
+		threads="$t $threads"
+		t=$((t / 2))
+	done
 '
 
-test_perf 'index-pack 2 threads' '
+test_perf PERF_EXTRA 'index-pack 0 threads' '
 	rm -rf repo.git &&
 	git init --bare repo.git &&
-	GIT_DIR=repo.git git index-pack --threads=2 --stdin < $PACK
-'
-
-test_perf 'index-pack 4 threads' '
-	rm -rf repo.git &&
-	git init --bare repo.git &&
-	GIT_DIR=repo.git git index-pack --threads=4 --stdin < $PACK
+	GIT_DIR=repo.git git index-pack --threads=1 --stdin < $PACK
 '
 
-test_perf 'index-pack 8 threads' '
-	rm -rf repo.git &&
-	git init --bare repo.git &&
-	GIT_DIR=repo.git git index-pack --threads=8 --stdin < $PACK
-'
+for t in $threads
+do
+	THREADS=$t
+	export THREADS
+	test_perf PERF_EXTRA "index-pack $t threads" '
+		rm -rf repo.git &&
+		git init --bare repo.git &&
+		GIT_DIR=repo.git GIT_FORCE_THREADS=1 \
+		git index-pack --threads=$THREADS --stdin <$PACK
+	'
+done
 
 test_perf 'index-pack default number of threads' '
 	rm -rf repo.git &&
diff --git a/third_party/git/t/perf/p5303-many-packs.sh b/third_party/git/t/perf/p5303-many-packs.sh
index 3779851941..f4c2ab0584 100755
--- a/third_party/git/t/perf/p5303-many-packs.sh
+++ b/third_party/git/t/perf/p5303-many-packs.sh
@@ -73,10 +73,15 @@ do
 		git rev-list --objects --all >/dev/null
 	'
 
+	test_perf "abbrev-commit ($nr_packs)" '
+		git rev-list --abbrev-commit HEAD >/dev/null
+	'
+
 	# This simulates the interesting part of the repack, which is the
 	# actual pack generation, without smudging the on-disk setup
 	# between trials.
 	test_perf "repack ($nr_packs)" '
+		GIT_TEST_FULL_IN_PACK_ARRAY=1 \
 		git pack-objects --keep-true-parents \
 		  --honor-pack-keep --non-empty --all \
 		  --reflog --indexed-objects --delta-base-offset \
@@ -84,4 +89,22 @@ do
 	'
 done
 
+# Measure pack loading with 10,000 packs.
+test_expect_success 'generate lots of packs' '
+	for i in $(test_seq 10000); do
+		echo "blob"
+		echo "data <<EOF"
+		echo "blob $i"
+		echo "EOF"
+		echo "checkpoint"
+	done |
+	git -c fastimport.unpackLimit=0 fast-import
+'
+
+# The purpose of this test is to evaluate load time for a large number
+# of packs while doing as little other work as possible.
+test_perf "load 10,000 packs" '
+	git rev-parse --verify "HEAD^{commit}"
+'
+
 test_done
diff --git a/third_party/git/t/perf/p5310-pack-bitmaps.sh b/third_party/git/t/perf/p5310-pack-bitmaps.sh
index 6a3a42531b..b3e725f031 100755
--- a/third_party/git/t/perf/p5310-pack-bitmaps.sh
+++ b/third_party/git/t/perf/p5310-pack-bitmaps.sh
@@ -31,14 +31,37 @@ 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
 '
 
+test_perf 'rev-list (commits)' '
+	git rev-list --all --use-bitmap-index >/dev/null
+'
+
+test_perf 'rev-list (objects)' '
+	git rev-list --all --use-bitmap-index --objects >/dev/null
+'
+
+test_perf 'rev-list count with blob:none' '
+	git rev-list --use-bitmap-index --count --objects --all \
+		--filter=blob:none >/dev/null
+'
+
+test_perf 'rev-list count with blob:limit=1k' '
+	git rev-list --use-bitmap-index --count --objects --all \
+		--filter=blob:limit=1k >/dev/null
+'
+
+test_perf 'rev-list count with tree:0' '
+	git rev-list --use-bitmap-index --count --objects --all \
+		--filter=tree:0 >/dev/null
+'
+
+test_perf 'simulated partial clone' '
+	git pack-objects --stdout --all --filter=blob:none </dev/null >/dev/null
+'
+
 test_expect_success 'create partial bitmap state' '
 	# pick a commit to represent the repo tip in the past
 	cutoff=$(git rev-list HEAD~100 -1) &&
@@ -68,4 +91,9 @@ test_perf 'pack to file (partial bitmap)' '
 	git pack-objects --use-bitmap-index --all pack2b </dev/null >/dev/null
 '
 
+test_perf 'rev-list with tree filter (partial bitmap)' '
+	git rev-list --use-bitmap-index --count --objects --all \
+		--filter=tree:0 >/dev/null
+'
+
 test_done
diff --git a/third_party/git/t/perf/p5600-clone-reference.sh b/third_party/git/t/perf/p5601-clone-reference.sh
index 68fed66347..68fed66347 100755
--- a/third_party/git/t/perf/p5600-clone-reference.sh
+++ b/third_party/git/t/perf/p5601-clone-reference.sh
diff --git a/third_party/git/t/perf/p9300-fast-import-export.sh b/third_party/git/t/perf/p9300-fast-import-export.sh
new file mode 100755
index 0000000000..586161e9ad
--- /dev/null
+++ b/third_party/git/t/perf/p9300-fast-import-export.sh
@@ -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
diff --git a/third_party/git/t/perf/perf-lib.sh b/third_party/git/t/perf/perf-lib.sh
index b58a43ea43..821581a885 100644
--- a/third_party/git/t/perf/perf-lib.sh
+++ b/third_party/git/t/perf/perf-lib.sh
@@ -214,7 +214,7 @@ test_perf_ () {
 	else
 		test_ok_ "$1"
 	fi
-	"$TEST_DIRECTORY"/perf/min_time.perl test_time.* >"$base".times
+	"$TEST_DIRECTORY"/perf/min_time.perl test_time.* >"$base".result
 }
 
 test_perf () {
@@ -223,7 +223,7 @@ test_perf () {
 
 test_size_ () {
 	say >&3 "running: $2"
-	if test_eval_ "$2" 3>"$base".size; then
+	if test_eval_ "$2" 3>"$base".result; then
 		test_ok_ "$1"
 	else
 		test_failure_ "$@"
@@ -245,3 +245,5 @@ test_at_end_hook_ () {
 test_export () {
 	export "$@"
 }
+
+test_lazy_prereq PERF_EXTRA 'test_bool_env GIT_PERF_EXTRA false'
diff --git a/third_party/git/t/t0000-basic.sh b/third_party/git/t/t0000-basic.sh
index 9ca0818cbe..923281af93 100755
--- a/third_party/git/t/t0000-basic.sh
+++ b/third_party/git/t/t0000-basic.sh
@@ -20,9 +20,9 @@ modification *should* take notice and update the test vectors here.
 
 . ./test-lib.sh
 
-try_local_x () {
-	local x="local" &&
-	echo "$x"
+try_local_xy () {
+	local x="local" y="alsolocal" &&
+	echo "$x $y"
 }
 
 # Check whether the shell supports the "local" keyword. "local" is not
@@ -35,11 +35,12 @@ try_local_x () {
 # relying on "local".
 test_expect_success 'verify that the running shell supports "local"' '
 	x="notlocal" &&
-	echo "local" >expected1 &&
-	try_local_x >actual1 &&
+	y="alsonotlocal" &&
+	echo "local alsolocal" >expected1 &&
+	try_local_xy >actual1 &&
 	test_cmp expected1 actual1 &&
-	echo "notlocal" >expected2 &&
-	echo "$x" >actual2 &&
+	echo "notlocal alsonotlocal" >expected2 &&
+	echo "$x $y" >actual2 &&
 	test_cmp expected2 actual2
 '
 
@@ -76,9 +77,7 @@ _run_sub_test_lib_test_common () {
 		# the sub-test.
 		sane_unset HARNESS_ACTIVE &&
 		cd "$name" &&
-		cat >"$name.sh" <<-EOF &&
-		#!$SHELL_PATH
-
+		write_script "$name.sh" "$TEST_SHELL_PATH" <<-EOF &&
 		test_description='$descr (run in sub test-lib)
 
 		This is run in a sub test-lib so that we do not get incorrect
@@ -93,15 +92,15 @@ _run_sub_test_lib_test_common () {
 		. "\$TEST_DIRECTORY"/test-lib.sh
 		EOF
 		cat >>"$name.sh" &&
-		chmod +x "$name.sh" &&
 		export TEST_DIRECTORY &&
 		TEST_OUTPUT_DIRECTORY=$(pwd) &&
 		export TEST_OUTPUT_DIRECTORY &&
+		sane_unset GIT_TEST_FAIL_PREREQS &&
 		if test -z "$neg"
 		then
 			./"$name.sh" "$@" >out 2>err
 		else
-			!  ./"$name.sh" "$@" >out 2>err
+			! ./"$name.sh" "$@" >out 2>err
 		fi
 	)
 }
@@ -126,7 +125,7 @@ check_sub_test_lib_test () {
 
 check_sub_test_lib_test_err () {
 	name="$1" # stdin is the expected output from the test
-	# expected error output is in descriptior 3
+	# expected error output is in descriptor 3
 	(
 		cd "$name" &&
 		sed -e 's/^> //' -e 's/Z$//' >expect.out &&
@@ -154,7 +153,7 @@ test_expect_success 'pretend we have a fully passing test suite' "
 "
 
 test_expect_success 'pretend we have a partially passing test suite' "
-	test_must_fail run_sub_test_lib_test \
+	run_sub_test_lib_test_err \
 		partial-pass '2/3 tests passing' <<-\\EOF &&
 	test_expect_success 'passing test #1' 'true'
 	test_expect_success 'failing test #2' 'false'
@@ -218,7 +217,7 @@ test_expect_success 'pretend we have fixed one of two known breakages (run in su
 "
 
 test_expect_success 'pretend we have a pass, fail, and known breakage' "
-	test_must_fail run_sub_test_lib_test \
+	run_sub_test_lib_test_err \
 		mixed-results1 'mixed results #1' <<-\\EOF &&
 	test_expect_success 'passing test' 'true'
 	test_expect_success 'failing test' 'false'
@@ -237,7 +236,7 @@ test_expect_success 'pretend we have a pass, fail, and known breakage' "
 "
 
 test_expect_success 'pretend we have a mix of all possible results' "
-	test_must_fail run_sub_test_lib_test \
+	run_sub_test_lib_test_err \
 		mixed-results2 'mixed results #2' <<-\\EOF &&
 	test_expect_success 'passing test' 'true'
 	test_expect_success 'passing test' 'true'
@@ -273,24 +272,24 @@ test_expect_success 'pretend we have a mix of all possible results' "
 "
 
 test_expect_success C_LOCALE_OUTPUT 'test --verbose' '
-	test_must_fail run_sub_test_lib_test \
-		test-verbose "test verbose" --verbose <<-\EOF &&
+	run_sub_test_lib_test_err \
+		t1234-verbose "test verbose" --verbose <<-\EOF &&
 	test_expect_success "passing test" true
 	test_expect_success "test with output" "echo foo"
 	test_expect_success "failing test" false
 	test_done
 	EOF
-	mv test-verbose/out test-verbose/out+ &&
-	grep -v "^Initialized empty" test-verbose/out+ >test-verbose/out &&
-	check_sub_test_lib_test test-verbose <<-\EOF
-	> expecting success: true
+	mv t1234-verbose/out t1234-verbose/out+ &&
+	grep -v "^Initialized empty" t1234-verbose/out+ >t1234-verbose/out &&
+	check_sub_test_lib_test t1234-verbose <<-\EOF
+	> expecting success of 1234.1 '\''passing test'\'': true
 	> ok 1 - passing test
 	> Z
-	> expecting success: echo foo
+	> expecting success of 1234.2 '\''test with output'\'': echo foo
 	> foo
 	> ok 2 - test with output
 	> Z
-	> expecting success: false
+	> expecting success of 1234.3 '\''failing test'\'': false
 	> not ok 3 - failing test
 	> #	false
 	> Z
@@ -300,18 +299,18 @@ test_expect_success C_LOCALE_OUTPUT 'test --verbose' '
 '
 
 test_expect_success 'test --verbose-only' '
-	test_must_fail run_sub_test_lib_test \
-		test-verbose-only-2 "test verbose-only=2" \
+	run_sub_test_lib_test_err \
+		t2345-verbose-only-2 "test verbose-only=2" \
 		--verbose-only=2 <<-\EOF &&
 	test_expect_success "passing test" true
 	test_expect_success "test with output" "echo foo"
 	test_expect_success "failing test" false
 	test_done
 	EOF
-	check_sub_test_lib_test test-verbose-only-2 <<-\EOF
+	check_sub_test_lib_test t2345-verbose-only-2 <<-\EOF
 	> ok 1 - passing test
 	> Z
-	> expecting success: echo foo
+	> expecting success of 2345.2 '\''test with output'\'': echo foo
 	> foo
 	> ok 2 - test with output
 	> Z
@@ -391,6 +390,44 @@ test_expect_success 'GIT_SKIP_TESTS sh pattern' "
 	)
 "
 
+test_expect_success 'GIT_SKIP_TESTS entire suite' "
+	(
+		GIT_SKIP_TESTS='git' && export GIT_SKIP_TESTS &&
+		run_sub_test_lib_test git-skip-tests-entire-suite \
+			'GIT_SKIP_TESTS entire suite' <<-\\EOF &&
+		for i in 1 2 3
+		do
+			test_expect_success \"passing test #\$i\" 'true'
+		done
+		test_done
+		EOF
+		check_sub_test_lib_test git-skip-tests-entire-suite <<-\\EOF
+		> 1..0 # SKIP skip all tests in git
+		EOF
+	)
+"
+
+test_expect_success 'GIT_SKIP_TESTS does not skip unmatched suite' "
+	(
+		GIT_SKIP_TESTS='notgit' && export GIT_SKIP_TESTS &&
+		run_sub_test_lib_test git-skip-tests-unmatched-suite \
+			'GIT_SKIP_TESTS does not skip unmatched suite' <<-\\EOF &&
+		for i in 1 2 3
+		do
+			test_expect_success \"passing test #\$i\" 'true'
+		done
+		test_done
+		EOF
+		check_sub_test_lib_test git-skip-tests-unmatched-suite <<-\\EOF
+		> ok 1 - passing test #1
+		> ok 2 - passing test #2
+		> ok 3 - passing test #3
+		> # passed all 3 test(s)
+		> 1..3
+		EOF
+	)
+"
+
 test_expect_success '--run basic' "
 	run_sub_test_lib_test run-basic \
 		'--run basic' --run='1 3 5' <<-\\EOF &&
@@ -794,8 +831,21 @@ 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' "
-	test_must_fail run_sub_test_lib_test \
+	run_sub_test_lib_test_err \
 		failing-cleanup 'Failing tests with cleanup commands' <<-\\EOF &&
 	test_expect_success 'tests clean up even after a failure' '
 		touch clean-after-failure &&
@@ -824,7 +874,7 @@ test_expect_success 'tests clean up even on failures' "
 "
 
 test_expect_success 'test_atexit is run' "
-	test_must_fail run_sub_test_lib_test \
+	run_sub_test_lib_test_err \
 		atexit-cleanup 'Run atexit commands' -i <<-\\EOF &&
 	test_expect_success 'tests clean up even after a failure' '
 		> ../../clean-atexit &&
@@ -841,10 +891,6 @@ test_expect_success 'test_atexit is run' "
 	test_path_is_missing also-clean-atexit
 "
 
-test_expect_success 'test_oid setup' '
-	test_oid_init
-'
-
 test_expect_success 'test_oid provides sane info by default' '
 	test_oid zero >actual &&
 	grep "^00*\$" actual &&
@@ -878,6 +924,51 @@ test_expect_success 'test_oid can look up data for SHA-256' '
 	test "$hexsz" -eq 64
 '
 
+test_expect_success 'test_oid can look up data for a specified algorithm' '
+	rawsz="$(test_oid --hash=sha1 rawsz)" &&
+	hexsz="$(test_oid --hash=sha1 hexsz)" &&
+	test "$rawsz" -eq 20 &&
+	test "$hexsz" -eq 40 &&
+	rawsz="$(test_oid --hash=sha256 rawsz)" &&
+	hexsz="$(test_oid --hash=sha256 hexsz)" &&
+	test "$rawsz" -eq 32 &&
+	test "$hexsz" -eq 64
+'
+
+test_expect_success 'test_bool_env' '
+	(
+		sane_unset envvar &&
+
+		test_bool_env envvar true &&
+		! test_bool_env envvar false &&
+
+		envvar= &&
+		export envvar &&
+		! test_bool_env envvar true &&
+		! test_bool_env envvar false &&
+
+		envvar=true &&
+		test_bool_env envvar true &&
+		test_bool_env envvar false &&
+
+		envvar=false &&
+		! test_bool_env envvar true &&
+		! test_bool_env envvar false &&
+
+		envvar=invalid &&
+		# When encountering an invalid bool value, test_bool_env
+		# prints its error message to the original stderr of the
+		# test script, hence the redirection of fd 7, and aborts
+		# with "exit 1", hence the subshell.
+		! ( test_bool_env envvar true ) 7>err &&
+		grep "error: test_bool_env requires bool values" err &&
+
+		envvar=true &&
+		! ( test_bool_env envvar invalid ) 7>err &&
+		grep "error: test_bool_env requires bool values" err
+	)
+'
+
 ################################################################
 # Basics of the basics
 
@@ -1187,4 +1278,22 @@ test_expect_success 'very long name in the index handled sanely' '
 	test $len = 4098
 '
 
+test_expect_success 'test_must_fail on a failing git command' '
+	test_must_fail git notacommand
+'
+
+test_expect_success 'test_must_fail on a failing git command with env' '
+	test_must_fail env var1=a var2=b git notacommand
+'
+
+test_expect_success 'test_must_fail rejects a non-git command' '
+	! test_must_fail grep ^$ notafile 2>err &&
+	grep -F "test_must_fail: only '"'"'git'"'"' is allowed" err
+'
+
+test_expect_success 'test_must_fail rejects a non-git command with env' '
+	! test_must_fail env var1=a var2=b grep ^$ notafile 2>err &&
+	grep -F "test_must_fail: only '"'"'git'"'"' is allowed" err
+'
+
 test_done
diff --git a/third_party/git/t/t0001-init.sh b/third_party/git/t/t0001-init.sh
index 26f8206326..2f7c3dcd0f 100755
--- a/third_party/git/t/t0001-init.sh
+++ b/third_party/git/t/t0001-init.sh
@@ -316,6 +316,28 @@ test_expect_success 'init with separate gitdir' '
 	test_path_is_dir realgitdir/refs
 '
 
+test_expect_success 'explicit bare & --separate-git-dir incompatible' '
+	test_must_fail git init --bare --separate-git-dir goop.git bare.git 2>err &&
+	test_i18ngrep "mutually exclusive" err
+'
+
+test_expect_success 'implicit bare & --separate-git-dir incompatible' '
+	test_when_finished "rm -rf bare.git" &&
+	mkdir -p bare.git &&
+	test_must_fail env GIT_DIR=. \
+		git -C bare.git init --separate-git-dir goop.git 2>err &&
+	test_i18ngrep "incompatible" err
+'
+
+test_expect_success 'bare & --separate-git-dir incompatible within worktree' '
+	test_when_finished "rm -rf bare.git linkwt seprepo" &&
+	test_commit gumby &&
+	git clone --bare . bare.git &&
+	git -C bare.git worktree add --detach ../linkwt &&
+	test_must_fail git -C linkwt init --separate-git-dir seprepo 2>err &&
+	test_i18ngrep "incompatible" err
+'
+
 test_lazy_prereq GETCWD_IGNORES_PERMS '
 	base=GETCWD_TEST_BASE_DIR &&
 	mkdir -p $base/dir &&
@@ -392,13 +414,25 @@ 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
+sep_git_dir_worktree ()  {
+	test_when_finished "rm -rf mainwt linkwt seprepo" &&
+	git init mainwt &&
+	test_commit -C mainwt gumby &&
+	git -C mainwt worktree add --detach ../linkwt &&
+	git -C "$1" init --separate-git-dir ../seprepo &&
+	git -C mainwt rev-parse --git-common-dir >expect &&
+	git -C linkwt rev-parse --git-common-dir >actual &&
+	test_cmp expect actual
 }
 
+test_expect_success 're-init to move gitdir with linked worktrees' '
+	sep_git_dir_worktree mainwt
+'
+
+test_expect_success 're-init to move gitdir within linked worktree' '
+	sep_git_dir_worktree linkwt
+'
+
 test_expect_success MINGW '.git hidden' '
 	rm -rf newdir &&
 	(
@@ -406,7 +440,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
 '
@@ -448,6 +482,39 @@ test_expect_success 're-init from a linked worktree' '
 	)
 '
 
+test_expect_success 'init honors GIT_DEFAULT_HASH' '
+	GIT_DEFAULT_HASH=sha1 git init sha1 &&
+	git -C sha1 rev-parse --show-object-format >actual &&
+	echo sha1 >expected &&
+	test_cmp expected actual &&
+	GIT_DEFAULT_HASH=sha256 git init sha256 &&
+	git -C sha256 rev-parse --show-object-format >actual &&
+	echo sha256 >expected &&
+	test_cmp expected actual
+'
+
+test_expect_success 'init honors --object-format' '
+	git init --object-format=sha1 explicit-sha1 &&
+	git -C explicit-sha1 rev-parse --show-object-format >actual &&
+	echo sha1 >expected &&
+	test_cmp expected actual &&
+	git init --object-format=sha256 explicit-sha256 &&
+	git -C explicit-sha256 rev-parse --show-object-format >actual &&
+	echo sha256 >expected &&
+	test_cmp expected actual
+'
+
+test_expect_success 'extensions.objectFormat is not allowed with repo version 0' '
+	git init --object-format=sha256 explicit-v0 &&
+	git -C explicit-v0 config core.repositoryformatversion 0 &&
+	test_must_fail git -C explicit-v0 rev-parse --show-object-format
+'
+
+test_expect_success 'init rejects attempts to initialize with different hash' '
+	test_must_fail git -C sha1 init --object-format=sha256 &&
+	test_must_fail git -C sha256 init --object-format=sha1
+'
+
 test_expect_success MINGW 'core.hidedotfiles = false' '
 	git config --global core.hidedotfiles false &&
 	rm -rf newdir &&
@@ -471,4 +538,30 @@ test_expect_success MINGW 'redirect std handles' '
 	grep "Needed a single revision" output.txt
 '
 
+test_expect_success '--initial-branch' '
+	git init --initial-branch=hello initial-branch-option &&
+	git -C initial-branch-option symbolic-ref HEAD >actual &&
+	echo refs/heads/hello >expect &&
+	test_cmp expect actual &&
+
+	: re-initializing should not change the branch name &&
+	git init --initial-branch=ignore initial-branch-option 2>err &&
+	test_i18ngrep "ignored --initial-branch" err &&
+	git -C initial-branch-option symbolic-ref HEAD >actual &&
+	grep hello actual
+'
+
+test_expect_success 'overridden default initial branch name (config)' '
+	test_config_global init.defaultBranch nmb &&
+	git init initial-branch-config &&
+	git -C initial-branch-config symbolic-ref HEAD >actual &&
+	grep nmb actual
+'
+
+test_expect_success 'invalid default branch name' '
+	test_config_global init.defaultBranch "with space" &&
+	test_must_fail git init initial-branch-invalid 2>err &&
+	test_i18ngrep "invalid branch name" err
+'
+
 test_done
diff --git a/third_party/git/t/t0002-gitfile.sh b/third_party/git/t/t0002-gitfile.sh
index 0aa9908ea1..960ed150cb 100755
--- a/third_party/git/t/t0002-gitfile.sh
+++ b/third_party/git/t/t0002-gitfile.sh
@@ -62,7 +62,7 @@ test_expect_success 'check commit-tree' '
 '
 
 test_expect_success 'check rev-list' '
-	echo $SHA >"$REAL/HEAD" &&
+	git update-ref "HEAD" "$SHA" &&
 	test "$SHA" = "$(git rev-list HEAD)"
 '
 
diff --git a/third_party/git/t/t0003-attributes.sh b/third_party/git/t/t0003-attributes.sh
index 71e63d8b50..b660593c20 100755
--- a/third_party/git/t/t0003-attributes.sh
+++ b/third_party/git/t/t0003-attributes.sh
@@ -5,19 +5,16 @@ test_description=gitattributes
 . ./test-lib.sh
 
 attr_check () {
-	path="$1" expect="$2"
+	path="$1" expect="$2" git_opts="$3" &&
 
-	git $3 check-attr test -- "$path" >actual 2>err &&
-	echo "$path: test: $2" >expect &&
+	git $git_opts check-attr test -- "$path" >actual 2>err &&
+	echo "$path: test: $expect" >expect &&
 	test_cmp expect actual &&
-	test_line_count = 0 err
+	test_must_be_empty err
 }
 
 attr_check_quote () {
-
-	path="$1"
-	quoted_path="$2"
-	expect="$3"
+	path="$1" quoted_path="$2" expect="$3" &&
 
 	git check-attr test -- "$path" >actual &&
 	echo "\"$quoted_path\": test: $expect" >expect &&
@@ -27,7 +24,7 @@ attr_check_quote () {
 
 test_expect_success 'open-quoted pathname' '
 	echo "\"a test=a" >.gitattributes &&
-	test_must_fail attr_check a a
+	attr_check a unspecified
 '
 
 
@@ -112,20 +109,20 @@ test_expect_success 'attribute test' '
 
 test_expect_success 'attribute matching is case sensitive when core.ignorecase=0' '
 
-	test_must_fail attr_check F f "-c core.ignorecase=0" &&
-	test_must_fail attr_check a/F f "-c core.ignorecase=0" &&
-	test_must_fail attr_check a/c/F f "-c core.ignorecase=0" &&
-	test_must_fail attr_check a/G a/g "-c core.ignorecase=0" &&
-	test_must_fail attr_check a/B/g a/b/g "-c core.ignorecase=0" &&
-	test_must_fail attr_check a/b/G a/b/g "-c core.ignorecase=0" &&
-	test_must_fail attr_check a/b/H a/b/h "-c core.ignorecase=0" &&
-	test_must_fail attr_check a/b/D/g "a/b/d/*" "-c core.ignorecase=0" &&
-	test_must_fail attr_check oNoFf unset "-c core.ignorecase=0" &&
-	test_must_fail attr_check oFfOn set "-c core.ignorecase=0" &&
+	attr_check F unspecified "-c core.ignorecase=0" &&
+	attr_check a/F unspecified "-c core.ignorecase=0" &&
+	attr_check a/c/F unspecified "-c core.ignorecase=0" &&
+	attr_check a/G unspecified "-c core.ignorecase=0" &&
+	attr_check a/B/g a/g "-c core.ignorecase=0" &&
+	attr_check a/b/G unspecified "-c core.ignorecase=0" &&
+	attr_check a/b/H unspecified "-c core.ignorecase=0" &&
+	attr_check a/b/D/g a/g "-c core.ignorecase=0" &&
+	attr_check oNoFf unspecified "-c core.ignorecase=0" &&
+	attr_check oFfOn unspecified "-c core.ignorecase=0" &&
 	attr_check NO unspecified "-c core.ignorecase=0" &&
-	test_must_fail attr_check a/b/D/NO "a/b/d/*" "-c core.ignorecase=0" &&
+	attr_check a/b/D/NO unspecified "-c core.ignorecase=0" &&
 	attr_check a/b/d/YES a/b/d/* "-c core.ignorecase=0" &&
-	test_must_fail attr_check a/E/f "A/e/F" "-c core.ignorecase=0"
+	attr_check a/E/f f "-c core.ignorecase=0"
 
 '
 
@@ -149,8 +146,8 @@ test_expect_success 'attribute matching is case insensitive when core.ignorecase
 '
 
 test_expect_success CASE_INSENSITIVE_FS 'additional case insensitivity tests' '
-	test_must_fail attr_check a/B/D/g "a/b/d/*" "-c core.ignorecase=0" &&
-	test_must_fail attr_check A/B/D/NO "a/b/d/*" "-c core.ignorecase=0" &&
+	attr_check a/B/D/g a/g "-c core.ignorecase=0" &&
+	attr_check A/B/D/NO unspecified "-c core.ignorecase=0" &&
 	attr_check A/b/h a/b/h "-c core.ignorecase=1" &&
 	attr_check a/B/D/g "a/b/d/*" "-c core.ignorecase=1" &&
 	attr_check A/B/D/NO "a/b/d/*" "-c core.ignorecase=1"
@@ -244,7 +241,7 @@ EOF
 	git check-attr foo -- "a/b/f" >>actual 2>>err &&
 	git check-attr foo -- "a/b/c/f" >>actual 2>>err &&
 	test_cmp expect actual &&
-	test_line_count = 0 err
+	test_must_be_empty err
 '
 
 test_expect_success '"**" with no slashes test' '
@@ -265,7 +262,7 @@ EOF
 	git check-attr foo -- "a/b/f" >>actual 2>>err &&
 	git check-attr foo -- "a/b/c/f" >>actual 2>>err &&
 	test_cmp expect actual &&
-	test_line_count = 0 err
+	test_must_be_empty err
 '
 
 test_expect_success 'using --git-dir and --work-tree' '
diff --git a/third_party/git/t/t0006-date.sh b/third_party/git/t/t0006-date.sh
index b723db1f76..f68149be88 100755
--- a/third_party/git/t/t0006-date.sh
+++ b/third_party/git/t/t0006-date.sh
@@ -83,6 +83,11 @@ check_parse 2008-02 bad
 check_parse 2008-02-14 bad
 check_parse '2008-02-14 20:30:45' '2008-02-14 20:30:45 +0000'
 check_parse '2008-02-14 20:30:45 -0500' '2008-02-14 20:30:45 -0500'
+check_parse '2008.02.14 20:30:45 -0500' '2008-02-14 20:30:45 -0500'
+check_parse '20080214T203045-04:00' '2008-02-14 20:30:45 -0400'
+check_parse '20080214T203045 -04:00' '2008-02-14 20:30:45 -0400'
+check_parse '20080214T203045.019-04:00' '2008-02-14 20:30:45 -0400'
+check_parse '2008-02-14 20:30:45.019-04:00' '2008-02-14 20:30:45 -0400'
 check_parse '2008-02-14 20:30:45 -0015' '2008-02-14 20:30:45 -0015'
 check_parse '2008-02-14 20:30:45 -5' '2008-02-14 20:30:45 +0000'
 check_parse '2008-02-14 20:30:45 -5:' '2008-02-14 20:30:45 +0000'
@@ -105,6 +110,7 @@ check_approxidate 5.seconds.ago '2009-08-30 19:19:55'
 check_approxidate 10.minutes.ago '2009-08-30 19:10:00'
 check_approxidate yesterday '2009-08-29 19:20:00'
 check_approxidate 3.days.ago '2009-08-27 19:20:00'
+check_approxidate '12:34:56.3.days.ago' '2009-08-27 12:34:56'
 check_approxidate 3.weeks.ago '2009-08-09 19:20:00'
 check_approxidate 3.months.ago '2009-05-30 19:20:00'
 check_approxidate 2.years.3.months.ago '2007-05-30 19:20:00'
diff --git a/third_party/git/t/t0007-git-var.sh b/third_party/git/t/t0007-git-var.sh
index 1f600e2cae..88b9ae8158 100755
--- a/third_party/git/t/t0007-git-var.sh
+++ b/third_party/git/t/t0007-git-var.sh
@@ -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/third_party/git/t/t0008-ignores.sh b/third_party/git/t/t0008-ignores.sh
index 1744cee5e9..370a389e5c 100755
--- a/third_party/git/t/t0008-ignores.sh
+++ b/third_party/git/t/t0008-ignores.sh
@@ -424,9 +424,24 @@ test_expect_success 'local ignore inside a sub-directory with --verbose' '
 	)
 '
 
-test_expect_success_multi 'nested include' \
-	'a/b/.gitignore:8:!on*	a/b/one' '
-	test_check_ignore "a/b/one"
+test_expect_success 'nested include of negated pattern' '
+	expect "" &&
+	test_check_ignore "a/b/one" 1
+'
+
+test_expect_success 'nested include of negated pattern with -q' '
+	expect "" &&
+	test_check_ignore "-q a/b/one" 1
+'
+
+test_expect_success 'nested include of negated pattern with -v' '
+	expect "a/b/.gitignore:8:!on*	a/b/one" &&
+	test_check_ignore "-v a/b/one" 0
+'
+
+test_expect_success 'nested include of negated pattern with -v -n' '
+	expect "a/b/.gitignore:8:!on*	a/b/one" &&
+	test_check_ignore "-v -n a/b/one" 0
 '
 
 ############################################################################
@@ -460,7 +475,6 @@ test_expect_success 'cd to ignored sub-directory' '
 	expect_from_stdin <<-\EOF &&
 		foo
 		twoooo
-		../one
 		seven
 		../../one
 	EOF
@@ -543,7 +557,6 @@ test_expect_success 'global ignore' '
 		globalthree
 		a/globalthree
 		a/per-repo
-		globaltwo
 	EOF
 	test_check_ignore "globalone per-repo globalthree a/globalthree a/per-repo not-ignored globaltwo"
 '
@@ -586,17 +599,7 @@ EOF
 cat <<-\EOF >expected-default
 	one
 	a/one
-	a/b/on
-	a/b/one
-	a/b/one one
-	a/b/one two
-	"a/b/one\"three"
-	a/b/two
 	a/b/twooo
-	globaltwo
-	a/globaltwo
-	a/b/globaltwo
-	b/globaltwo
 EOF
 cat <<-EOF >expected-verbose
 	.gitignore:1:one	one
@@ -696,8 +699,12 @@ cat <<-EOF >expected-all
 	$global_excludes:2:!globaltwo	../b/globaltwo
 	::	c/not-ignored
 EOF
+cat <<-EOF >expected-default
+../one
+one
+b/twooo
+EOF
 grep -v '^::	' expected-all >expected-verbose
-sed -e 's/.*	//' expected-verbose >expected-default
 
 broken_c_unquote stdin >stdin0
 
diff --git a/third_party/git/t/t0014-alias.sh b/third_party/git/t/t0014-alias.sh
index a070e645d7..8d3d9144c0 100755
--- a/third_party/git/t/t0014-alias.sh
+++ b/third_party/git/t/t0014-alias.sh
@@ -37,4 +37,11 @@ test_expect_success 'looping aliases - internal execution' '
 #	test_i18ngrep "^fatal: alias loop detected: expansion of" output
 #'
 
+test_expect_success 'run-command formats empty args properly' '
+    test_must_fail env GIT_TRACE=1 git frotz a "" b " " c 2>actual.raw &&
+    sed -ne "/run_command:/s/.*trace: run_command: //p" actual.raw >actual &&
+    echo "git-frotz a '\'''\'' b '\'' '\'' c" >expect &&
+    test_cmp expect actual
+'
+
 test_done
diff --git a/third_party/git/t/t0018-advice.sh b/third_party/git/t/t0018-advice.sh
new file mode 100755
index 0000000000..e03554d2f3
--- /dev/null
+++ b/third_party/git/t/t0018-advice.sh
@@ -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
diff --git a/third_party/git/t/t0020-crlf.sh b/third_party/git/t/t0020-crlf.sh
index 854da0ae16..b63ba62e5d 100755
--- a/third_party/git/t/t0020-crlf.sh
+++ b/third_party/git/t/t0020-crlf.sh
@@ -159,8 +159,8 @@ test_expect_success 'checkout with autocrlf=input' '
 	rm -f tmp one dir/two three &&
 	git config core.autocrlf input &&
 	git read-tree --reset -u HEAD &&
-	test_must_fail has_cr one &&
-	test_must_fail has_cr dir/two &&
+	! has_cr one &&
+	! has_cr dir/two &&
 	git update-index -- one dir/two &&
 	test "$one" = $(git hash-object --stdin <one) &&
 	test "$two" = $(git hash-object --stdin <dir/two) &&
@@ -237,9 +237,9 @@ test_expect_success '.gitattributes says two is binary' '
 	git config core.autocrlf true &&
 	git read-tree --reset -u HEAD &&
 
-	test_must_fail has_cr dir/two &&
+	! has_cr dir/two &&
 	verbose has_cr one &&
-	test_must_fail has_cr three
+	! has_cr three
 '
 
 test_expect_success '.gitattributes says two is input' '
@@ -248,7 +248,7 @@ test_expect_success '.gitattributes says two is input' '
 	echo "two crlf=input" >.gitattributes &&
 	git read-tree --reset -u HEAD &&
 
-	test_must_fail has_cr dir/two
+	! has_cr dir/two
 '
 
 test_expect_success '.gitattributes says two and three are text' '
@@ -270,7 +270,7 @@ test_expect_success 'in-tree .gitattributes (1)' '
 	rm -rf tmp one dir .gitattributes patch.file three &&
 	git read-tree --reset -u HEAD &&
 
-	test_must_fail has_cr one &&
+	! has_cr one &&
 	verbose has_cr three
 '
 
@@ -280,7 +280,7 @@ test_expect_success 'in-tree .gitattributes (2)' '
 	git read-tree --reset HEAD &&
 	git checkout-index -f -q -u -a &&
 
-	test_must_fail has_cr one &&
+	! has_cr one &&
 	verbose has_cr three
 '
 
@@ -291,7 +291,7 @@ test_expect_success 'in-tree .gitattributes (3)' '
 	git checkout-index -u .gitattributes &&
 	git checkout-index -u one dir/two three &&
 
-	test_must_fail has_cr one &&
+	! has_cr one &&
 	verbose has_cr three
 '
 
@@ -302,7 +302,7 @@ test_expect_success 'in-tree .gitattributes (4)' '
 	git checkout-index -u one dir/two three &&
 	git checkout-index -u .gitattributes &&
 
-	test_must_fail has_cr one &&
+	! has_cr one &&
 	verbose has_cr three
 '
 
diff --git a/third_party/git/t/t0021-conversion.sh b/third_party/git/t/t0021-conversion.sh
index e10f5f787f..4bfffa9c31 100755
--- a/third_party/git/t/t0021-conversion.sh
+++ b/third_party/git/t/t0021-conversion.sh
@@ -35,7 +35,7 @@ filter_git () {
 # Compare two files and ensure that `clean` and `smudge` respectively are
 # called at least once if specified in the `expect` file. The actual
 # invocation count is not relevant because their number can vary.
-# c.f. http://public-inbox.org/git/xmqqshv18i8i.fsf@gitster.mtv.corp.google.com/
+# c.f. http://lore.kernel.org/git/xmqqshv18i8i.fsf@gitster.mtv.corp.google.com/
 test_cmp_count () {
 	expect=$1
 	actual=$2
@@ -50,7 +50,7 @@ test_cmp_count () {
 
 # Compare two files but exclude all `clean` invocations because Git can
 # call `clean` zero or more times.
-# c.f. http://public-inbox.org/git/xmqqshv18i8i.fsf@gitster.mtv.corp.google.com/
+# c.f. http://lore.kernel.org/git/xmqqshv18i8i.fsf@gitster.mtv.corp.google.com/
 test_cmp_exclude_clean () {
 	expect=$1
 	actual=$2
@@ -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,18 +382,23 @@ 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 &&
 
+		# Make sure that the file appears dirty, so checkout below has to
+		# run the configured filter.
+		touch test.r &&
 		filter_git checkout --quiet --no-progress empty-branch &&
 		cat >expected.log <<-EOF &&
 			START
@@ -403,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 &&
@@ -417,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" &&
@@ -516,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 &&
@@ -575,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 &&
@@ -588,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 &&
@@ -626,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 &&
@@ -634,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 &&
@@ -662,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 &&
@@ -724,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
@@ -764,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 &&
@@ -792,7 +933,6 @@ test_expect_success PERL 'missing file in delayed checkout' '
 
 	rm -rf repo-cloned &&
 	test_must_fail git clone repo repo-cloned 2>git-stderr.log &&
-	cat git-stderr.log &&
 	grep "error: .missing-delay\.a. was not filtered properly" git-stderr.log
 '
 
diff --git a/third_party/git/t/t0021/rot13-filter.pl b/third_party/git/t/t0021/rot13-filter.pl
index 470107248e..cd32a82da5 100644
--- a/third_party/git/t/t0021/rot13-filter.pl
+++ b/third_party/git/t/t0021/rot13-filter.pl
@@ -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'";
 			}
 
diff --git a/third_party/git/t/t0027-auto-crlf.sh b/third_party/git/t/t0027-auto-crlf.sh
index 959b6da449..9fcd56fab3 100755
--- a/third_party/git/t/t0027-auto-crlf.sh
+++ b/third_party/git/t/t0027-auto-crlf.sh
@@ -215,7 +215,7 @@ stats_ascii () {
 }
 
 
-# contruct the attr/ returned by git ls-files --eol
+# construct the attr/ returned by git ls-files --eol
 # Take none (=empty), one or two args
 # convert.c: eol=XX overrides text=auto
 attr_ascii () {
diff --git a/third_party/git/t/t0028-working-tree-encoding.sh b/third_party/git/t/t0028-working-tree-encoding.sh
index 1090e650ed..bfc4fb9af5 100755
--- a/third_party/git/t/t0028-working-tree-encoding.sh
+++ b/third_party/git/t/t0028-working-tree-encoding.sh
@@ -17,7 +17,7 @@ test_lazy_prereq NO_UTF32_BOM '
 write_utf16 () {
 	if test_have_prereq NO_UTF16_BOM
 	then
-		printf '\xfe\xff'
+		printf '\376\377'
 	fi &&
 	iconv -f UTF-8 -t UTF-16
 }
@@ -25,7 +25,7 @@ write_utf16 () {
 write_utf32 () {
 	if test_have_prereq NO_UTF32_BOM
 	then
-		printf '\x00\x00\xfe\xff'
+		printf '\0\0\376\377'
 	fi &&
 	iconv -f UTF-8 -t UTF-32
 }
@@ -40,7 +40,7 @@ test_expect_success 'setup test files' '
 	printf "$text" | write_utf16 >test.utf16.raw &&
 	printf "$text" | write_utf32 >test.utf32.raw &&
 	printf "\377\376"                         >test.utf16lebom.raw &&
-	printf "$text" | iconv -f UTF-8 -t UTF-32LE >>test.utf16lebom.raw &&
+	printf "$text" | iconv -f UTF-8 -t UTF-16LE >>test.utf16lebom.raw &&
 
 	# Line ending tests
 	printf "one\ntwo\nthree\n" >lf.utf8.raw &&
@@ -280,4 +280,43 @@ test_expect_success ICONV_SHIFT_JIS 'check roundtrip encoding' '
 	git reset
 '
 
+# $1: checkout encoding
+# $2: test string
+# $3: binary test string in checkout encoding
+test_commit_utf8_checkout_other () {
+	encoding="$1"
+	orig_string="$2"
+	expect_bytes="$3"
+
+	test_expect_success "Commit UTF-8, checkout $encoding" '
+		test_when_finished "git checkout HEAD -- .gitattributes" &&
+
+		test_ext="commit_utf8_checkout_$encoding" &&
+		test_file="test.$test_ext" &&
+
+		# Commit as UTF-8
+		echo "*.$test_ext text working-tree-encoding=UTF-8" >.gitattributes &&
+		printf "$orig_string" >$test_file &&
+		git add $test_file &&
+		git commit -m "Test data" &&
+
+		# Checkout in tested encoding
+		rm $test_file &&
+		echo "*.$test_ext text working-tree-encoding=$encoding" >.gitattributes &&
+		git checkout HEAD -- $test_file &&
+
+		# Test
+		printf $expect_bytes >$test_file.raw &&
+		test_cmp_bin $test_file.raw $test_file
+	'
+}
+
+test_commit_utf8_checkout_other "UTF-8"        "Test ะขะตัั‚" "\124\145\163\164\040\320\242\320\265\321\201\321\202"
+test_commit_utf8_checkout_other "UTF-16LE"     "Test ะขะตัั‚" "\124\000\145\000\163\000\164\000\040\000\042\004\065\004\101\004\102\004"
+test_commit_utf8_checkout_other "UTF-16BE"     "Test ะขะตัั‚" "\000\124\000\145\000\163\000\164\000\040\004\042\004\065\004\101\004\102"
+test_commit_utf8_checkout_other "UTF-16LE-BOM" "Test ะขะตัั‚" "\377\376\124\000\145\000\163\000\164\000\040\000\042\004\065\004\101\004\102\004"
+test_commit_utf8_checkout_other "UTF-16BE-BOM" "Test ะขะตัั‚" "\376\377\000\124\000\145\000\163\000\164\000\040\004\042\004\065\004\101\004\102"
+test_commit_utf8_checkout_other "UTF-32LE"     "Test ะขะตัั‚" "\124\000\000\000\145\000\000\000\163\000\000\000\164\000\000\000\040\000\000\000\042\004\000\000\065\004\000\000\101\004\000\000\102\004\000\000"
+test_commit_utf8_checkout_other "UTF-32BE"     "Test ะขะตัั‚" "\000\000\000\124\000\000\000\145\000\000\000\163\000\000\000\164\000\000\000\040\000\000\004\042\000\000\004\065\000\000\004\101\000\000\004\102"
+
 test_done
diff --git a/third_party/git/t/t0040-parse-options.sh b/third_party/git/t/t0040-parse-options.sh
index cebc77fab0..14cafc138b 100755
--- a/third_party/git/t/t0040-parse-options.sh
+++ b/third_party/git/t/t0040-parse-options.sh
@@ -23,6 +23,8 @@ usage: test-tool parse-options <options>
     -j <n>                get a integer, too
     -m, --magnitude <n>   get a magnitude
     --set23               set integer to 23
+    --mode1               set integer to 1 (cmdmode option)
+    --mode2               set integer to 2 (cmdmode option)
     -L, --length <str>    get length of <str>
     -F, --file <file>     set file to <file>
 
@@ -42,7 +44,7 @@ Magic arguments
     --no-ambiguous        negative ambiguity
 
 Standard options
-    --abbrev[=<n>]        use <n> digits to display SHA-1s
+    --abbrev[=<n>]        use <n> digits to display object names
     -v, --verbose         be verbose
     -n, --dry-run         dry run
     -q, --quiet           be quiet
@@ -52,7 +54,7 @@ Alias
     -A, --alias-source <string>
                           get a string
     -Z, --alias-target <string>
-                          get a string
+                          alias of --alias-source
 
 EOF
 
@@ -242,7 +244,7 @@ test_expect_success 'Alias options do not contribute to abbreviation' '
 '
 
 cat >typo.err <<\EOF
-error: did you mean `--boolean` (with two dashes ?)
+error: did you mean `--boolean` (with two dashes)?
 EOF
 
 test_expect_success 'detect possible typos' '
@@ -252,7 +254,7 @@ test_expect_success 'detect possible typos' '
 '
 
 cat >typo.err <<\EOF
-error: did you mean `--ambiguous` (with two dashes ?)
+error: did you mean `--ambiguous` (with two dashes)?
 EOF
 
 test_expect_success 'detect possible typos' '
@@ -324,6 +326,22 @@ test_expect_success 'OPT_NEGBIT() works' '
 	test-tool parse-options --expect="boolean: 6" -bb --no-neg-or4
 '
 
+test_expect_success 'OPT_CMDMODE() works' '
+	test-tool parse-options --expect="integer: 1" --mode1
+'
+
+test_expect_success 'OPT_CMDMODE() detects incompatibility' '
+	test_must_fail test-tool parse-options --mode1 --mode2 >output 2>output.err &&
+	test_must_be_empty output &&
+	test_i18ngrep "incompatible with --mode" output.err
+'
+
+test_expect_success 'OPT_CMDMODE() detects incompatibility with something else' '
+	test_must_fail test-tool parse-options --set23 --mode2 >output 2>output.err &&
+	test_must_be_empty output &&
+	test_i18ngrep "incompatible with something else" output.err
+'
+
 test_expect_success 'OPT_COUNTUP() with PARSE_OPT_NODASH works' '
 	test-tool parse-options --expect="boolean: 6" + + + + + +
 '
@@ -399,4 +417,11 @@ test_expect_success 'GIT_TEST_DISALLOW_ABBREVIATED_OPTIONS works' '
 		test-tool parse-options --ye
 '
 
+test_expect_success '--end-of-options treats remainder as args' '
+	test-tool parse-options \
+	    --expect="verbose: -1" \
+	    --expect="arg 00: --verbose" \
+	    --end-of-options --verbose
+'
+
 test_done
diff --git a/third_party/git/t/t0050-filesystem.sh b/third_party/git/t/t0050-filesystem.sh
index 192c94eccd..608673fb77 100755
--- a/third_party/git/t/t0050-filesystem.sh
+++ b/third_party/git/t/t0050-filesystem.sh
@@ -131,4 +131,24 @@ $test_unicode 'merge (silent unicode normalization)' '
 	git merge topic
 '
 
+test_expect_success CASE_INSENSITIVE_FS 'checkout with no pathspec and a case insensitive fs' '
+	git init repo &&
+	(
+		cd repo &&
+
+		>Gitweb &&
+		git add Gitweb &&
+		git commit -m "add Gitweb" &&
+
+		git checkout --orphan todo &&
+		git reset --hard &&
+		mkdir -p gitweb/subdir &&
+		>gitweb/subdir/file &&
+		git add gitweb &&
+		git commit -m "add gitweb/subdir/file" &&
+
+		git checkout master
+	)
+'
+
 test_done
diff --git a/third_party/git/t/t0060-path-utils.sh b/third_party/git/t/t0060-path-utils.sh
index c7b53e494b..56db5c8aba 100755
--- a/third_party/git/t/t0060-path-utils.sh
+++ b/third_party/git/t/t0060-path-utils.sh
@@ -165,6 +165,15 @@ test_expect_success 'absolute path rejects the empty string' '
 	test_must_fail test-tool path-utils absolute_path ""
 '
 
+test_expect_success MINGW '<drive-letter>:\\abc is an absolute path' '
+	for letter in : \" C Z 1 รค
+	do
+		path=$letter:\\abc &&
+		absolute="$(test-tool path-utils absolute_path "$path")" &&
+		test "$path" = "$absolute" || return 1
+	done
+'
+
 test_expect_success 'real path rejects the empty string' '
 	test_must_fail test-tool path-utils real_path ""
 '
@@ -285,9 +294,13 @@ test_git_path GIT_OBJECT_DIRECTORY=foo objects/foo foo/foo
 test_git_path GIT_OBJECT_DIRECTORY=foo objects2 .git/objects2
 test_expect_success 'setup common repository' 'git --git-dir=bar init'
 test_git_path GIT_COMMON_DIR=bar index                    .git/index
+test_git_path GIT_COMMON_DIR=bar index.lock               .git/index.lock
 test_git_path GIT_COMMON_DIR=bar HEAD                     .git/HEAD
 test_git_path GIT_COMMON_DIR=bar logs/HEAD                .git/logs/HEAD
+test_git_path GIT_COMMON_DIR=bar logs/HEAD.lock           .git/logs/HEAD.lock
 test_git_path GIT_COMMON_DIR=bar logs/refs/bisect/foo     .git/logs/refs/bisect/foo
+test_git_path GIT_COMMON_DIR=bar logs/refs                bar/logs/refs
+test_git_path GIT_COMMON_DIR=bar logs/refs/               bar/logs/refs/
 test_git_path GIT_COMMON_DIR=bar logs/refs/bisec/foo      bar/logs/refs/bisec/foo
 test_git_path GIT_COMMON_DIR=bar logs/refs/bisec          bar/logs/refs/bisec
 test_git_path GIT_COMMON_DIR=bar logs/refs/bisectfoo      bar/logs/refs/bisectfoo
@@ -423,6 +436,9 @@ test_expect_success 'match .gitmodules' '
 		~1000000 \
 		~9999999 \
 		\
+		.gitmodules:\$DATA \
+		"gitmod~4 . :\$DATA" \
+		\
 		--not \
 		".gitmodules x"  \
 		".gitmodules .x" \
@@ -447,7 +463,36 @@ test_expect_success 'match .gitmodules' '
 		\
 		GI7EB~1 \
 		GI7EB~01 \
-		GI7EB~1X
+		GI7EB~1X \
+		\
+		.gitmodules,:\$DATA
+'
+
+test_expect_success MINGW 'is_valid_path() on Windows' '
+	test-tool path-utils is_valid_path \
+		win32 \
+		"win32 x" \
+		../hello.txt \
+		C:\\git \
+		comm \
+		conout.c \
+		com0.c \
+		lptN \
+		\
+		--not \
+		"win32 "  \
+		"win32 /x "  \
+		"win32."  \
+		"win32 . ." \
+		.../hello.txt \
+		colon:test \
+		"AUX.c" \
+		"abc/conOut\$  .xyz/test" \
+		lpt8 \
+		com9.c \
+		"lpt*" \
+		Nul \
+		"PRN./abc"
 '
 
 test_done
diff --git a/third_party/git/t/t0061-run-command.sh b/third_party/git/t/t0061-run-command.sh
index 015fac8b5d..7d599675e3 100755
--- a/third_party/git/t/t0061-run-command.sh
+++ b/third_party/git/t/t0061-run-command.sh
@@ -12,6 +12,10 @@ cat >hello-script <<-EOF
 	cat hello-script
 EOF
 
+test_expect_success MINGW 'subprocess inherits only std handles' '
+	test-tool run-command inherited-handle
+'
+
 test_expect_success 'start_command reports ENOENT (slash)' '
 	test-tool run-command start-command-ENOENT ./does-not-exist 2>err &&
 	test_i18ngrep "\./does-not-exist" err
@@ -210,10 +214,23 @@ test_expect_success MINGW 'verify curlies are quoted properly' '
 	test_cmp expect actual
 '
 
-test_expect_success MINGW 'can spawn with argv[0] containing spaces' '
-	cp "$GIT_BUILD_DIR/t/helper/test-fake-ssh$X" ./ &&
-	test_must_fail "$PWD/test-fake-ssh$X" 2>err &&
-	grep TRASH_DIRECTORY err
+test_expect_success MINGW 'can spawn .bat with argv[0] containing spaces' '
+	bat="$TRASH_DIRECTORY/bat with spaces in name.bat" &&
+
+	# Every .bat invocation will log its arguments to file "out"
+	rm -f out &&
+	echo "echo %* >>out" >"$bat" &&
+
+	# Ask git to invoke .bat; clone will fail due to fake SSH helper
+	test_must_fail env GIT_SSH="$bat" git clone myhost:src ssh-clone &&
+
+	# Spawning .bat can fail if there are two quoted cmd.exe arguments.
+	# .bat itself is first (due to spaces in name), so just one more is
+	# needed to verify. GIT_SSH will invoke .bat multiple times:
+	# 1) -G myhost
+	# 2) myhost "git-upload-pack src"
+	# First invocation will always succeed. Test the second one.
+	grep "git-upload-pack" out
 '
 
 test_done
diff --git a/third_party/git/t/t0064-sha1-array.sh b/third_party/git/t/t0064-sha1-array.sh
index 5dda570b9a..45685af2fd 100755
--- a/third_party/git/t/t0064-sha1-array.sh
+++ b/third_party/git/t/t0064-sha1-array.sh
@@ -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
diff --git a/third_party/git/t/t0067-parse_pathspec_file.sh b/third_party/git/t/t0067-parse_pathspec_file.sh
new file mode 100755
index 0000000000..7bab49f361
--- /dev/null
+++ b/third_party/git/t/t0067-parse_pathspec_file.sh
@@ -0,0 +1,108 @@
+#!/bin/sh
+
+test_description='Test parse_pathspec_file()'
+
+. ./test-lib.sh
+
+test_expect_success 'one item from stdin' '
+	cat >expect <<-\EOF &&
+	fileA.t
+	EOF
+
+	echo fileA.t |
+	test-tool parse-pathspec-file --pathspec-from-file=- >actual &&
+
+	test_cmp expect actual
+'
+
+test_expect_success 'one item from file' '
+	cat >expect <<-\EOF &&
+	fileA.t
+	EOF
+
+	echo fileA.t >list &&
+	test-tool parse-pathspec-file --pathspec-from-file=list >actual &&
+
+	test_cmp expect actual
+'
+
+test_expect_success 'NUL delimiters' '
+	cat >expect <<-\EOF &&
+	fileA.t
+	fileB.t
+	EOF
+
+	printf "fileA.t\0fileB.t\0" |
+	test-tool parse-pathspec-file --pathspec-from-file=- --pathspec-file-nul >actual &&
+
+	test_cmp expect actual
+'
+
+test_expect_success 'LF delimiters' '
+	cat >expect <<-\EOF &&
+	fileA.t
+	fileB.t
+	EOF
+
+	printf "fileA.t\nfileB.t\n" |
+	test-tool parse-pathspec-file --pathspec-from-file=- >actual &&
+
+	test_cmp expect actual
+'
+
+test_expect_success 'no trailing delimiter' '
+	cat >expect <<-\EOF &&
+	fileA.t
+	fileB.t
+	EOF
+
+	printf "fileA.t\nfileB.t" |
+	test-tool parse-pathspec-file --pathspec-from-file=- >actual &&
+
+	test_cmp expect actual
+'
+
+test_expect_success 'CRLF delimiters' '
+	cat >expect <<-\EOF &&
+	fileA.t
+	fileB.t
+	EOF
+
+	printf "fileA.t\r\nfileB.t\r\n" |
+	test-tool parse-pathspec-file --pathspec-from-file=- >actual &&
+
+	test_cmp expect actual
+'
+
+test_expect_success 'quotes' '
+	cat >expect <<-\EOF &&
+	fileA.t
+	EOF
+
+	cat >list <<-\EOF &&
+	"file\101.t"
+	EOF
+
+	test-tool parse-pathspec-file --pathspec-from-file=list >actual &&
+
+	test_cmp expect actual
+'
+
+test_expect_success '--pathspec-file-nul takes quotes literally' '
+	# Note: there is an extra newline because --pathspec-file-nul takes
+	# input \n literally, too
+	cat >expect <<-\EOF &&
+	"file\101.t"
+
+	EOF
+
+	cat >list <<-\EOF &&
+	"file\101.t"
+	EOF
+
+	test-tool parse-pathspec-file --pathspec-from-file=list --pathspec-file-nul >actual &&
+
+	test_cmp expect actual
+'
+
+test_done
diff --git a/third_party/git/t/t0081-line-buffer.sh b/third_party/git/t/t0081-line-buffer.sh
deleted file mode 100755
index ce92e6acad..0000000000
--- a/third_party/git/t/t0081-line-buffer.sh
+++ /dev/null
@@ -1,90 +0,0 @@
-#!/bin/sh
-
-test_description="Test the svn importer's input handling routines.
-
-These tests provide some simple checks that the line_buffer API
-behaves as advertised.
-
-While at it, check that input of newlines and null bytes are handled
-correctly.
-"
-. ./test-lib.sh
-
-test_expect_success 'hello world' '
-	echo ">HELLO" >expect &&
-	test-line-buffer <<-\EOF >actual &&
-	binary 6
-	HELLO
-	EOF
-	test_cmp expect actual
-'
-
-test_expect_success '0-length read, send along greeting' '
-	echo ">HELLO" >expect &&
-	test-line-buffer <<-\EOF >actual &&
-	binary 0
-	copy 6
-	HELLO
-	EOF
-	test_cmp expect actual
-'
-
-test_expect_success !MINGW 'read from file descriptor' '
-	rm -f input &&
-	echo hello >expect &&
-	echo hello >input &&
-	echo copy 6 |
-	test-line-buffer "&4" 4<input >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'skip, copy null byte' '
-	echo Q | q_to_nul >expect &&
-	q_to_nul <<-\EOF | test-line-buffer >actual &&
-	skip 2
-	Q
-	copy 2
-	Q
-	EOF
-	test_cmp expect actual
-'
-
-test_expect_success 'read null byte' '
-	echo ">QhelloQ" | q_to_nul >expect &&
-	q_to_nul <<-\EOF | test-line-buffer >actual &&
-	binary 8
-	QhelloQ
-	EOF
-	test_cmp expect actual
-'
-
-test_expect_success 'long reads are truncated' '
-	echo ">foo" >expect &&
-	test-line-buffer <<-\EOF >actual &&
-	binary 5
-	foo
-	EOF
-	test_cmp expect actual
-'
-
-test_expect_success 'long copies are truncated' '
-	printf "%s\n" ">" foo >expect &&
-	test-line-buffer <<-\EOF >actual &&
-	binary 1
-
-	copy 5
-	foo
-	EOF
-	test_cmp expect actual
-'
-
-test_expect_success 'long binary reads are truncated' '
-	echo ">foo" >expect &&
-	test-line-buffer <<-\EOF >actual &&
-	binary 5
-	foo
-	EOF
-	test_cmp expect actual
-'
-
-test_done
diff --git a/third_party/git/t/t0090-cache-tree.sh b/third_party/git/t/t0090-cache-tree.sh
index ce9a4a5f32..5a633690bf 100755
--- a/third_party/git/t/t0090-cache-tree.sh
+++ b/third_party/git/t/t0090-cache-tree.sh
@@ -21,9 +21,10 @@ generate_expected_cache_tree_rec () {
 	parent="$2" &&
 	# ls-files might have foo/bar, foo/bar/baz, and foo/bar/quux
 	# We want to count only foo because it's the only direct child
-	subtrees=$(git ls-files|grep /|cut -d / -f 1|uniq) &&
+	git ls-files >files &&
+	subtrees=$(grep / files|cut -d / -f 1|uniq) &&
 	subtree_count=$(echo "$subtrees"|awk -v c=0 '$1 != "" {++c} END {print c}') &&
-	entries=$(git ls-files|wc -l) &&
+	entries=$(wc -l <files) &&
 	printf "SHA $dir (%d entries, %d subtrees)\n" "$entries" "$subtree_count" &&
 	for subtree in $subtrees
 	do
diff --git a/third_party/git/t/t0091-bugreport.sh b/third_party/git/t/t0091-bugreport.sh
new file mode 100755
index 0000000000..526304ff95
--- /dev/null
+++ b/third_party/git/t/t0091-bugreport.sh
@@ -0,0 +1,76 @@
+#!/bin/sh
+
+test_description='git bugreport'
+
+. ./test-lib.sh
+
+# Headers "[System Info]" will be followed by a non-empty line if we put some
+# information there; we can make sure all our headers were followed by some
+# information to check if the command was successful.
+HEADER_PATTERN="^\[.*\]$"
+
+check_all_headers_populated () {
+	while read -r line
+	do
+		if test "$(grep "$HEADER_PATTERN" "$line")"
+		then
+			echo "$line"
+			read -r nextline
+			if test -z "$nextline"; then
+				return 1;
+			fi
+		fi
+	done
+}
+
+test_expect_success 'creates a report with content in the right places' '
+	test_when_finished rm git-bugreport-check-headers.txt &&
+	git bugreport -s check-headers &&
+	check_all_headers_populated <git-bugreport-check-headers.txt
+'
+
+test_expect_success 'dies if file with same name as report already exists' '
+	test_when_finished rm git-bugreport-duplicate.txt &&
+	>>git-bugreport-duplicate.txt &&
+	test_must_fail git bugreport --suffix duplicate
+'
+
+test_expect_success '--output-directory puts the report in the provided dir' '
+	test_when_finished rm -fr foo/ &&
+	git bugreport -o foo/ &&
+	test_path_is_file foo/git-bugreport-*
+'
+
+test_expect_success 'incorrect arguments abort with usage' '
+	test_must_fail git bugreport --false 2>output &&
+	test_i18ngrep usage output &&
+	test_path_is_missing git-bugreport-*
+'
+
+test_expect_success 'runs outside of a git dir' '
+	test_when_finished rm non-repo/git-bugreport-* &&
+	nongit git bugreport
+'
+
+test_expect_success 'can create leading directories outside of a git dir' '
+	test_when_finished rm -fr foo/bar/baz &&
+	nongit git bugreport -o foo/bar/baz
+'
+
+test_expect_success 'indicates populated hooks' '
+	test_when_finished rm git-bugreport-hooks.txt &&
+	test_when_finished rm -fr .git/hooks &&
+	rm -fr .git/hooks &&
+	mkdir .git/hooks &&
+	for hook in applypatch-msg prepare-commit-msg.sample
+	do
+		write_script ".git/hooks/$hook" <<-EOF || return 1
+		echo "hook $hook exists"
+		EOF
+	done &&
+	git bugreport -s hooks &&
+	grep applypatch-msg git-bugreport-hooks.txt &&
+	! grep prepare-commit-msg git-bugreport-hooks.txt
+'
+
+test_done
diff --git a/third_party/git/t/t0095-bloom.sh b/third_party/git/t/t0095-bloom.sh
new file mode 100755
index 0000000000..7e4ab1795f
--- /dev/null
+++ b/third_party/git/t/t0095-bloom.sh
@@ -0,0 +1,117 @@
+#!/bin/sh
+
+test_description='Testing the various Bloom filter computations in bloom.c'
+. ./test-lib.sh
+
+test_expect_success 'compute unseeded murmur3 hash for empty string' '
+	cat >expect <<-\EOF &&
+	Murmur3 Hash with seed=0:0x00000000
+	EOF
+	test-tool bloom get_murmur3 "" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'compute unseeded murmur3 hash for test string 1' '
+	cat >expect <<-\EOF &&
+	Murmur3 Hash with seed=0:0x627b0c2c
+	EOF
+	test-tool bloom get_murmur3 "Hello world!" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'compute unseeded murmur3 hash for test string 2' '
+	cat >expect <<-\EOF &&
+	Murmur3 Hash with seed=0:0x2e4ff723
+	EOF
+	test-tool bloom get_murmur3 "The quick brown fox jumps over the lazy dog" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'compute bloom key for empty string' '
+	cat >expect <<-\EOF &&
+	Hashes:0x5615800c|0x5b966560|0x61174ab4|0x66983008|0x6c19155c|0x7199fab0|0x771ae004|
+	Filter_Length:2
+	Filter_Data:11|11|
+	EOF
+	test-tool bloom generate_filter "" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'compute bloom key for whitespace' '
+	cat >expect <<-\EOF &&
+	Hashes:0xf178874c|0x5f3d6eb6|0xcd025620|0x3ac73d8a|0xa88c24f4|0x16510c5e|0x8415f3c8|
+	Filter_Length:2
+	Filter_Data:51|55|
+	EOF
+	test-tool bloom generate_filter " " >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'compute bloom key for test string 1' '
+	cat >expect <<-\EOF &&
+	Hashes:0xb270de9b|0x1bb6f26e|0x84fd0641|0xee431a14|0x57892de7|0xc0cf41ba|0x2a15558d|
+	Filter_Length:2
+	Filter_Data:92|6c|
+	EOF
+	test-tool bloom generate_filter "Hello world!" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'compute bloom key for test string 2' '
+	cat >expect <<-\EOF &&
+	Hashes:0x20ab385b|0xf5237fe2|0xc99bc769|0x9e140ef0|0x728c5677|0x47049dfe|0x1b7ce585|
+	Filter_Length:2
+	Filter_Data:a5|4a|
+	EOF
+	test-tool bloom generate_filter "file.txt" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'get bloom filters for commit with no changes' '
+	git init &&
+	git commit --allow-empty -m "c0" &&
+	cat >expect <<-\EOF &&
+	Filter_Length:1
+	Filter_Data:00|
+	EOF
+	test-tool bloom get_filter_for_commit "$(git rev-parse HEAD)" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'get bloom filter for commit with 10 changes' '
+	rm actual &&
+	rm expect &&
+	mkdir smallDir &&
+	for i in $(test_seq 0 9)
+	do
+		echo $i >smallDir/$i
+	done &&
+	git add smallDir &&
+	git commit -m "commit with 10 changes" &&
+	cat >expect <<-\EOF &&
+	Filter_Length:14
+	Filter_Data:02|b3|c4|a0|34|e7|fe|eb|cb|47|fe|a0|e8|72|
+	EOF
+	test-tool bloom get_filter_for_commit "$(git rev-parse HEAD)" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success EXPENSIVE 'get bloom filter for commit with 513 changes' '
+	rm actual &&
+	rm expect &&
+	mkdir bigDir &&
+	for i in $(test_seq 0 511)
+	do
+		echo $i >bigDir/$i
+	done &&
+	git add bigDir &&
+	git commit -m "commit with 513 changes" &&
+	cat >expect <<-\EOF &&
+	Filter_Length:1
+	Filter_Data:ff|
+	EOF
+	test-tool bloom get_filter_for_commit "$(git rev-parse HEAD)" >actual &&
+	test_cmp expect actual
+'
+
+test_done
diff --git a/third_party/git/t/t0211-trace2-perf.sh b/third_party/git/t/t0211-trace2-perf.sh
index 2c3ad6e8c1..6ee8ee3b67 100755
--- a/third_party/git/t/t0211-trace2-perf.sh
+++ b/third_party/git/t/t0211-trace2-perf.sh
@@ -130,11 +130,11 @@ test_expect_success 'perf stream, child processes' '
 		d0|main|version|||||$V
 		d0|main|start||_T_ABS_|||_EXE_ trace2 004child test-tool trace2 004child test-tool trace2 001return 0
 		d0|main|cmd_name|||||trace2 (trace2)
-		d0|main|child_start||_T_ABS_|||[ch0] class:? argv: test-tool trace2 004child test-tool trace2 001return 0
+		d0|main|child_start||_T_ABS_|||[ch0] class:? argv:[test-tool trace2 004child test-tool trace2 001return 0]
 		d1|main|version|||||$V
 		d1|main|start||_T_ABS_|||_EXE_ trace2 004child test-tool trace2 001return 0
 		d1|main|cmd_name|||||trace2 (trace2/trace2)
-		d1|main|child_start||_T_ABS_|||[ch0] class:? argv: test-tool trace2 001return 0
+		d1|main|child_start||_T_ABS_|||[ch0] class:? argv:[test-tool trace2 001return 0]
 		d2|main|version|||||$V
 		d2|main|start||_T_ABS_|||_EXE_ trace2 001return 0
 		d2|main|cmd_name|||||trace2 (trace2/trace2/trace2)
diff --git a/third_party/git/t/t0212-trace2-event.sh b/third_party/git/t/t0212-trace2-event.sh
index ff5b9cc729..1529155cf0 100755
--- a/third_party/git/t/t0212-trace2-event.sh
+++ b/third_party/git/t/t0212-trace2-event.sh
@@ -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 &&
@@ -265,4 +302,23 @@ test_expect_success JSON_PP 'using global config, event stream, error event' '
 	test_cmp expect actual
 '
 
+test_expect_success 'discard traces when there are too many files' '
+	mkdir trace_target_dir &&
+	test_when_finished "rm -r trace_target_dir" &&
+	(
+		GIT_TRACE2_MAX_FILES=5 &&
+		export GIT_TRACE2_MAX_FILES &&
+		cd trace_target_dir &&
+		test_seq $GIT_TRACE2_MAX_FILES >../expected_filenames.txt &&
+		xargs touch <../expected_filenames.txt &&
+		cd .. &&
+		GIT_TRACE2_EVENT="$(pwd)/trace_target_dir" test-tool trace2 001return 0
+	) &&
+	echo git-trace2-discard >>expected_filenames.txt &&
+	ls trace_target_dir >ls_output.txt &&
+	test_cmp expected_filenames.txt ls_output.txt &&
+	head -n1 trace_target_dir/git-trace2-discard | grep \"event\":\"version\" &&
+	head -n2 trace_target_dir/git-trace2-discard | tail -n1 | grep \"event\":\"too_many_files\"
+'
+
 test_done
diff --git a/third_party/git/t/t0300-credentials.sh b/third_party/git/t/t0300-credentials.sh
index 82eaaea0f4..bc2d74098f 100755
--- a/third_party/git/t/t0300-credentials.sh
+++ b/third_party/git/t/t0300-credentials.sh
@@ -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
 	--
@@ -240,6 +315,102 @@ test_expect_success 'do not match configured credential' '
 	EOF
 '
 
+test_expect_success 'match multiple configured helpers' '
+	test_config credential.helper "verbatim \"\" \"\"" &&
+	test_config credential.https://example.com.helper "$HELPER" &&
+	check fill <<-\EOF
+	protocol=https
+	host=example.com
+	path=repo.git
+	--
+	protocol=https
+	host=example.com
+	username=foo
+	password=bar
+	--
+	verbatim: get
+	verbatim: protocol=https
+	verbatim: host=example.com
+	EOF
+'
+
+test_expect_success 'match multiple configured helpers with URLs' '
+	test_config credential.https://example.com/repo.git.helper "verbatim \"\" \"\"" &&
+	test_config credential.https://example.com.helper "$HELPER" &&
+	check fill <<-\EOF
+	protocol=https
+	host=example.com
+	path=repo.git
+	--
+	protocol=https
+	host=example.com
+	username=foo
+	password=bar
+	--
+	verbatim: get
+	verbatim: protocol=https
+	verbatim: host=example.com
+	EOF
+'
+
+test_expect_success 'match percent-encoded values' '
+	test_config credential.https://example.com/%2566.git.helper "$HELPER" &&
+	check fill <<-\EOF
+	url=https://example.com/%2566.git
+	--
+	protocol=https
+	host=example.com
+	username=foo
+	password=bar
+	--
+	EOF
+'
+
+test_expect_success 'match percent-encoded UTF-8 values in path' '
+	test_config credential.https://example.com.useHttpPath true &&
+	test_config credential.https://example.com/perรบ.git.helper "$HELPER" &&
+	check fill <<-\EOF
+	url=https://example.com/per%C3%BA.git
+	--
+	protocol=https
+	host=example.com
+	path=perรบ.git
+	username=foo
+	password=bar
+	--
+	EOF
+'
+
+test_expect_success 'match percent-encoded values in username' '
+	test_config credential.https://user%2fname@example.com/foo/bar.git.helper "$HELPER" &&
+	check fill <<-\EOF
+	url=https://user%2fname@example.com/foo/bar.git
+	--
+	protocol=https
+	host=example.com
+	username=foo
+	password=bar
+	--
+	EOF
+'
+
+test_expect_success 'fetch with multiple path components' '
+	test_unconfig credential.helper &&
+	test_config credential.https://example.com/foo/repo.git.helper "verbatim foo bar" &&
+	check fill <<-\EOF
+	url=https://example.com/foo/repo.git
+	--
+	protocol=https
+	host=example.com
+	username=foo
+	password=bar
+	--
+	verbatim: get
+	verbatim: protocol=https
+	verbatim: host=example.com
+	EOF
+'
+
 test_expect_success 'pull username from config' '
 	test_config credential.https://example.com.username foo &&
 	check fill <<-\EOF
@@ -255,6 +426,63 @@ test_expect_success 'pull username from config' '
 	EOF
 '
 
+test_expect_success 'honors username from URL over helper (URL)' '
+	test_config credential.https://example.com.username bob &&
+	test_config credential.https://example.com.helper "verbatim \"\" bar" &&
+	check fill <<-\EOF
+	url=https://alice@example.com
+	--
+	protocol=https
+	host=example.com
+	username=alice
+	password=bar
+	--
+	verbatim: get
+	verbatim: protocol=https
+	verbatim: host=example.com
+	verbatim: username=alice
+	EOF
+'
+
+test_expect_success 'honors username from URL over helper (components)' '
+	test_config credential.https://example.com.username bob &&
+	test_config credential.https://example.com.helper "verbatim \"\" bar" &&
+	check fill <<-\EOF
+	protocol=https
+	host=example.com
+	username=alice
+	--
+	protocol=https
+	host=example.com
+	username=alice
+	password=bar
+	--
+	verbatim: get
+	verbatim: protocol=https
+	verbatim: host=example.com
+	verbatim: username=alice
+	EOF
+'
+
+test_expect_success 'last matching username wins' '
+	test_config credential.https://example.com/path.git.username bob &&
+	test_config credential.https://example.com.username alice &&
+	test_config credential.https://example.com.helper "verbatim \"\" bar" &&
+	check fill <<-\EOF
+	url=https://example.com/path.git
+	--
+	protocol=https
+	host=example.com
+	username=alice
+	password=bar
+	--
+	verbatim: get
+	verbatim: protocol=https
+	verbatim: host=example.com
+	verbatim: username=alice
+	EOF
+'
+
 test_expect_success 'http paths can be part of context' '
 	check fill "verbatim foo bar" <<-\EOF &&
 	protocol=https
@@ -289,23 +517,179 @@ test_expect_success 'http paths can be part of context' '
 	EOF
 '
 
+test_expect_success 'context uses urlmatch' '
+	test_config "credential.https://*.org.useHttpPath" true &&
+	check fill "verbatim foo bar" <<-\EOF
+	protocol=https
+	host=example.org
+	path=foo.git
+	--
+	protocol=https
+	host=example.org
+	path=foo.git
+	username=foo
+	password=bar
+	--
+	verbatim: get
+	verbatim: protocol=https
+	verbatim: host=example.org
+	verbatim: path=foo.git
+	EOF
+'
+
 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_expect_success 'credential config with partial URLs' '
+	echo "echo password=yep" | write_script git-credential-yep &&
+	test_write_lines url=https://user@example.com/repo.git >stdin &&
+	for partial in \
+		example.com \
+		user@example.com \
+		https:// \
+		https://example.com \
+		https://example.com/ \
+		https://user@example.com \
+		https://user@example.com/ \
+		https://example.com/repo.git \
+		https://user@example.com/repo.git \
+		/repo.git
+	do
+		git -c credential.$partial.helper=yep \
+			credential fill <stdin >stdout &&
+		grep yep stdout ||
+		return 1
+	done &&
+
+	for partial in \
+		dont.use.this \
+		http:// \
+		/repo
+	do
+		git -c credential.$partial.helper=yep \
+			credential fill <stdin >stdout &&
+		! grep yep stdout ||
+		return 1
+	done &&
+
+	git -c credential.$partial.helper=yep \
+		-c credential.with%0anewline.username=uh-oh \
+		credential fill <stdin >stdout 2>stderr &&
+	test_i18ngrep "skipping credential lookup for key" stderr
+'
+
 test_done
diff --git a/third_party/git/t/t0302-credential-store.sh b/third_party/git/t/t0302-credential-store.sh
index d6b54e8c65..716bf1af9f 100755
--- a/third_party/git/t/t0302-credential-store.sh
+++ b/third_party/git/t/t0302-credential-store.sh
@@ -107,7 +107,6 @@ test_expect_success 'store: if both xdg and home files exist, only store in home
 	test_must_be_empty "$HOME/.config/git/credentials"
 '
 
-
 test_expect_success 'erase: erase matching credentials from both xdg and home files' '
 	echo "https://home-user:home-pass@example.com" >"$HOME/.git-credentials" &&
 	mkdir -p "$HOME/.config/git" &&
@@ -120,4 +119,94 @@ test_expect_success 'erase: erase matching credentials from both xdg and home fi
 	test_must_be_empty "$HOME/.config/git/credentials"
 '
 
+invalid_credential_test() {
+	test_expect_success "get: ignore credentials without $1 as invalid" '
+		echo "$2" >"$HOME/.git-credentials" &&
+		check fill store <<-\EOF
+		protocol=https
+		host=example.com
+		--
+		protocol=https
+		host=example.com
+		username=askpass-username
+		password=askpass-password
+		--
+		askpass: Username for '\''https://example.com'\'':
+		askpass: Password for '\''https://askpass-username@example.com'\'':
+		--
+		EOF
+	'
+}
+
+invalid_credential_test "scheme" ://user:pass@example.com
+invalid_credential_test "valid host/path" https://user:pass@
+invalid_credential_test "username/password" https://pass@example.com
+
+test_expect_success 'get: credentials with DOS line endings are invalid' '
+	printf "https://user:pass@example.com\r\n" >"$HOME/.git-credentials" &&
+	check fill store <<-\EOF
+	protocol=https
+	host=example.com
+	--
+	protocol=https
+	host=example.com
+	username=askpass-username
+	password=askpass-password
+	--
+	askpass: Username for '\''https://example.com'\'':
+	askpass: Password for '\''https://askpass-username@example.com'\'':
+	--
+	EOF
+'
+
+test_expect_success 'get: credentials with path and DOS line endings are valid' '
+	printf "https://user:pass@example.com/repo.git\r\n" >"$HOME/.git-credentials" &&
+	check fill store <<-\EOF
+	url=https://example.com/repo.git
+	--
+	protocol=https
+	host=example.com
+	username=user
+	password=pass
+	--
+	EOF
+'
+
+test_expect_success 'get: credentials with DOS line endings are invalid if path is relevant' '
+	printf "https://user:pass@example.com/repo.git\r\n" >"$HOME/.git-credentials" &&
+	test_config credential.useHttpPath true &&
+	check fill store <<-\EOF
+	url=https://example.com/repo.git
+	--
+	protocol=https
+	host=example.com
+	path=repo.git
+	username=askpass-username
+	password=askpass-password
+	--
+	askpass: Username for '\''https://example.com/repo.git'\'':
+	askpass: Password for '\''https://askpass-username@example.com/repo.git'\'':
+	--
+	EOF
+'
+
+test_expect_success 'get: store file can contain empty/bogus lines' '
+	echo "" >"$HOME/.git-credentials" &&
+	q_to_tab <<-\CREDENTIAL >>"$HOME/.git-credentials" &&
+	#comment
+	Q
+	https://user:pass@example.com
+	CREDENTIAL
+	check fill store <<-\EOF
+	protocol=https
+	host=example.com
+	--
+	protocol=https
+	host=example.com
+	username=user
+	password=pass
+	--
+	EOF
+'
+
 test_done
diff --git a/third_party/git/t/t0410-partial-clone.sh b/third_party/git/t/t0410-partial-clone.sh
index 5bd892f2f7..584a039b85 100755
--- a/third_party/git/t/t0410-partial-clone.sh
+++ b/third_party/git/t/t0410-partial-clone.sh
@@ -26,10 +26,44 @@ promise_and_delete () {
 test_expect_success 'extensions.partialclone without filter' '
 	test_create_repo server &&
 	git clone --filter="blob:none" "file://$(pwd)/server" client &&
-	git -C client config --unset core.partialclonefilter &&
+	git -C client config --unset remote.origin.partialclonefilter &&
 	git -C client fetch origin
 '
 
+test_expect_success 'convert shallow clone to partial clone' '
+	rm -fr server client &&
+	test_create_repo server &&
+	test_commit -C server my_commit 1 &&
+	test_commit -C server my_commit2 1 &&
+	git clone --depth=1 "file://$(pwd)/server" client &&
+	git -C client fetch --unshallow --filter="blob:none" &&
+	test_cmp_config -C client true remote.origin.promisor &&
+	test_cmp_config -C client blob:none remote.origin.partialclonefilter &&
+	test_cmp_config -C client 1 core.repositoryformatversion
+'
+
+test_expect_success SHA1 'convert to partial clone with noop extension' '
+	rm -fr server client &&
+	test_create_repo server &&
+	test_commit -C server my_commit 1 &&
+	test_commit -C server my_commit2 1 &&
+	git clone --depth=1 "file://$(pwd)/server" client &&
+	test_cmp_config -C client 0 core.repositoryformatversion &&
+	git -C client config extensions.noop true &&
+	git -C client fetch --unshallow --filter="blob:none"
+'
+
+test_expect_success SHA1 'converting to partial clone fails with unrecognized extension' '
+	rm -fr server client &&
+	test_create_repo server &&
+	test_commit -C server my_commit 1 &&
+	test_commit -C server my_commit2 1 &&
+	git clone --depth=1 "file://$(pwd)/server" client &&
+	test_cmp_config -C client 0 core.repositoryformatversion &&
+	git -C client config extensions.nonsense true &&
+	test_must_fail git -C client fetch --unshallow --filter="blob:none"
+'
+
 test_expect_success 'missing reflog object, but promised by a commit, passes fsck' '
 	rm -rf repo &&
 	test_create_repo repo &&
@@ -149,7 +183,7 @@ test_expect_success 'missing CLI object, but promised, passes fsck' '
 '
 
 test_expect_success 'fetching of missing objects' '
-	rm -rf repo &&
+	rm -rf repo err &&
 	test_create_repo server &&
 	test_commit -C server foo &&
 	git -C server repack -a -d --write-bitmap-index &&
@@ -160,14 +194,18 @@ test_expect_success 'fetching of missing objects' '
 
 	git -C repo config core.repositoryformatversion 1 &&
 	git -C repo config extensions.partialclone "origin" &&
-	git -C repo cat-file -p "$HASH" &&
+	git -C repo cat-file -p "$HASH" 2>err &&
+
+	# Ensure that no spurious FETCH_HEAD messages are written
+	! grep FETCH_HEAD err &&
 
 	# Ensure that the .promisor file is written, and check that its
 	# associated packfile contains the object
 	ls repo/.git/objects/pack/pack-*.promisor >promisorlist &&
 	test_line_count = 1 promisorlist &&
-	IDX=$(cat promisorlist | sed "s/promisor$/idx/") &&
-	git verify-pack --verbose "$IDX" | grep "$HASH"
+	IDX=$(sed "s/promisor$/idx/" promisorlist) &&
+	git verify-pack --verbose "$IDX" >out &&
+	grep "$HASH" out
 '
 
 test_expect_success 'fetching of missing objects works with ref-in-want enabled' '
@@ -179,11 +217,58 @@ test_expect_success 'fetching of missing objects works with ref-in-want enabled'
 	rm -rf repo/.git/objects/* &&
 	rm -f trace &&
 	GIT_TRACE_PACKET="$(pwd)/trace" git -C repo cat-file -p "$HASH" &&
-	grep "git< fetch=.*ref-in-want" trace
+	grep "fetch< fetch=.*ref-in-want" trace
+'
+
+test_expect_success 'fetching of missing objects from another promisor remote' '
+	git clone "file://$(pwd)/server" server2 &&
+	test_commit -C server2 bar &&
+	git -C server2 repack -a -d --write-bitmap-index &&
+	HASH2=$(git -C server2 rev-parse bar) &&
+
+	git -C repo remote add server2 "file://$(pwd)/server2" &&
+	git -C repo config remote.server2.promisor true &&
+	git -C repo cat-file -p "$HASH2" &&
+
+	git -C repo fetch server2 &&
+	rm -rf repo/.git/objects/* &&
+	git -C repo cat-file -p "$HASH2" &&
+
+	# Ensure that the .promisor file is written, and check that its
+	# associated packfile contains the object
+	ls repo/.git/objects/pack/pack-*.promisor >promisorlist &&
+	test_line_count = 1 promisorlist &&
+	IDX=$(sed "s/promisor$/idx/" promisorlist) &&
+	git verify-pack --verbose "$IDX" >out &&
+	grep "$HASH2" out
+'
+
+test_expect_success 'fetching of missing objects configures a promisor remote' '
+	git clone "file://$(pwd)/server" server3 &&
+	test_commit -C server3 baz &&
+	git -C server3 repack -a -d --write-bitmap-index &&
+	HASH3=$(git -C server3 rev-parse baz) &&
+	git -C server3 config uploadpack.allowfilter 1 &&
+
+	rm repo/.git/objects/pack/pack-*.promisor &&
+
+	git -C repo remote add server3 "file://$(pwd)/server3" &&
+	git -C repo fetch --filter="blob:none" server3 $HASH3 &&
+
+	test_cmp_config -C repo true remote.server3.promisor &&
+
+	# Ensure that the .promisor file is written, and check that its
+	# associated packfile contains the object
+	ls repo/.git/objects/pack/pack-*.promisor >promisorlist &&
+	test_line_count = 1 promisorlist &&
+	IDX=$(sed "s/promisor$/idx/" promisorlist) &&
+	git verify-pack --verbose "$IDX" >out &&
+	grep "$HASH3" out
 '
 
 test_expect_success 'fetching of missing blobs works' '
-	rm -rf server repo &&
+	rm -rf server server2 repo &&
+	rm -rf server server3 repo &&
 	test_create_repo server &&
 	test_commit -C server foo &&
 	git -C server repack -a -d --write-bitmap-index &&
@@ -234,7 +319,7 @@ test_expect_success 'rev-list stops traversal at missing and promised commit' '
 
 	git -C repo config core.repositoryformatversion 1 &&
 	git -C repo config extensions.partialclone "arbitrary string" &&
-	GIT_TEST_COMMIT_GRAPH=0 git -C repo rev-list --exclude-promisor-objects --objects bar >out &&
+	GIT_TEST_COMMIT_GRAPH=0 git -C repo -c core.commitGraph=false rev-list --exclude-promisor-objects --objects bar >out &&
 	grep $(git -C repo rev-parse bar) out &&
 	! grep $FOO out
 '
@@ -381,6 +466,19 @@ test_expect_success 'rev-list dies for missing objects on cmd line' '
 	done
 '
 
+test_expect_success 'single promisor remote can be re-initialized gracefully' '
+	# ensure one promisor is in the promisors list
+	rm -rf repo &&
+	test_create_repo repo &&
+	test_create_repo other &&
+	git -C repo remote add foo "file://$(pwd)/other" &&
+	git -C repo config remote.foo.promisor true &&
+	git -C repo config extensions.partialclone foo &&
+
+	# reinitialize the promisors list
+	git -C repo fetch --filter=blob:none foo
+'
+
 test_expect_success 'gc repacks promisor objects separately from non-promisor objects' '
 	rm -rf repo &&
 	test_create_repo repo &&
@@ -492,6 +590,20 @@ test_expect_success 'gc stops traversal when a missing but promised object is re
 	! grep "$TREE_HASH" out
 '
 
+test_expect_success 'do not fetch when checking existence of tree we construct ourselves' '
+	rm -rf repo &&
+	test_create_repo repo &&
+	test_commit -C repo base &&
+	test_commit -C repo side1 &&
+	git -C repo checkout base &&
+	test_commit -C repo side2 &&
+
+	git -C repo config core.repositoryformatversion 1 &&
+	git -C repo config extensions.partialclone "arbitrary string" &&
+
+	git -C repo cherry-pick side1
+'
+
 . "$TEST_DIRECTORY"/lib-httpd.sh
 start_httpd
 
@@ -514,8 +626,12 @@ test_expect_success 'fetching of missing objects from an HTTP server' '
 	# associated packfile contains the object
 	ls repo/.git/objects/pack/pack-*.promisor >promisorlist &&
 	test_line_count = 1 promisorlist &&
-	IDX=$(cat promisorlist | sed "s/promisor$/idx/") &&
-	git verify-pack --verbose "$IDX" | grep "$HASH"
+	IDX=$(sed "s/promisor$/idx/" promisorlist) &&
+	git verify-pack --verbose "$IDX" >out &&
+	grep "$HASH" out
 '
 
+# DO NOT add non-httpd-specific tests here, because the last part of this
+# test script is only executed when httpd is available and enabled.
+
 test_done
diff --git a/third_party/git/t/t0500-progress-display.sh b/third_party/git/t/t0500-progress-display.sh
new file mode 100755
index 0000000000..1ed1df351c
--- /dev/null
+++ b/third_party/git/t/t0500-progress-display.sh
@@ -0,0 +1,312 @@
+#!/bin/sh
+
+test_description='progress display'
+
+. ./test-lib.sh
+
+show_cr () {
+	tr '\015' Q | sed -e "s/Q/<CR>\\$LF/g"
+}
+
+test_expect_success 'simple progress display' '
+	cat >expect <<-\EOF &&
+	Working hard: 1<CR>
+	Working hard: 2<CR>
+	Working hard: 5<CR>
+	Working hard: 5, done.
+	EOF
+
+	cat >in <<-\EOF &&
+	update
+	progress 1
+	update
+	progress 2
+	progress 3
+	progress 4
+	update
+	progress 5
+	EOF
+	test-tool progress "Working hard" <in 2>stderr &&
+
+	show_cr <stderr >out &&
+	test_i18ncmp expect out
+'
+
+test_expect_success 'progress display with total' '
+	cat >expect <<-\EOF &&
+	Working hard:  33% (1/3)<CR>
+	Working hard:  66% (2/3)<CR>
+	Working hard: 100% (3/3)<CR>
+	Working hard: 100% (3/3), done.
+	EOF
+
+	cat >in <<-\EOF &&
+	progress 1
+	progress 2
+	progress 3
+	EOF
+	test-tool progress --total=3 "Working hard" <in 2>stderr &&
+
+	show_cr <stderr >out &&
+	test_i18ncmp expect out
+'
+
+test_expect_success 'progress display breaks long lines #1' '
+	sed -e "s/Z$//" >expect <<\EOF &&
+Working hard.......2.........3.........4.........5.........6:   0% (100/100000)<CR>
+Working hard.......2.........3.........4.........5.........6:   1% (1000/100000)<CR>
+Working hard.......2.........3.........4.........5.........6:                   Z
+   10% (10000/100000)<CR>
+  100% (100000/100000)<CR>
+  100% (100000/100000), done.
+EOF
+
+	cat >in <<-\EOF &&
+	progress 100
+	progress 1000
+	progress 10000
+	progress 100000
+	EOF
+	test-tool progress --total=100000 \
+		"Working hard.......2.........3.........4.........5.........6" \
+		<in 2>stderr &&
+
+	show_cr <stderr >out &&
+	test_i18ncmp expect out
+'
+
+test_expect_success 'progress display breaks long lines #2' '
+	# Note: we do not need that many spaces after the title to cover up
+	# the last line before breaking the progress line.
+	sed -e "s/Z$//" >expect <<\EOF &&
+Working hard.......2.........3.........4.........5.........6:   0% (1/100000)<CR>
+Working hard.......2.........3.........4.........5.........6:   0% (2/100000)<CR>
+Working hard.......2.........3.........4.........5.........6:                   Z
+   10% (10000/100000)<CR>
+  100% (100000/100000)<CR>
+  100% (100000/100000), done.
+EOF
+
+	cat >in <<-\EOF &&
+	update
+	progress 1
+	update
+	progress 2
+	progress 10000
+	progress 100000
+	EOF
+	test-tool progress --total=100000 \
+		"Working hard.......2.........3.........4.........5.........6" \
+		<in 2>stderr &&
+
+	show_cr <stderr >out &&
+	test_i18ncmp expect out
+'
+
+test_expect_success 'progress display breaks long lines #3 - even the first is too long' '
+	# Note: we do not actually need any spaces at the end of the title
+	# line, because there is no previous progress line to cover up.
+	sed -e "s/Z$//" >expect <<\EOF &&
+Working hard.......2.........3.........4.........5.........6:                   Z
+   25% (25000/100000)<CR>
+   50% (50000/100000)<CR>
+   75% (75000/100000)<CR>
+  100% (100000/100000)<CR>
+  100% (100000/100000), done.
+EOF
+
+	cat >in <<-\EOF &&
+	progress 25000
+	progress 50000
+	progress 75000
+	progress 100000
+	EOF
+	test-tool progress --total=100000 \
+		"Working hard.......2.........3.........4.........5.........6" \
+		<in 2>stderr &&
+
+	show_cr <stderr >out &&
+	test_i18ncmp expect out
+'
+
+test_expect_success 'progress display breaks long lines #4 - title line matches terminal width' '
+	cat >expect <<\EOF &&
+Working hard.......2.........3.........4.........5.........6.........7.........:
+   25% (25000/100000)<CR>
+   50% (50000/100000)<CR>
+   75% (75000/100000)<CR>
+  100% (100000/100000)<CR>
+  100% (100000/100000), done.
+EOF
+
+	cat >in <<-\EOF &&
+	progress 25000
+	progress 50000
+	progress 75000
+	progress 100000
+	EOF
+	test-tool progress --total=100000 \
+		"Working hard.......2.........3.........4.........5.........6.........7........." \
+		<in 2>stderr &&
+
+	show_cr <stderr >out &&
+	test_i18ncmp expect out
+'
+
+# Progress counter goes backwards, this should not happen in practice.
+test_expect_success 'progress shortens - crazy caller' '
+	cat >expect <<-\EOF &&
+	Working hard:  10% (100/1000)<CR>
+	Working hard:  20% (200/1000)<CR>
+	Working hard:   0% (1/1000)  <CR>
+	Working hard: 100% (1000/1000)<CR>
+	Working hard: 100% (1000/1000), done.
+	EOF
+
+	cat >in <<-\EOF &&
+	progress 100
+	progress 200
+	progress 1
+	progress 1000
+	EOF
+	test-tool progress --total=1000 "Working hard" <in 2>stderr &&
+
+	show_cr <stderr >out &&
+	test_i18ncmp expect out
+'
+
+test_expect_success 'progress display with throughput' '
+	cat >expect <<-\EOF &&
+	Working hard: 10<CR>
+	Working hard: 20, 200.00 KiB | 100.00 KiB/s<CR>
+	Working hard: 30, 300.00 KiB | 100.00 KiB/s<CR>
+	Working hard: 40, 400.00 KiB | 100.00 KiB/s<CR>
+	Working hard: 40, 400.00 KiB | 100.00 KiB/s, done.
+	EOF
+
+	cat >in <<-\EOF &&
+	throughput 102400 1000
+	update
+	progress 10
+	throughput 204800 2000
+	update
+	progress 20
+	throughput 307200 3000
+	update
+	progress 30
+	throughput 409600 4000
+	update
+	progress 40
+	EOF
+	test-tool progress "Working hard" <in 2>stderr &&
+
+	show_cr <stderr >out &&
+	test_i18ncmp expect out
+'
+
+test_expect_success 'progress display with throughput and total' '
+	cat >expect <<-\EOF &&
+	Working hard:  25% (10/40)<CR>
+	Working hard:  50% (20/40), 200.00 KiB | 100.00 KiB/s<CR>
+	Working hard:  75% (30/40), 300.00 KiB | 100.00 KiB/s<CR>
+	Working hard: 100% (40/40), 400.00 KiB | 100.00 KiB/s<CR>
+	Working hard: 100% (40/40), 400.00 KiB | 100.00 KiB/s, done.
+	EOF
+
+	cat >in <<-\EOF &&
+	throughput 102400 1000
+	progress 10
+	throughput 204800 2000
+	progress 20
+	throughput 307200 3000
+	progress 30
+	throughput 409600 4000
+	progress 40
+	EOF
+	test-tool progress --total=40 "Working hard" <in 2>stderr &&
+
+	show_cr <stderr >out &&
+	test_i18ncmp expect out
+'
+
+test_expect_success 'cover up after throughput shortens' '
+	cat >expect <<-\EOF &&
+	Working hard: 1<CR>
+	Working hard: 2, 800.00 KiB | 400.00 KiB/s<CR>
+	Working hard: 3, 1.17 MiB | 400.00 KiB/s  <CR>
+	Working hard: 4, 1.56 MiB | 400.00 KiB/s<CR>
+	Working hard: 4, 1.56 MiB | 400.00 KiB/s, done.
+	EOF
+
+	cat >in <<-\EOF &&
+	throughput 409600 1000
+	update
+	progress 1
+	throughput 819200 2000
+	update
+	progress 2
+	throughput 1228800 3000
+	update
+	progress 3
+	throughput 1638400 4000
+	update
+	progress 4
+	EOF
+	test-tool progress "Working hard" <in 2>stderr &&
+
+	show_cr <stderr >out &&
+	test_i18ncmp expect out
+'
+
+test_expect_success 'cover up after throughput shortens a lot' '
+	cat >expect <<-\EOF &&
+	Working hard: 1<CR>
+	Working hard: 2, 1000.00 KiB | 1000.00 KiB/s<CR>
+	Working hard: 3, 3.00 MiB | 1.50 MiB/s      <CR>
+	Working hard: 3, 3.00 MiB | 1024.00 KiB/s, done.
+	EOF
+
+	cat >in <<-\EOF &&
+	throughput 1 1000
+	update
+	progress 1
+	throughput 1024000 2000
+	update
+	progress 2
+	throughput 3145728 3000
+	update
+	progress 3
+	EOF
+	test-tool progress "Working hard" <in 2>stderr &&
+
+	show_cr <stderr >out &&
+	test_i18ncmp expect out
+'
+
+test_expect_success 'progress generates traces' '
+	cat >in <<-\EOF &&
+	throughput 102400 1000
+	update
+	progress 10
+	throughput 204800 2000
+	update
+	progress 20
+	throughput 307200 3000
+	update
+	progress 30
+	throughput 409600 4000
+	update
+	progress 40
+	EOF
+
+	GIT_TRACE2_EVENT="$(pwd)/trace.event" test-tool progress --total=40 \
+		"Working hard" <in 2>stderr &&
+
+	# t0212/parse_events.perl intentionally omits regions and data.
+	grep -e "region_enter" -e "\"category\":\"progress\"" trace.event &&
+	grep -e "region_leave" -e "\"category\":\"progress\"" trace.event &&
+	grep "\"key\":\"total_objects\",\"value\":\"40\"" trace.event &&
+	grep "\"key\":\"total_bytes\",\"value\":\"409600\"" trace.event
+'
+
+test_done
diff --git a/third_party/git/t/t1006-cat-file.sh b/third_party/git/t/t1006-cat-file.sh
index 43c4be1e5e..2f501d2dc9 100755
--- a/third_party/git/t/t1006-cat-file.sh
+++ b/third_party/git/t/t1006-cat-file.sh
@@ -140,8 +140,6 @@ test_expect_success '--batch-check without %(rest) considers whole line' '
 	test_cmp expect actual
 '
 
-test_oid_init
-
 tree_sha1=$(git write-tree)
 tree_size=$(($(test_oid rawsz) + 13))
 tree_pretty_content="100644 blob $hello_sha1	hello"
diff --git a/third_party/git/t/t1011-read-tree-sparse-checkout.sh b/third_party/git/t/t1011-read-tree-sparse-checkout.sh
index ba71b159ba..140f459977 100755
--- a/third_party/git/t/t1011-read-tree-sparse-checkout.sh
+++ b/third_party/git/t/t1011-read-tree-sparse-checkout.sh
@@ -74,13 +74,19 @@ test_expect_success 'read-tree --no-sparse-checkout with empty .git/info/sparse-
 test_expect_success 'read-tree with empty .git/info/sparse-checkout' '
 	git config core.sparsecheckout true &&
 	echo >.git/info/sparse-checkout &&
-	read_tree_u_must_fail -m -u HEAD &&
+	read_tree_u_must_succeed -m -u HEAD &&
 	git ls-files --stage >result &&
 	test_cmp expected result &&
 	git ls-files -t >result &&
+	cat >expected.swt <<-\EOF &&
+	S init.t
+	S sub/added
+	S sub/addedtoo
+	S subsub/added
+	EOF
 	test_cmp expected.swt result &&
-	test -f init.t &&
-	test -f sub/added
+	! test -f init.t &&
+	! test -f sub/added
 '
 
 test_expect_success 'match directories with trailing slash' '
@@ -215,7 +221,6 @@ test_expect_success 'read-tree adds to worktree, dirty case' '
 '
 
 test_expect_success 'index removal and worktree narrowing at the same time' '
-	>empty &&
 	echo init.t >.git/info/sparse-checkout &&
 	echo sub/added >>.git/info/sparse-checkout &&
 	git checkout -f top &&
@@ -223,7 +228,7 @@ test_expect_success 'index removal and worktree narrowing at the same time' '
 	git checkout removed &&
 	git ls-files sub/added >result &&
 	test ! -f sub/added &&
-	test_cmp empty result
+	test_must_be_empty result
 '
 
 test_expect_success 'read-tree --reset removes outside worktree' '
@@ -234,18 +239,19 @@ test_expect_success 'read-tree --reset removes outside worktree' '
 	test_must_be_empty result
 '
 
-test_expect_success 'print errors when failed to update worktree' '
+test_expect_success 'print warnings when some worktree updates disabled' '
 	echo sub >.git/info/sparse-checkout &&
 	git checkout -f init &&
 	mkdir sub &&
 	touch sub/added sub/addedtoo &&
-	test_must_fail git checkout top 2>actual &&
+	# Use -q to suppress "Previous HEAD position" and "Head is now at" msgs
+	git checkout -q top 2>actual &&
 	cat >expected <<\EOF &&
-error: The following untracked working tree files would be overwritten by checkout:
+warning: The following paths were already present and thus not updated despite sparse patterns:
 	sub/added
 	sub/addedtoo
-Please move or remove them before you switch branches.
-Aborting
+
+After fixing the above paths, you may want to run `git sparse-checkout reapply`.
 EOF
 	test_i18ncmp expected actual
 '
diff --git a/third_party/git/t/t1013-read-tree-submodule.sh b/third_party/git/t/t1013-read-tree-submodule.sh
index 91a6fafcb4..b6df7444c0 100755
--- a/third_party/git/t/t1013-read-tree-submodule.sh
+++ b/third_party/git/t/t1013-read-tree-submodule.sh
@@ -12,8 +12,8 @@ test_submodule_switch_recursing_with_args "read-tree -u -m"
 
 test_submodule_forced_switch_recursing_with_args "read-tree -u --reset"
 
-test_submodule_switch "git read-tree -u -m"
+test_submodule_switch "read-tree -u -m"
 
-test_submodule_forced_switch "git read-tree -u --reset"
+test_submodule_forced_switch "read-tree -u --reset"
 
 test_done
diff --git a/third_party/git/t/t1014-read-tree-confusing.sh b/third_party/git/t/t1014-read-tree-confusing.sh
index 2f5a25d503..da3376b3bb 100755
--- a/third_party/git/t/t1014-read-tree-confusing.sh
+++ b/third_party/git/t/t1014-read-tree-confusing.sh
@@ -49,6 +49,7 @@ git~1
 .git.SPACE .git.{space}
 .\\\\.GIT\\\\foobar backslashes
 .git\\\\foobar backslashes2
+.git...:alternate-stream
 EOF
 
 test_expect_success 'utf-8 paths allowed with core.protectHFS off' '
diff --git a/third_party/git/t/t1050-large.sh b/third_party/git/t/t1050-large.sh
index dcb4dbba67..61e89a8071 100755
--- a/third_party/git/t/t1050-large.sh
+++ b/third_party/git/t/t1050-large.sh
@@ -53,7 +53,8 @@ test_expect_success 'add a large file or two' '
 	for p in .git/objects/pack/pack-*.pack
 	do
 		count=$(( $count + 1 ))
-		if test -f "$p" && idx=${p%.pack}.idx && test -f "$idx"
+		if test_path_is_file "$p" &&
+		   idx=${p%.pack}.idx && test_path_is_file "$idx"
 		then
 			continue
 		fi
@@ -63,9 +64,9 @@ test_expect_success 'add a large file or two' '
 	test $count = 1 &&
 	cnt=$(git show-index <"$idx" | wc -l) &&
 	test $cnt = 2 &&
-	for l in .git/objects/??/??????????????????????????????????????
+	for l in .git/objects/$OIDPATH_REGEX
 	do
-		test -f "$l" || continue
+		test_path_is_file "$l" || continue
 		bad=t
 	done &&
 	test -z "$bad" &&
@@ -76,7 +77,8 @@ test_expect_success 'add a large file or two' '
 	for p in .git/objects/pack/pack-*.pack
 	do
 		count=$(( $count + 1 ))
-		if test -f "$p" && idx=${p%.pack}.idx && test -f "$idx"
+		if test_path_is_file "$p" &&
+		   idx=${p%.pack}.idx && test_path_is_file "$idx"
 		then
 			continue
 		fi
@@ -111,7 +113,7 @@ test_expect_success 'packsize limit' '
 		count=0 &&
 		for pi in .git/objects/pack/pack-*.idx
 		do
-			test -f "$pi" && count=$(( $count + 1 ))
+			test_path_is_file "$pi" && count=$(( $count + 1 ))
 		done &&
 		test $count = 2 &&
 
@@ -175,7 +177,8 @@ test_expect_success 'git-show a large file' '
 
 test_expect_success 'index-pack' '
 	git clone file://"$(pwd)"/.git foo &&
-	GIT_DIR=non-existent git index-pack --strict --verify foo/.git/objects/pack/*.pack
+	GIT_DIR=non-existent git index-pack --object-format=$(test_oid algo) \
+		--strict --verify foo/.git/objects/pack/*.pack
 '
 
 test_expect_success 'repack' '
@@ -194,15 +197,15 @@ test_expect_success 'pack-objects with large loose object' '
 	test_cmp huge actual
 '
 
-test_expect_success 'tar achiving' '
+test_expect_success 'tar archiving' '
 	git archive --format=tar HEAD >/dev/null
 '
 
-test_expect_success 'zip achiving, store only' '
+test_expect_success 'zip archiving, store only' '
 	git archive --format=zip -0 HEAD >/dev/null
 '
 
-test_expect_success 'zip achiving, deflate' '
+test_expect_success 'zip archiving, deflate' '
 	git archive --format=zip HEAD >/dev/null
 '
 
diff --git a/third_party/git/t/t1090-sparse-checkout-scope.sh b/third_party/git/t/t1090-sparse-checkout-scope.sh
index 40cc004326..f35a73dd20 100755
--- a/third_party/git/t/t1090-sparse-checkout-scope.sh
+++ b/third_party/git/t/t1090-sparse-checkout-scope.sh
@@ -63,7 +63,6 @@ test_expect_success 'in partial clone, sparse checkout only fetches needed blobs
 	git -C server commit -m message &&
 
 	test_config -C client core.sparsecheckout 1 &&
-	test_config -C client extensions.partialclone origin &&
 	echo "!/*" >client/.git/info/sparse-checkout &&
 	echo "/a" >>client/.git/info/sparse-checkout &&
 	git -C client fetch --filter=blob:none origin &&
diff --git a/third_party/git/t/t1091-sparse-checkout-builtin.sh b/third_party/git/t/t1091-sparse-checkout-builtin.sh
new file mode 100755
index 0000000000..84acfc48b6
--- /dev/null
+++ b/third_party/git/t/t1091-sparse-checkout-builtin.sh
@@ -0,0 +1,629 @@
+#!/bin/sh
+
+test_description='sparse checkout builtin tests'
+
+. ./test-lib.sh
+
+list_files() {
+	# Do not replace this with 'ls "$1"', as "ls" with BSD-lineage
+	# enables "-A" by default for root and ends up including ".git" and
+	# such in its output. (Note, though, that running the test suite as
+	# root is generally not recommended.)
+	(cd "$1" && printf '%s\n' *)
+}
+
+check_files() {
+	list_files "$1" >actual &&
+	shift &&
+	printf "%s\n" $@ >expect &&
+	test_cmp expect actual
+}
+
+test_expect_success 'setup' '
+	git init repo &&
+	(
+		cd repo &&
+		echo "initial" >a &&
+		mkdir folder1 folder2 deep &&
+		mkdir deep/deeper1 deep/deeper2 &&
+		mkdir deep/deeper1/deepest &&
+		cp a folder1 &&
+		cp a folder2 &&
+		cp a deep &&
+		cp a deep/deeper1 &&
+		cp a deep/deeper2 &&
+		cp a deep/deeper1/deepest &&
+		git add . &&
+		git commit -m "initial commit"
+	)
+'
+
+test_expect_success 'git sparse-checkout list (empty)' '
+	git -C repo sparse-checkout list >list 2>err &&
+	test_must_be_empty list &&
+	test_i18ngrep "this worktree is not sparse (sparse-checkout file may not exist)" err
+'
+
+test_expect_success 'git sparse-checkout list (populated)' '
+	test_when_finished rm -f repo/.git/info/sparse-checkout &&
+	cat >repo/.git/info/sparse-checkout <<-\EOF &&
+	/folder1/*
+	/deep/
+	**/a
+	!*bin*
+	EOF
+	cp repo/.git/info/sparse-checkout expect &&
+	git -C repo sparse-checkout list >list &&
+	test_cmp expect list
+'
+
+test_expect_success 'git sparse-checkout init' '
+	git -C repo sparse-checkout init &&
+	cat >expect <<-\EOF &&
+	/*
+	!/*/
+	EOF
+	test_cmp expect repo/.git/info/sparse-checkout &&
+	test_cmp_config -C repo true core.sparsecheckout &&
+	check_files repo a
+'
+
+test_expect_success 'git sparse-checkout list after init' '
+	git -C repo sparse-checkout list >actual &&
+	cat >expect <<-\EOF &&
+	/*
+	!/*/
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'init with existing sparse-checkout' '
+	echo "*folder*" >> repo/.git/info/sparse-checkout &&
+	git -C repo sparse-checkout init &&
+	cat >expect <<-\EOF &&
+	/*
+	!/*/
+	*folder*
+	EOF
+	test_cmp expect repo/.git/info/sparse-checkout &&
+	check_files repo a folder1 folder2
+'
+
+test_expect_success 'clone --sparse' '
+	git clone --sparse "file://$(pwd)/repo" clone &&
+	git -C clone sparse-checkout list >actual &&
+	cat >expect <<-\EOF &&
+	/*
+	!/*/
+	EOF
+	test_cmp expect actual &&
+	check_files clone a
+'
+
+test_expect_success 'interaction with clone --no-checkout (unborn index)' '
+	git clone --no-checkout "file://$(pwd)/repo" clone_no_checkout &&
+	git -C clone_no_checkout sparse-checkout init --cone &&
+	git -C clone_no_checkout sparse-checkout set folder1 &&
+
+	git -C clone_no_checkout sparse-checkout list >actual &&
+	cat >expect <<-\EOF &&
+	folder1
+	EOF
+	test_cmp expect actual &&
+
+	# nothing checked out, expect "No such file or directory"
+	! ls clone_no_checkout/* >actual &&
+	test_must_be_empty actual &&
+	test_path_is_missing clone_no_checkout/.git/index &&
+
+	# No branch is checked out until we manually switch to one
+	git -C clone_no_checkout switch master &&
+	test_path_is_file clone_no_checkout/.git/index &&
+	check_files clone_no_checkout a folder1
+'
+
+test_expect_success 'set enables config' '
+	git init empty-config &&
+	(
+		cd empty-config &&
+		test_commit test file &&
+		test_path_is_missing .git/config.worktree &&
+		git sparse-checkout set nothing &&
+		test_path_is_file .git/config.worktree &&
+		test_cmp_config true core.sparseCheckout
+	)
+'
+
+test_expect_success 'set sparse-checkout using builtin' '
+	git -C repo sparse-checkout set "/*" "!/*/" "*folder*" &&
+	cat >expect <<-\EOF &&
+	/*
+	!/*/
+	*folder*
+	EOF
+	git -C repo sparse-checkout list >actual &&
+	test_cmp expect actual &&
+	test_cmp expect repo/.git/info/sparse-checkout &&
+	check_files repo a folder1 folder2
+'
+
+test_expect_success 'set sparse-checkout using --stdin' '
+	cat >expect <<-\EOF &&
+	/*
+	!/*/
+	/folder1/
+	/folder2/
+	EOF
+	git -C repo sparse-checkout set --stdin <expect &&
+	git -C repo sparse-checkout list >actual &&
+	test_cmp expect actual &&
+	test_cmp expect repo/.git/info/sparse-checkout &&
+	check_files repo "a folder1 folder2"
+'
+
+test_expect_success 'add to sparse-checkout' '
+	cat repo/.git/info/sparse-checkout >expect &&
+	cat >add <<-\EOF &&
+	pattern1
+	/folder1/
+	pattern2
+	EOF
+	cat add >>expect &&
+	git -C repo sparse-checkout add --stdin <add &&
+	git -C repo sparse-checkout list >actual &&
+	test_cmp expect actual &&
+	test_cmp expect repo/.git/info/sparse-checkout &&
+	check_files repo "a folder1 folder2"
+'
+
+test_expect_success 'cone mode: match patterns' '
+	git -C repo config --worktree core.sparseCheckoutCone true &&
+	rm -rf repo/a repo/folder1 repo/folder2 &&
+	git -C repo read-tree -mu HEAD 2>err &&
+	test_i18ngrep ! "disabling cone patterns" err &&
+	git -C repo reset --hard &&
+	check_files repo a folder1 folder2
+'
+
+test_expect_success 'cone mode: warn on bad pattern' '
+	test_when_finished mv sparse-checkout repo/.git/info/ &&
+	cp repo/.git/info/sparse-checkout . &&
+	echo "!/deep/deeper/*" >>repo/.git/info/sparse-checkout &&
+	git -C repo read-tree -mu HEAD 2>err &&
+	test_i18ngrep "unrecognized negative pattern" err
+'
+
+test_expect_success 'sparse-checkout disable' '
+	test_when_finished rm -rf repo/.git/info/sparse-checkout &&
+	git -C repo sparse-checkout disable &&
+	test_path_is_file repo/.git/info/sparse-checkout &&
+	git -C repo config --list >config &&
+	test_must_fail git config core.sparseCheckout &&
+	check_files repo a deep folder1 folder2
+'
+
+test_expect_success 'cone mode: init and set' '
+	git -C repo sparse-checkout init --cone &&
+	git -C repo config --list >config &&
+	test_i18ngrep "core.sparsecheckoutcone=true" config &&
+	list_files repo >dir  &&
+	echo a >expect &&
+	test_cmp expect dir &&
+	git -C repo sparse-checkout set deep/deeper1/deepest/ 2>err &&
+	test_must_be_empty err &&
+	check_files repo a deep &&
+	check_files repo/deep a deeper1 &&
+	check_files repo/deep/deeper1 a deepest &&
+	cat >expect <<-\EOF &&
+	/*
+	!/*/
+	/deep/
+	!/deep/*/
+	/deep/deeper1/
+	!/deep/deeper1/*/
+	/deep/deeper1/deepest/
+	EOF
+	test_cmp expect repo/.git/info/sparse-checkout &&
+	git -C repo sparse-checkout set --stdin 2>err <<-\EOF &&
+	folder1
+	folder2
+	EOF
+	test_must_be_empty err &&
+	check_files repo a folder1 folder2
+'
+
+test_expect_success 'cone mode: list' '
+	cat >expect <<-\EOF &&
+	folder1
+	folder2
+	EOF
+	git -C repo sparse-checkout set --stdin <expect &&
+	git -C repo sparse-checkout list >actual 2>err &&
+	test_must_be_empty err &&
+	test_cmp expect actual
+'
+
+test_expect_success 'cone mode: set with nested folders' '
+	git -C repo sparse-checkout set deep deep/deeper1/deepest 2>err &&
+	test_line_count = 0 err &&
+	cat >expect <<-\EOF &&
+	/*
+	!/*/
+	/deep/
+	EOF
+	test_cmp repo/.git/info/sparse-checkout expect
+'
+
+test_expect_success 'cone mode: add independent path' '
+	git -C repo sparse-checkout set deep/deeper1 &&
+	git -C repo sparse-checkout add folder1 &&
+	cat >expect <<-\EOF &&
+	/*
+	!/*/
+	/deep/
+	!/deep/*/
+	/deep/deeper1/
+	/folder1/
+	EOF
+	test_cmp expect repo/.git/info/sparse-checkout &&
+	check_files repo a deep folder1
+'
+
+test_expect_success 'cone mode: add sibling path' '
+	git -C repo sparse-checkout set deep/deeper1 &&
+	git -C repo sparse-checkout add deep/deeper2 &&
+	cat >expect <<-\EOF &&
+	/*
+	!/*/
+	/deep/
+	!/deep/*/
+	/deep/deeper1/
+	/deep/deeper2/
+	EOF
+	test_cmp expect repo/.git/info/sparse-checkout &&
+	check_files repo a deep
+'
+
+test_expect_success 'cone mode: add parent path' '
+	git -C repo sparse-checkout set deep/deeper1 folder1 &&
+	git -C repo sparse-checkout add deep &&
+	cat >expect <<-\EOF &&
+	/*
+	!/*/
+	/deep/
+	/folder1/
+	EOF
+	test_cmp expect repo/.git/info/sparse-checkout &&
+	check_files repo a deep folder1
+'
+
+test_expect_success 'not-up-to-date does not block rest of sparsification' '
+	test_when_finished git -C repo sparse-checkout disable &&
+	test_when_finished git -C repo reset --hard &&
+	git -C repo sparse-checkout set deep &&
+
+	echo update >repo/deep/deeper2/a &&
+	cp repo/.git/info/sparse-checkout expect &&
+	test_write_lines "!/deep/*/" "/deep/deeper1/" >>expect &&
+
+	git -C repo sparse-checkout set deep/deeper1 2>err &&
+
+	test_i18ngrep "The following paths are not up to date" err &&
+	test_cmp expect repo/.git/info/sparse-checkout &&
+	check_files repo/deep a deeper1 deeper2 &&
+	check_files repo/deep/deeper1 a deepest &&
+	check_files repo/deep/deeper1/deepest a &&
+	check_files repo/deep/deeper2 a
+'
+
+test_expect_success 'revert to old sparse-checkout on empty update' '
+	git init empty-test &&
+	(
+		echo >file &&
+		git add file &&
+		git commit -m "test" &&
+		git sparse-checkout set nothing 2>err &&
+		test_i18ngrep ! "Sparse checkout leaves no entry on working directory" err &&
+		test_i18ngrep ! ".git/index.lock" err &&
+		git sparse-checkout set file
+	)
+'
+
+test_expect_success 'fail when lock is taken' '
+	test_when_finished rm -rf repo/.git/info/sparse-checkout.lock &&
+	touch repo/.git/info/sparse-checkout.lock &&
+	test_must_fail git -C repo sparse-checkout set deep 2>err &&
+	test_i18ngrep "Unable to create .*\.lock" err
+'
+
+test_expect_success '.gitignore should not warn about cone mode' '
+	git -C repo config --worktree core.sparseCheckoutCone true &&
+	echo "**/bin/*" >repo/.gitignore &&
+	git -C repo reset --hard 2>err &&
+	test_i18ngrep ! "disabling cone patterns" err
+'
+
+test_expect_success 'sparse-checkout (init|set|disable) warns with dirty status' '
+	git clone repo dirty &&
+	echo dirty >dirty/folder1/a &&
+
+	git -C dirty sparse-checkout init 2>err &&
+	test_i18ngrep "warning.*The following paths are not up to date" err &&
+
+	git -C dirty sparse-checkout set /folder2/* /deep/deeper1/* 2>err &&
+	test_i18ngrep "warning.*The following paths are not up to date" err &&
+	test_path_is_file dirty/folder1/a &&
+
+	git -C dirty sparse-checkout disable 2>err &&
+	test_must_be_empty err &&
+
+	git -C dirty reset --hard &&
+	git -C dirty sparse-checkout init &&
+	git -C dirty sparse-checkout set /folder2/* /deep/deeper1/* &&
+	test_path_is_missing dirty/folder1/a &&
+	git -C dirty sparse-checkout disable &&
+	test_path_is_file dirty/folder1/a
+'
+
+test_expect_success 'sparse-checkout (init|set|disable) warns with unmerged status' '
+	git clone repo unmerged &&
+
+	cat >input <<-EOF &&
+	0 $ZERO_OID	folder1/a
+	100644 $(git -C unmerged rev-parse HEAD:folder1/a) 1	folder1/a
+	EOF
+	git -C unmerged update-index --index-info <input &&
+
+	git -C unmerged sparse-checkout init 2>err &&
+	test_i18ngrep "warning.*The following paths are unmerged" err &&
+
+	git -C unmerged sparse-checkout set /folder2/* /deep/deeper1/* 2>err &&
+	test_i18ngrep "warning.*The following paths are unmerged" err &&
+	test_path_is_file dirty/folder1/a &&
+
+	git -C unmerged sparse-checkout disable 2>err &&
+	test_i18ngrep "warning.*The following paths are unmerged" err &&
+
+	git -C unmerged reset --hard &&
+	git -C unmerged sparse-checkout init &&
+	git -C unmerged sparse-checkout set /folder2/* /deep/deeper1/* &&
+	git -C unmerged sparse-checkout disable
+'
+
+test_expect_success 'sparse-checkout reapply' '
+	git clone repo tweak &&
+
+	echo dirty >tweak/deep/deeper2/a &&
+
+	cat >input <<-EOF &&
+	0 $ZERO_OID	folder1/a
+	100644 $(git -C tweak rev-parse HEAD:folder1/a) 1	folder1/a
+	EOF
+	git -C tweak update-index --index-info <input &&
+
+	git -C tweak sparse-checkout init --cone 2>err &&
+	test_i18ngrep "warning.*The following paths are not up to date" err &&
+	test_i18ngrep "warning.*The following paths are unmerged" err &&
+
+	git -C tweak sparse-checkout set folder2 deep/deeper1 2>err &&
+	test_i18ngrep "warning.*The following paths are not up to date" err &&
+	test_i18ngrep "warning.*The following paths are unmerged" err &&
+
+	git -C tweak sparse-checkout reapply 2>err &&
+	test_i18ngrep "warning.*The following paths are not up to date" err &&
+	test_path_is_file tweak/deep/deeper2/a &&
+	test_i18ngrep "warning.*The following paths are unmerged" err &&
+	test_path_is_file tweak/folder1/a &&
+
+	git -C tweak checkout HEAD deep/deeper2/a &&
+	git -C tweak sparse-checkout reapply 2>err &&
+	test_i18ngrep ! "warning.*The following paths are not up to date" err &&
+	test_path_is_missing tweak/deep/deeper2/a &&
+	test_i18ngrep "warning.*The following paths are unmerged" err &&
+	test_path_is_file tweak/folder1/a &&
+
+	git -C tweak add folder1/a &&
+	git -C tweak sparse-checkout reapply 2>err &&
+	test_must_be_empty err &&
+	test_path_is_missing tweak/deep/deeper2/a &&
+	test_path_is_missing tweak/folder1/a &&
+
+	git -C tweak sparse-checkout disable
+'
+
+test_expect_success 'cone mode: set with core.ignoreCase=true' '
+	rm repo/.git/info/sparse-checkout &&
+	git -C repo sparse-checkout init --cone &&
+	git -C repo -c core.ignoreCase=true sparse-checkout set folder1 &&
+	cat >expect <<-\EOF &&
+	/*
+	!/*/
+	/folder1/
+	EOF
+	test_cmp expect repo/.git/info/sparse-checkout &&
+	check_files repo a folder1
+'
+
+test_expect_success 'interaction with submodules' '
+	git clone repo super &&
+	(
+		cd super &&
+		mkdir modules &&
+		git submodule add ../repo modules/child &&
+		git add . &&
+		git commit -m "add submodule" &&
+		git sparse-checkout init --cone &&
+		git sparse-checkout set folder1
+	) &&
+	check_files super a folder1 modules &&
+	check_files super/modules/child a deep folder1 folder2
+'
+
+test_expect_success 'different sparse-checkouts with worktrees' '
+	git -C repo worktree add --detach ../worktree &&
+	check_files worktree "a deep folder1 folder2" &&
+	git -C worktree sparse-checkout init --cone &&
+	git -C repo sparse-checkout set folder1 &&
+	git -C worktree sparse-checkout set deep/deeper1 &&
+	check_files repo a folder1 &&
+	check_files worktree a deep
+'
+
+test_expect_success 'set using filename keeps file on-disk' '
+	git -C repo sparse-checkout set a deep &&
+	cat >expect <<-\EOF &&
+	/*
+	!/*/
+	/a/
+	/deep/
+	EOF
+	test_cmp expect repo/.git/info/sparse-checkout &&
+	check_files repo a deep
+'
+
+check_read_tree_errors () {
+	REPO=$1
+	FILES=$2
+	ERRORS=$3
+	git -C $REPO -c core.sparseCheckoutCone=false read-tree -mu HEAD 2>err &&
+	test_must_be_empty err &&
+	check_files $REPO "$FILES" &&
+	git -C $REPO read-tree -mu HEAD 2>err &&
+	if test -z "$ERRORS"
+	then
+		test_must_be_empty err
+	else
+		test_i18ngrep "$ERRORS" err
+	fi &&
+	check_files $REPO $FILES
+}
+
+test_expect_success 'pattern-checks: /A/**' '
+	cat >repo/.git/info/sparse-checkout <<-\EOF &&
+	/*
+	!/*/
+	/folder1/**
+	EOF
+	check_read_tree_errors repo "a folder1" "disabling cone pattern matching"
+'
+
+test_expect_success 'pattern-checks: /A/**/B/' '
+	cat >repo/.git/info/sparse-checkout <<-\EOF &&
+	/*
+	!/*/
+	/deep/**/deepest
+	EOF
+	check_read_tree_errors repo "a deep" "disabling cone pattern matching" &&
+	check_files repo/deep "deeper1" &&
+	check_files repo/deep/deeper1 "deepest"
+'
+
+test_expect_success 'pattern-checks: too short' '
+	cat >repo/.git/info/sparse-checkout <<-\EOF &&
+	/*
+	!/*/
+	/
+	EOF
+	check_read_tree_errors repo "a" "disabling cone pattern matching"
+'
+test_expect_success 'pattern-checks: not too short' '
+	cat >repo/.git/info/sparse-checkout <<-\EOF &&
+	/*
+	!/*/
+	/b/
+	EOF
+	git -C repo read-tree -mu HEAD 2>err &&
+	test_must_be_empty err &&
+	check_files repo a
+'
+
+test_expect_success 'pattern-checks: trailing "*"' '
+	cat >repo/.git/info/sparse-checkout <<-\EOF &&
+	/*
+	!/*/
+	/a*
+	EOF
+	check_read_tree_errors repo "a" "disabling cone pattern matching"
+'
+
+test_expect_success 'pattern-checks: starting "*"' '
+	cat >repo/.git/info/sparse-checkout <<-\EOF &&
+	/*
+	!/*/
+	*eep/
+	EOF
+	check_read_tree_errors repo "a deep" "disabling cone pattern matching"
+'
+
+test_expect_success 'pattern-checks: contained glob characters' '
+	for c in "[a]" "\\" "?" "*"
+	do
+		cat >repo/.git/info/sparse-checkout <<-EOF &&
+		/*
+		!/*/
+		something$c-else/
+		EOF
+		check_read_tree_errors repo "a" "disabling cone pattern matching"
+	done
+'
+
+test_expect_success BSLASHPSPEC 'pattern-checks: escaped characters' '
+	git clone repo escaped &&
+	TREEOID=$(git -C escaped rev-parse HEAD:folder1) &&
+	NEWTREE=$(git -C escaped mktree <<-EOF
+	$(git -C escaped ls-tree HEAD)
+	040000 tree $TREEOID	zbad\\dir
+	040000 tree $TREEOID	zdoes*exist
+	040000 tree $TREEOID	zglob[!a]?
+	EOF
+	) &&
+	COMMIT=$(git -C escaped commit-tree $NEWTREE -p HEAD) &&
+	git -C escaped reset --hard $COMMIT &&
+	check_files escaped "a deep folder1 folder2 zbad\\dir zdoes*exist" zglob[!a]? &&
+	git -C escaped sparse-checkout init --cone &&
+	git -C escaped sparse-checkout set zbad\\dir/bogus "zdoes*not*exist" "zdoes*exist" "zglob[!a]?" &&
+	cat >expect <<-\EOF &&
+	/*
+	!/*/
+	/zbad\\dir/
+	!/zbad\\dir/*/
+	/zbad\\dir/bogus/
+	/zdoes\*exist/
+	/zdoes\*not\*exist/
+	/zglob\[!a]\?/
+	EOF
+	test_cmp expect escaped/.git/info/sparse-checkout &&
+	check_read_tree_errors escaped "a zbad\\dir zdoes*exist zglob[!a]?" &&
+	git -C escaped ls-tree -d --name-only HEAD >list-expect &&
+	git -C escaped sparse-checkout set --stdin <list-expect &&
+	cat >expect <<-\EOF &&
+	/*
+	!/*/
+	/deep/
+	/folder1/
+	/folder2/
+	/zbad\\dir/
+	/zdoes\*exist/
+	/zglob\[!a]\?/
+	EOF
+	test_cmp expect escaped/.git/info/sparse-checkout &&
+	check_files escaped "a deep folder1 folder2 zbad\\dir zdoes*exist" zglob[!a]? &&
+	git -C escaped sparse-checkout list >list-actual &&
+	test_cmp list-expect list-actual
+'
+
+test_expect_success MINGW 'cone mode replaces backslashes with slashes' '
+	git -C repo sparse-checkout set deep\\deeper1 &&
+	cat >expect <<-\EOF &&
+	/*
+	!/*/
+	/deep/
+	!/deep/*/
+	/deep/deeper1/
+	EOF
+	test_cmp expect repo/.git/info/sparse-checkout &&
+	check_files repo a deep &&
+	check_files repo/deep a deeper1
+'
+
+test_done
diff --git a/third_party/git/t/t1300-config.sh b/third_party/git/t/t1300-config.sh
index 428177c390..825d9a184f 100755
--- a/third_party/git/t/t1300-config.sh
+++ b/third_party/git/t/t1300-config.sh
@@ -1191,47 +1191,47 @@ test_expect_success 'old-fashioned settings are case insensitive' '
 	test_when_finished "rm -f testConfig testConfig_expect testConfig_actual" &&
 
 	cat >testConfig_actual <<-EOF &&
-		[V.A]
-		r = value1
+	[V.A]
+	r = value1
 	EOF
 	q_to_tab >testConfig_expect <<-EOF &&
-		[V.A]
-		Qr = value2
+	[V.A]
+	Qr = value2
 	EOF
 	git config -f testConfig_actual "v.a.r" value2 &&
 	test_cmp testConfig_expect testConfig_actual &&
 
 	cat >testConfig_actual <<-EOF &&
-		[V.A]
-		r = value1
+	[V.A]
+	r = value1
 	EOF
 	q_to_tab >testConfig_expect <<-EOF &&
-		[V.A]
-		QR = value2
+	[V.A]
+	QR = value2
 	EOF
 	git config -f testConfig_actual "V.a.R" value2 &&
 	test_cmp testConfig_expect testConfig_actual &&
 
 	cat >testConfig_actual <<-EOF &&
-		[V.A]
-		r = value1
+	[V.A]
+	r = value1
 	EOF
 	q_to_tab >testConfig_expect <<-EOF &&
-		[V.A]
-		r = value1
-		Qr = value2
+	[V.A]
+	r = value1
+	Qr = value2
 	EOF
 	git config -f testConfig_actual "V.A.r" value2 &&
 	test_cmp testConfig_expect testConfig_actual &&
 
 	cat >testConfig_actual <<-EOF &&
-		[V.A]
-		r = value1
+	[V.A]
+	r = value1
 	EOF
 	q_to_tab >testConfig_expect <<-EOF &&
-		[V.A]
-		r = value1
-		Qr = value2
+	[V.A]
+	r = value1
+	Qr = value2
 	EOF
 	git config -f testConfig_actual "v.A.r" value2 &&
 	test_cmp testConfig_expect testConfig_actual
@@ -1241,26 +1241,26 @@ test_expect_success 'setting different case sensitive subsections ' '
 	test_when_finished "rm -f testConfig testConfig_expect testConfig_actual" &&
 
 	cat >testConfig_actual <<-EOF &&
-		[V "A"]
-		R = v1
-		[K "E"]
-		Y = v1
-		[a "b"]
-		c = v1
-		[d "e"]
-		f = v1
+	[V "A"]
+	R = v1
+	[K "E"]
+	Y = v1
+	[a "b"]
+	c = v1
+	[d "e"]
+	f = v1
 	EOF
 	q_to_tab >testConfig_expect <<-EOF &&
-		[V "A"]
-		Qr = v2
-		[K "E"]
-		Qy = v2
-		[a "b"]
-		Qc = v2
-		[d "e"]
-		f = v1
-		[d "E"]
-		Qf = v2
+	[V "A"]
+	Qr = v2
+	[K "E"]
+	Qy = v2
+	[a "b"]
+	Qc = v2
+	[d "e"]
+	f = v1
+	[d "E"]
+	Qf = v2
 	EOF
 	# exact match
 	git config -f testConfig_actual a.b.c v2 &&
@@ -1294,26 +1294,25 @@ test_expect_success 'git -c is not confused by empty environment' '
 	GIT_CONFIG_PARAMETERS="" git -c x.one=1 config --list
 '
 
-sq="'"
 test_expect_success 'detect bogus GIT_CONFIG_PARAMETERS' '
 	cat >expect <<-\EOF &&
 	env.one one
 	env.two two
 	EOF
-	GIT_CONFIG_PARAMETERS="${sq}env.one=one${sq} ${sq}env.two=two${sq}" \
+	GIT_CONFIG_PARAMETERS="${SQ}env.one=one${SQ} ${SQ}env.two=two${SQ}" \
 		git config --get-regexp "env.*" >actual &&
 	test_cmp expect actual &&
 
 	cat >expect <<-EOF &&
-	env.one one${sq}
+	env.one one${SQ}
 	env.two two
 	EOF
-	GIT_CONFIG_PARAMETERS="${sq}env.one=one${sq}\\$sq$sq$sq ${sq}env.two=two${sq}" \
+	GIT_CONFIG_PARAMETERS="${SQ}env.one=one${SQ}\\$SQ$SQ$SQ ${SQ}env.two=two${SQ}" \
 		git config --get-regexp "env.*" >actual &&
 	test_cmp expect actual &&
 
 	test_must_fail env \
-		GIT_CONFIG_PARAMETERS="${sq}env.one=one${sq}\\$sq ${sq}env.two=two${sq}" \
+		GIT_CONFIG_PARAMETERS="${SQ}env.one=one${SQ}\\$SQ ${SQ}env.two=two${SQ}" \
 		git config --get-regexp "env.*"
 '
 
@@ -1409,6 +1408,8 @@ test_expect_success 'urlmatch favors more specific URLs' '
 		cookieFile = /tmp/wildcard.txt
 	[http "https://*.example.com/wildcardwithsubdomain"]
 		cookieFile = /tmp/wildcardwithsubdomain.txt
+	[http "https://*.example.*"]
+		cookieFile = /tmp/multiwildcard.txt
 	[http "https://trailing.example.com"]
 		cookieFile = /tmp/trailing.txt
 	[http "https://user@*.example.com/"]
@@ -1455,6 +1456,10 @@ test_expect_success 'urlmatch favors more specific URLs' '
 
 	echo http.cookiefile /tmp/sub.txt >expect &&
 	git config --get-urlmatch HTTP https://user@sub.example.com >actual &&
+	test_cmp expect actual &&
+
+	echo http.cookiefile /tmp/multiwildcard.txt >expect &&
+	git config --get-urlmatch HTTP https://wildcard.example.org >actual &&
 	test_cmp expect actual
 '
 
@@ -1623,40 +1628,40 @@ test_expect_success 'set up --show-origin tests' '
 	INCLUDE_DIR="$HOME/include" &&
 	mkdir -p "$INCLUDE_DIR" &&
 	cat >"$INCLUDE_DIR"/absolute.include <<-\EOF &&
-		[user]
-			absolute = include
+	[user]
+		absolute = include
 	EOF
 	cat >"$INCLUDE_DIR"/relative.include <<-\EOF &&
-		[user]
-			relative = include
+	[user]
+		relative = include
 	EOF
 	cat >"$HOME"/.gitconfig <<-EOF &&
-		[user]
-			global = true
-			override = global
-		[include]
-			path = "$INCLUDE_DIR/absolute.include"
+	[user]
+		global = true
+		override = global
+	[include]
+		path = "$INCLUDE_DIR/absolute.include"
 	EOF
 	cat >.git/config <<-\EOF
-		[user]
-			local = true
-			override = local
-		[include]
-			path = ../include/relative.include
+	[user]
+		local = true
+		override = local
+	[include]
+		path = ../include/relative.include
 	EOF
 '
 
 test_expect_success '--show-origin with --list' '
 	cat >expect <<-EOF &&
-		file:$HOME/.gitconfig	user.global=true
-		file:$HOME/.gitconfig	user.override=global
-		file:$HOME/.gitconfig	include.path=$INCLUDE_DIR/absolute.include
-		file:$INCLUDE_DIR/absolute.include	user.absolute=include
-		file:.git/config	user.local=true
-		file:.git/config	user.override=local
-		file:.git/config	include.path=../include/relative.include
-		file:.git/../include/relative.include	user.relative=include
-		command line:	user.cmdline=true
+	file:$HOME/.gitconfig	user.global=true
+	file:$HOME/.gitconfig	user.override=global
+	file:$HOME/.gitconfig	include.path=$INCLUDE_DIR/absolute.include
+	file:$INCLUDE_DIR/absolute.include	user.absolute=include
+	file:.git/config	user.local=true
+	file:.git/config	user.override=local
+	file:.git/config	include.path=../include/relative.include
+	file:.git/../include/relative.include	user.relative=include
+	command line:	user.cmdline=true
 	EOF
 	git -c user.cmdline=true config --list --show-origin >output &&
 	test_cmp expect output
@@ -1664,16 +1669,16 @@ test_expect_success '--show-origin with --list' '
 
 test_expect_success '--show-origin with --list --null' '
 	cat >expect <<-EOF &&
-		file:$HOME/.gitconfigQuser.global
-		trueQfile:$HOME/.gitconfigQuser.override
-		globalQfile:$HOME/.gitconfigQinclude.path
-		$INCLUDE_DIR/absolute.includeQfile:$INCLUDE_DIR/absolute.includeQuser.absolute
-		includeQfile:.git/configQuser.local
-		trueQfile:.git/configQuser.override
-		localQfile:.git/configQinclude.path
-		../include/relative.includeQfile:.git/../include/relative.includeQuser.relative
-		includeQcommand line:Quser.cmdline
-		trueQ
+	file:$HOME/.gitconfigQuser.global
+	trueQfile:$HOME/.gitconfigQuser.override
+	globalQfile:$HOME/.gitconfigQinclude.path
+	$INCLUDE_DIR/absolute.includeQfile:$INCLUDE_DIR/absolute.includeQuser.absolute
+	includeQfile:.git/configQuser.local
+	trueQfile:.git/configQuser.override
+	localQfile:.git/configQinclude.path
+	../include/relative.includeQfile:.git/../include/relative.includeQuser.relative
+	includeQcommand line:Quser.cmdline
+	trueQ
 	EOF
 	git -c user.cmdline=true config --null --list --show-origin >output.raw &&
 	nul_to_q <output.raw >output &&
@@ -1685,9 +1690,9 @@ test_expect_success '--show-origin with --list --null' '
 
 test_expect_success '--show-origin with single file' '
 	cat >expect <<-\EOF &&
-		file:.git/config	user.local=true
-		file:.git/config	user.override=local
-		file:.git/config	include.path=../include/relative.include
+	file:.git/config	user.local=true
+	file:.git/config	user.override=local
+	file:.git/config	include.path=../include/relative.include
 	EOF
 	git config --local --list --show-origin >output &&
 	test_cmp expect output
@@ -1695,8 +1700,8 @@ test_expect_success '--show-origin with single file' '
 
 test_expect_success '--show-origin with --get-regexp' '
 	cat >expect <<-EOF &&
-		file:$HOME/.gitconfig	user.global true
-		file:.git/config	user.local true
+	file:$HOME/.gitconfig	user.global true
+	file:.git/config	user.local true
 	EOF
 	git config --show-origin --get-regexp "user\.[g|l].*" >output &&
 	test_cmp expect output
@@ -1704,31 +1709,36 @@ test_expect_success '--show-origin with --get-regexp' '
 
 test_expect_success '--show-origin getting a single key' '
 	cat >expect <<-\EOF &&
-		file:.git/config	local
+	file:.git/config	local
 	EOF
 	git config --show-origin user.override >output &&
 	test_cmp expect output
 '
 
 test_expect_success 'set up custom config file' '
-	CUSTOM_CONFIG_FILE="file\" (dq) and spaces.conf" &&
+	CUSTOM_CONFIG_FILE="custom.conf" &&
 	cat >"$CUSTOM_CONFIG_FILE" <<-\EOF
-		[user]
-			custom = true
+	[user]
+		custom = true
 	EOF
 '
 
+test_expect_success !MINGW 'set up custom config file with special name characters' '
+	WEIRDLY_NAMED_FILE="file\" (dq) and spaces.conf" &&
+	cp "$CUSTOM_CONFIG_FILE" "$WEIRDLY_NAMED_FILE"
+'
+
 test_expect_success !MINGW '--show-origin escape special file name characters' '
 	cat >expect <<-\EOF &&
-		file:"file\" (dq) and spaces.conf"	user.custom=true
+	file:"file\" (dq) and spaces.conf"	user.custom=true
 	EOF
-	git config --file "$CUSTOM_CONFIG_FILE" --show-origin --list >output &&
+	git config --file "$WEIRDLY_NAMED_FILE" --show-origin --list >output &&
 	test_cmp expect output
 '
 
 test_expect_success '--show-origin stdin' '
 	cat >expect <<-\EOF &&
-		standard input:	user.custom=true
+	standard input:	user.custom=true
 	EOF
 	git config --file - --show-origin --list <"$CUSTOM_CONFIG_FILE" >output &&
 	test_cmp expect output
@@ -1736,11 +1746,11 @@ test_expect_success '--show-origin stdin' '
 
 test_expect_success '--show-origin stdin with file include' '
 	cat >"$INCLUDE_DIR"/stdin.include <<-EOF &&
-		[user]
-			stdin = include
+	[user]
+		stdin = include
 	EOF
 	cat >expect <<-EOF &&
-		file:$INCLUDE_DIR/stdin.include	include
+	file:$INCLUDE_DIR/stdin.include	include
 	EOF
 	echo "[include]path=\"$INCLUDE_DIR\"/stdin.include" |
 	git config --show-origin --includes --file - user.stdin >output &&
@@ -1748,18 +1758,18 @@ test_expect_success '--show-origin stdin with file include' '
 	test_cmp expect output
 '
 
-test_expect_success !MINGW '--show-origin blob' '
+test_expect_success '--show-origin blob' '
 	blob=$(git hash-object -w "$CUSTOM_CONFIG_FILE") &&
 	cat >expect <<-EOF &&
-		blob:$blob	user.custom=true
+	blob:$blob	user.custom=true
 	EOF
 	git config --blob=$blob --show-origin --list >output &&
 	test_cmp expect output
 '
 
-test_expect_success !MINGW '--show-origin blob ref' '
+test_expect_success '--show-origin blob ref' '
 	cat >expect <<-\EOF &&
-		blob:"master:file\" (dq) and spaces.conf"	user.custom=true
+	blob:master:custom.conf	user.custom=true
 	EOF
 	git add "$CUSTOM_CONFIG_FILE" &&
 	git commit -m "new config file" &&
@@ -1767,12 +1777,74 @@ test_expect_success !MINGW '--show-origin blob ref' '
 	test_cmp expect output
 '
 
-test_expect_success '--local requires a repo' '
-	# we expect 128 to ensure that we do not simply
-	# fail to find anything and return code "1"
-	test_expect_code 128 nongit git config --local foo.bar
+test_expect_success '--show-scope with --list' '
+	cat >expect <<-EOF &&
+	global	user.global=true
+	global	user.override=global
+	global	include.path=$INCLUDE_DIR/absolute.include
+	global	user.absolute=include
+	local	user.local=true
+	local	user.override=local
+	local	include.path=../include/relative.include
+	local	user.relative=include
+	command	user.cmdline=true
+	EOF
+	git -c user.cmdline=true config --list --show-scope >output &&
+	test_cmp expect output
+'
+
+test_expect_success !MINGW '--show-scope with --blob' '
+	blob=$(git hash-object -w "$CUSTOM_CONFIG_FILE") &&
+	cat >expect <<-EOF &&
+	command	user.custom=true
+	EOF
+	git config --blob=$blob --show-scope --list >output &&
+	test_cmp expect output
+'
+
+test_expect_success '--show-scope with --local' '
+	cat >expect <<-\EOF &&
+	local	user.local=true
+	local	user.override=local
+	local	include.path=../include/relative.include
+	EOF
+	git config --local --list --show-scope >output &&
+	test_cmp expect output
+'
+
+test_expect_success '--show-scope getting a single value' '
+	cat >expect <<-\EOF &&
+	local	true
+	EOF
+	git config --show-scope --get user.local >output &&
+	test_cmp expect output
+'
+
+test_expect_success '--show-scope with --show-origin' '
+	cat >expect <<-EOF &&
+	global	file:$HOME/.gitconfig	user.global=true
+	global	file:$HOME/.gitconfig	user.override=global
+	global	file:$HOME/.gitconfig	include.path=$INCLUDE_DIR/absolute.include
+	global	file:$INCLUDE_DIR/absolute.include	user.absolute=include
+	local	file:.git/config	user.local=true
+	local	file:.git/config	user.override=local
+	local	file:.git/config	include.path=../include/relative.include
+	local	file:.git/../include/relative.include	user.relative=include
+	command	command line:	user.cmdline=true
+	EOF
+	git -c user.cmdline=true config --list --show-origin --show-scope >output &&
+	test_cmp expect output
 '
 
+for opt in --local --worktree
+do
+	test_expect_success "$opt requires a repo" '
+		# we expect 128 to ensure that we do not simply
+		# fail to find anything and return code "1"
+		test_expect_code 128 nongit git config $opt foo.bar
+	'
+done
+
 cat >.git/config <<-\EOF &&
 [core]
 foo = true
diff --git a/third_party/git/t/t1302-repo-version.sh b/third_party/git/t/t1302-repo-version.sh
index ce4cff13bb..0acabb6d11 100755
--- a/third_party/git/t/t1302-repo-version.sh
+++ b/third_party/git/t/t1302-repo-version.sh
@@ -8,6 +8,10 @@ test_description='Test repository version check'
 . ./test-lib.sh
 
 test_expect_success 'setup' '
+	test_oid_cache <<-\EOF &&
+	version sha1:0
+	version sha256:1
+	EOF
 	cat >test.patch <<-\EOF &&
 	diff --git a/test.txt b/test.txt
 	new file mode 100644
@@ -23,7 +27,7 @@ test_expect_success 'setup' '
 '
 
 test_expect_success 'gitdir selection on normal repos' '
-	echo 0 >expect &&
+	echo $(test_oid version) >expect &&
 	git config core.repositoryformatversion >actual &&
 	git -C test config core.repositoryformatversion >actual2 &&
 	test_cmp expect actual &&
@@ -83,6 +87,9 @@ allow 1
 allow 1 noop
 abort 1 no-such-extension
 allow 0 no-such-extension
+allow 0 noop
+abort 0 noop-v1
+allow 1 noop-v1
 EOF
 
 test_expect_success 'precious-objects allowed' '
diff --git a/third_party/git/t/t1305-config-include.sh b/third_party/git/t/t1305-config-include.sh
index d20b4d150d..f1e1b289f9 100755
--- a/third_party/git/t/t1305-config-include.sh
+++ b/third_party/git/t/t1305-config-include.sh
@@ -63,7 +63,7 @@ test_expect_success 'listing includes option and expansion' '
 	test.one=1
 	EOF
 	git config --list >actual.full &&
-	grep -v ^core actual.full >actual &&
+	grep -v -e ^core -e ^extensions actual.full >actual &&
 	test_cmp expect actual
 '
 
diff --git a/third_party/git/t/t1306-xdg-files.sh b/third_party/git/t/t1306-xdg-files.sh
index 21e139a313..dd87b43be1 100755
--- a/third_party/git/t/t1306-xdg-files.sh
+++ b/third_party/git/t/t1306-xdg-files.sh
@@ -153,7 +153,7 @@ test_expect_success 'Checking attributes in both XDG and local attributes files'
 
 
 test_expect_success 'Checking attributes in a non-XDG global attributes file' '
-	test_might_fail rm .gitattributes &&
+	rm -f .gitattributes &&
 	echo "f attr_f=test" >"$HOME"/my_gitattributes &&
 	git config core.attributesfile "$HOME"/my_gitattributes &&
 	echo "f: attr_f: test" >expected &&
@@ -165,7 +165,7 @@ test_expect_success 'Checking attributes in a non-XDG global attributes file' '
 test_expect_success 'write: xdg file exists and ~/.gitconfig doesn'\''t' '
 	mkdir -p "$HOME"/.config/git &&
 	>"$HOME"/.config/git/config &&
-	test_might_fail rm "$HOME"/.gitconfig &&
+	rm -f "$HOME"/.gitconfig &&
 	git config --global user.name "write_config" &&
 	echo "[user]" >expected &&
 	echo "	name = write_config" >>expected &&
@@ -183,8 +183,8 @@ test_expect_success 'write: xdg file exists and ~/.gitconfig exists' '
 
 
 test_expect_success 'write: ~/.config/git/ exists and config file doesn'\''t' '
-	test_might_fail rm "$HOME"/.gitconfig &&
-	test_might_fail rm "$HOME"/.config/git/config &&
+	rm -f "$HOME"/.gitconfig &&
+	rm -f "$HOME"/.config/git/config &&
 	git config --global user.name "write_gitconfig" &&
 	echo "[user]" >expected &&
 	echo "	name = write_gitconfig" >>expected &&
diff --git a/third_party/git/t/t1307-config-blob.sh b/third_party/git/t/t1307-config-blob.sh
index 37dc689d8c..002e6d3388 100755
--- a/third_party/git/t/t1307-config-blob.sh
+++ b/third_party/git/t/t1307-config-blob.sh
@@ -74,7 +74,7 @@ test_expect_success 'can parse blob ending with CR' '
 '
 
 test_expect_success 'config --blob outside of a repository is an error' '
-	test_must_fail nongit git config --blob=foo --list
+	nongit test_must_fail git config --blob=foo --list
 '
 
 test_done
diff --git a/third_party/git/t/t1308-config-set.sh b/third_party/git/t/t1308-config-set.sh
index d0a2727b85..3a527e3a84 100755
--- a/third_party/git/t/t1308-config-set.sh
+++ b/third_party/git/t/t1308-config-set.sh
@@ -166,14 +166,14 @@ test_expect_success 'find value with highest priority from a configset' '
 '
 
 test_expect_success 'find value_list for a key from a configset' '
-	cat >except <<-\EOF &&
+	cat >expect <<-\EOF &&
+	lama
+	ball
 	sam
 	bat
 	hask
-	lama
-	ball
 	EOF
-	test-tool config configset_get_value case.baz config2 .git/config >actual &&
+	test-tool config configset_get_value_multi case.baz config2 .git/config >actual &&
 	test_cmp expect actual
 '
 
@@ -238,8 +238,8 @@ test_expect_success 'error on modifying repo config without repo' '
 
 cmdline_config="'foo.bar=from-cmdline'"
 test_expect_success 'iteration shows correct origins' '
-	echo "[foo]bar = from-repo" >.git/config &&
-	echo "[foo]bar = from-home" >.gitconfig &&
+	printf "[ignore]\n\tthis = please\n[foo]bar = from-repo\n" >.git/config &&
+	printf "[foo]\n\tbar = from-home\n" >.gitconfig &&
 	if test_have_prereq MINGW
 	then
 		# Use Windows path (i.e. *not* $HOME)
@@ -253,19 +253,29 @@ test_expect_success 'iteration shows correct origins' '
 	value=from-home
 	origin=file
 	name=$HOME_GITCONFIG
+	lno=2
 	scope=global
 
+	key=ignore.this
+	value=please
+	origin=file
+	name=.git/config
+	lno=2
+	scope=local
+
 	key=foo.bar
 	value=from-repo
 	origin=file
 	name=.git/config
-	scope=repo
+	lno=3
+	scope=local
 
 	key=foo.bar
 	value=from-cmdline
 	origin=command line
 	name=
-	scope=cmdline
+	lno=-1
+	scope=command
 	EOF
 	GIT_CONFIG_PARAMETERS=$cmdline_config test-tool config iterate >actual &&
 	test_cmp expect actual
diff --git a/third_party/git/t/t1309-early-config.sh b/third_party/git/t/t1309-early-config.sh
index 0c37e7180d..ebb8e1aecb 100755
--- a/third_party/git/t/t1309-early-config.sh
+++ b/third_party/git/t/t1309-early-config.sh
@@ -29,7 +29,7 @@ test_expect_success 'ceiling' '
 		cd sub &&
 		test-tool config read_early_config early.config
 	) >output &&
-	test -z "$(cat output)"
+	test_must_be_empty output
 '
 
 test_expect_success 'ceiling #2' '
@@ -91,7 +91,12 @@ test_expect_failure 'ignore .git/ with invalid config' '
 
 test_expect_success 'early config and onbranch' '
 	echo "[broken" >broken &&
-	test_with_config "[includeif \"onbranch:refs/heads/master\"]path=../broken"
+	test_with_config "[includeif \"onbranch:master\"]path=../broken"
+'
+
+test_expect_success 'onbranch config outside of git repo' '
+	test_config_global includeIf.onbranch:master.path non-existent &&
+	nongit git help
 '
 
 test_done
diff --git a/third_party/git/t/t1400-update-ref.sh b/third_party/git/t/t1400-update-ref.sh
index 1fbd940408..770e7be363 100755
--- a/third_party/git/t/t1400-update-ref.sh
+++ b/third_party/git/t/t1400-update-ref.sh
@@ -37,15 +37,15 @@ test_expect_success setup '
 
 test_expect_success "create $m" '
 	git update-ref $m $A &&
-	test $A = $(cat .git/$m)
+	test $A = $(git show-ref -s --verify $m)
 '
 test_expect_success "create $m with oldvalue verification" '
 	git update-ref $m $B $A &&
-	test $B = $(cat .git/$m)
+	test $B = $(git show-ref -s --verify $m)
 '
 test_expect_success "fail to delete $m with stale ref" '
 	test_must_fail git update-ref -d $m $A &&
-	test $B = "$(cat .git/$m)"
+	test $B = "$(git show-ref -s --verify $m)"
 '
 test_expect_success "delete $m" '
 	test_when_finished "rm -f .git/$m" &&
@@ -56,7 +56,7 @@ test_expect_success "delete $m" '
 test_expect_success "delete $m without oldvalue verification" '
 	test_when_finished "rm -f .git/$m" &&
 	git update-ref $m $A &&
-	test $A = $(cat .git/$m) &&
+	test $A = $(git show-ref -s --verify $m) &&
 	git update-ref -d $m &&
 	test_path_is_missing .git/$m
 '
@@ -69,15 +69,15 @@ test_expect_success "fail to create $n" '
 
 test_expect_success "create $m (by HEAD)" '
 	git update-ref HEAD $A &&
-	test $A = $(cat .git/$m)
+	test $A = $(git show-ref -s --verify $m)
 '
 test_expect_success "create $m (by HEAD) with oldvalue verification" '
 	git update-ref HEAD $B $A &&
-	test $B = $(cat .git/$m)
+	test $B = $(git show-ref -s --verify $m)
 '
 test_expect_success "fail to delete $m (by HEAD) with stale ref" '
 	test_must_fail git update-ref -d HEAD $A &&
-	test $B = $(cat .git/$m)
+	test $B = $(git show-ref -s --verify $m)
 '
 test_expect_success "delete $m (by HEAD)" '
 	test_when_finished "rm -f .git/$m" &&
@@ -160,10 +160,10 @@ test_expect_success 'core.logAllRefUpdates=always creates reflog by default' '
 	git reflog exists $outside
 '
 
-test_expect_success 'core.logAllRefUpdates=always creates no reflog for ORIG_HEAD' '
+test_expect_success 'core.logAllRefUpdates=always creates reflog for ORIG_HEAD' '
 	test_config core.logAllRefUpdates always &&
 	git update-ref ORIG_HEAD $A &&
-	test_must_fail git reflog exists ORIG_HEAD
+	git reflog exists ORIG_HEAD
 '
 
 test_expect_success '--no-create-reflog overrides core.logAllRefUpdates=always' '
@@ -178,14 +178,14 @@ test_expect_success '--no-create-reflog overrides core.logAllRefUpdates=always'
 
 test_expect_success "create $m (by HEAD)" '
 	git update-ref HEAD $A &&
-	test $A = $(cat .git/$m)
+	test $A = $(git show-ref -s --verify $m)
 '
 test_expect_success 'pack refs' '
 	git pack-refs --all
 '
 test_expect_success "move $m (by HEAD)" '
 	git update-ref HEAD $B $A &&
-	test $B = $(cat .git/$m)
+	test $B = $(git show-ref -s --verify $m)
 '
 test_expect_success "delete $m (by HEAD) should remove both packed and loose $m" '
 	test_when_finished "rm -f .git/$m" &&
@@ -255,7 +255,7 @@ test_expect_success '(not) change HEAD with wrong SHA1' '
 '
 test_expect_success "(not) changed .git/$m" '
 	test_when_finished "rm -f .git/$m" &&
-	! test $B = $(cat .git/$m)
+	! test $B = $(git show-ref -s --verify $m)
 '
 
 rm -f .git/logs/refs/heads/master
@@ -263,19 +263,19 @@ test_expect_success "create $m (logged by touch)" '
 	test_config core.logAllRefUpdates false &&
 	GIT_COMMITTER_DATE="2005-05-26 23:30" \
 	git update-ref --create-reflog HEAD $A -m "Initial Creation" &&
-	test $A = $(cat .git/$m)
+	test $A = $(git show-ref -s --verify $m)
 '
 test_expect_success "update $m (logged by touch)" '
 	test_config core.logAllRefUpdates false &&
 	GIT_COMMITTER_DATE="2005-05-26 23:31" \
 	git update-ref HEAD $B $A -m "Switch" &&
-	test $B = $(cat .git/$m)
+	test $B = $(git show-ref -s --verify $m)
 '
 test_expect_success "set $m (logged by touch)" '
 	test_config core.logAllRefUpdates false &&
 	GIT_COMMITTER_DATE="2005-05-26 23:41" \
 	git update-ref HEAD $A &&
-	test $A = $(cat .git/$m)
+	test $A = $(git show-ref -s --verify $m)
 '
 
 test_expect_success 'empty directory removal' '
@@ -319,19 +319,19 @@ test_expect_success "create $m (logged by config)" '
 	test_config core.logAllRefUpdates true &&
 	GIT_COMMITTER_DATE="2005-05-26 23:32" \
 	git update-ref HEAD $A -m "Initial Creation" &&
-	test $A = $(cat .git/$m)
+	test $A = $(git show-ref -s --verify $m)
 '
 test_expect_success "update $m (logged by config)" '
 	test_config core.logAllRefUpdates true &&
 	GIT_COMMITTER_DATE="2005-05-26 23:33" \
-	git update-ref HEAD'" $B $A "'-m "Switch" &&
-	test $B = $(cat .git/$m)
+	git update-ref HEAD $B $A -m "Switch" &&
+	test $B = $(git show-ref -s --verify $m)
 '
 test_expect_success "set $m (logged by config)" '
 	test_config core.logAllRefUpdates true &&
 	GIT_COMMITTER_DATE="2005-05-26 23:43" \
 	git update-ref HEAD $A &&
-	test $A = $(cat .git/$m)
+	test $A = $(git show-ref -s --verify $m)
 '
 
 cat >expect <<EOF
@@ -344,14 +344,16 @@ test_expect_success "verifying $m's log (logged by config)" '
 	test_cmp expect .git/logs/$m
 '
 
-git update-ref $m $D
-cat >.git/logs/$m <<EOF
-$Z $C $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150320 -0500
-$C $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150350 -0500
-$A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150380 -0500
-$F $Z $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150680 -0500
-$Z $E $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150980 -0500
-EOF
+test_expect_success 'set up for querying the reflog' '
+	git update-ref $m $D &&
+	cat >.git/logs/$m <<-EOF
+	$Z $C $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150320 -0500
+	$C $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150350 -0500
+	$A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150380 -0500
+	$F $Z $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150680 -0500
+	$Z $E $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150980 -0500
+	EOF
+'
 
 ed="Thu, 26 May 2005 18:32:00 -0500"
 gd="Thu, 26 May 2005 18:33:00 -0500"
@@ -359,55 +361,67 @@ ld="Thu, 26 May 2005 18:43:00 -0500"
 test_expect_success 'Query "master@{May 25 2005}" (before history)' '
 	test_when_finished "rm -f o e" &&
 	git rev-parse --verify "master@{May 25 2005}" >o 2>e &&
-	test $C = $(cat o) &&
-	test "warning: Log for '\''master'\'' only goes back to $ed." = "$(cat e)"
+	echo "$C" >expect &&
+	test_cmp expect o &&
+	echo "warning: log for '\''master'\'' only goes back to $ed" >expect &&
+	test_i18ncmp expect e
 '
 test_expect_success 'Query master@{2005-05-25} (before history)' '
 	test_when_finished "rm -f o e" &&
 	git rev-parse --verify master@{2005-05-25} >o 2>e &&
-	test $C = $(cat o) &&
-	test "warning: Log for '\''master'\'' only goes back to $ed." = "$(cat e)"
+	echo "$C" >expect &&
+	test_cmp expect o &&
+	echo "warning: log for '\''master'\'' only goes back to $ed" >expect &&
+	test_i18ncmp expect e
 '
 test_expect_success 'Query "master@{May 26 2005 23:31:59}" (1 second before history)' '
 	test_when_finished "rm -f o e" &&
 	git rev-parse --verify "master@{May 26 2005 23:31:59}" >o 2>e &&
-	test $C = $(cat o) &&
-	test "warning: Log for '\''master'\'' only goes back to $ed." = "$(cat e)"
+	echo "$C" >expect &&
+	test_cmp expect o &&
+	echo "warning: log for '\''master'\'' only goes back to $ed" >expect &&
+	test_i18ncmp expect e
 '
 test_expect_success 'Query "master@{May 26 2005 23:32:00}" (exactly history start)' '
 	test_when_finished "rm -f o e" &&
 	git rev-parse --verify "master@{May 26 2005 23:32:00}" >o 2>e &&
-	test $C = $(cat o) &&
-	test "" = "$(cat e)"
+	echo "$C" >expect &&
+	test_cmp expect o &&
+	test_must_be_empty e
 '
 test_expect_success 'Query "master@{May 26 2005 23:32:30}" (first non-creation change)' '
 	test_when_finished "rm -f o e" &&
 	git rev-parse --verify "master@{May 26 2005 23:32:30}" >o 2>e &&
-	test $A = $(cat o) &&
-	test "" = "$(cat e)"
+	echo "$A" >expect &&
+	test_cmp expect o &&
+	test_must_be_empty e
 '
 test_expect_success 'Query "master@{2005-05-26 23:33:01}" (middle of history with gap)' '
 	test_when_finished "rm -f o e" &&
 	git rev-parse --verify "master@{2005-05-26 23:33:01}" >o 2>e &&
-	test $B = $(cat o) &&
+	echo "$B" >expect &&
+	test_cmp expect o &&
 	test_i18ngrep -F "warning: log for ref $m has gap after $gd" e
 '
 test_expect_success 'Query "master@{2005-05-26 23:38:00}" (middle of history)' '
 	test_when_finished "rm -f o e" &&
 	git rev-parse --verify "master@{2005-05-26 23:38:00}" >o 2>e &&
-	test $Z = $(cat o) &&
-	test "" = "$(cat e)"
+	echo "$Z" >expect &&
+	test_cmp expect o &&
+	test_must_be_empty e
 '
 test_expect_success 'Query "master@{2005-05-26 23:43:00}" (exact end of history)' '
 	test_when_finished "rm -f o e" &&
 	git rev-parse --verify "master@{2005-05-26 23:43:00}" >o 2>e &&
-	test $E = $(cat o) &&
-	test "" = "$(cat e)"
+	echo "$E" >expect &&
+	test_cmp expect o &&
+	test_must_be_empty e
 '
 test_expect_success 'Query "master@{2005-05-28}" (past end of history)' '
 	test_when_finished "rm -f o e" &&
 	git rev-parse --verify "master@{2005-05-28}" >o 2>e &&
-	test $D = $(cat o) &&
+	echo "$D" >expect &&
+	test_cmp expect o &&
 	test_i18ngrep -F "warning: log for ref $m unexpectedly ended on $ld" e
 '
 
@@ -461,57 +475,57 @@ test_expect_success 'git cat-file blob master@{2005-05-26 23:42}:F (expect OTHER
 
 test_expect_success 'given old value for missing pseudoref, do not create' '
 	test_must_fail git update-ref PSEUDOREF $A $B 2>err &&
-	test_path_is_missing .git/PSEUDOREF &&
-	test_i18ngrep "could not read ref" err
+	test_must_fail git rev-parse PSEUDOREF &&
+	test_i18ngrep "unable to resolve reference" err
 '
 
 test_expect_success 'create pseudoref' '
 	git update-ref PSEUDOREF $A &&
-	test $A = $(cat .git/PSEUDOREF)
+	test $A = $(git rev-parse PSEUDOREF)
 '
 
 test_expect_success 'overwrite pseudoref with no old value given' '
 	git update-ref PSEUDOREF $B &&
-	test $B = $(cat .git/PSEUDOREF)
+	test $B = $(git rev-parse PSEUDOREF)
 '
 
 test_expect_success 'overwrite pseudoref with correct old value' '
 	git update-ref PSEUDOREF $C $B &&
-	test $C = $(cat .git/PSEUDOREF)
+	test $C = $(git rev-parse PSEUDOREF)
 '
 
 test_expect_success 'do not overwrite pseudoref with wrong old value' '
 	test_must_fail git update-ref PSEUDOREF $D $E 2>err &&
-	test $C = $(cat .git/PSEUDOREF) &&
-	test_i18ngrep "unexpected object ID" err
+	test $C = $(git rev-parse PSEUDOREF) &&
+	test_i18ngrep "cannot lock ref.*expected" err
 '
 
 test_expect_success 'delete pseudoref' '
 	git update-ref -d PSEUDOREF &&
-	test_path_is_missing .git/PSEUDOREF
+	test_must_fail git rev-parse PSEUDOREF
 '
 
 test_expect_success 'do not delete pseudoref with wrong old value' '
 	git update-ref PSEUDOREF $A &&
 	test_must_fail git update-ref -d PSEUDOREF $B 2>err &&
-	test $A = $(cat .git/PSEUDOREF) &&
-	test_i18ngrep "unexpected object ID" err
+	test $A = $(git rev-parse PSEUDOREF) &&
+	test_i18ngrep "cannot lock ref.*expected" err
 '
 
 test_expect_success 'delete pseudoref with correct old value' '
 	git update-ref -d PSEUDOREF $A &&
-	test_path_is_missing .git/PSEUDOREF
+	test_must_fail git rev-parse PSEUDOREF
 '
 
 test_expect_success 'create pseudoref with old OID zero' '
 	git update-ref PSEUDOREF $A $Z &&
-	test $A = $(cat .git/PSEUDOREF)
+	test $A = $(git rev-parse PSEUDOREF)
 '
 
 test_expect_success 'do not overwrite pseudoref with old OID zero' '
 	test_when_finished git update-ref -d PSEUDOREF &&
 	test_must_fail git update-ref PSEUDOREF $B $Z 2>err &&
-	test $A = $(cat .git/PSEUDOREF) &&
+	test $A = $(git rev-parse PSEUDOREF) &&
 	test_i18ngrep "already exists" err
 '
 
@@ -1340,15 +1354,6 @@ test_expect_success 'fails with duplicate ref update via symref' '
 	test_cmp expect actual
 '
 
-run_with_limited_open_files () {
-	(ulimit -n 32 && "$@")
-}
-
-test_lazy_prereq ULIMIT_FILE_DESCRIPTORS '
-	test_have_prereq !MINGW,!CYGWIN &&
-	run_with_limited_open_files true
-'
-
 test_expect_success ULIMIT_FILE_DESCRIPTORS 'large transaction creating branches does not burst open file limit' '
 (
 	for i in $(test_seq 33)
@@ -1390,4 +1395,135 @@ test_expect_success 'handle per-worktree refs in refs/bisect' '
 	! test_cmp main-head worktree-head
 '
 
+test_expect_success 'transaction handles empty commit' '
+	cat >stdin <<-EOF &&
+	start
+	prepare
+	commit
+	EOF
+	git update-ref --stdin <stdin >actual &&
+	printf "%s: ok\n" start prepare commit >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success 'transaction handles empty commit with missing prepare' '
+	cat >stdin <<-EOF &&
+	start
+	commit
+	EOF
+	git update-ref --stdin <stdin >actual &&
+	printf "%s: ok\n" start commit >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success 'transaction handles sole commit' '
+	cat >stdin <<-EOF &&
+	commit
+	EOF
+	git update-ref --stdin <stdin >actual &&
+	printf "%s: ok\n" commit >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success 'transaction handles empty abort' '
+	cat >stdin <<-EOF &&
+	start
+	prepare
+	abort
+	EOF
+	git update-ref --stdin <stdin >actual &&
+	printf "%s: ok\n" start prepare abort >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success 'transaction exits on multiple aborts' '
+	cat >stdin <<-EOF &&
+	abort
+	abort
+	EOF
+	test_must_fail git update-ref --stdin <stdin >actual 2>err &&
+	printf "%s: ok\n" abort >expect &&
+	test_cmp expect actual &&
+	grep "fatal: transaction is closed" err
+'
+
+test_expect_success 'transaction exits on start after prepare' '
+	cat >stdin <<-EOF &&
+	prepare
+	start
+	EOF
+	test_must_fail git update-ref --stdin <stdin 2>err >actual &&
+	printf "%s: ok\n" prepare >expect &&
+	test_cmp expect actual &&
+	grep "fatal: prepared transactions can only be closed" err
+'
+
+test_expect_success 'transaction handles empty abort with missing prepare' '
+	cat >stdin <<-EOF &&
+	start
+	abort
+	EOF
+	git update-ref --stdin <stdin >actual &&
+	printf "%s: ok\n" start abort >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success 'transaction handles sole abort' '
+	cat >stdin <<-EOF &&
+	abort
+	EOF
+	git update-ref --stdin <stdin >actual &&
+	printf "%s: ok\n" abort >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success 'transaction can handle commit' '
+	cat >stdin <<-EOF &&
+	start
+	create $a HEAD
+	commit
+	EOF
+	git update-ref --stdin <stdin >actual &&
+	printf "%s: ok\n" start commit >expect &&
+	test_cmp expect actual &&
+	git rev-parse HEAD >expect &&
+	git rev-parse $a >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'transaction can handle abort' '
+	cat >stdin <<-EOF &&
+	start
+	create $b HEAD
+	abort
+	EOF
+	git update-ref --stdin <stdin >actual &&
+	printf "%s: ok\n" start abort >expect &&
+	test_cmp expect actual &&
+	test_path_is_missing .git/$b
+'
+
+test_expect_success 'transaction aborts by default' '
+	cat >stdin <<-EOF &&
+	start
+	create $b HEAD
+	EOF
+	git update-ref --stdin <stdin >actual &&
+	printf "%s: ok\n" start >expect &&
+	test_cmp expect actual &&
+	test_path_is_missing .git/$b
+'
+
+test_expect_success 'transaction with prepare aborts by default' '
+	cat >stdin <<-EOF &&
+	start
+	create $b HEAD
+	prepare
+	EOF
+	git update-ref --stdin <stdin >actual &&
+	printf "%s: ok\n" start prepare >expect &&
+	test_cmp expect actual &&
+	test_path_is_missing .git/$b
+'
+
 test_done
diff --git a/third_party/git/t/t1404-update-ref-errors.sh b/third_party/git/t/t1404-update-ref-errors.sh
index 970c5c36b9..2d142e5535 100755
--- a/third_party/git/t/t1404-update-ref-errors.sh
+++ b/third_party/git/t/t1404-update-ref-errors.sh
@@ -32,8 +32,6 @@ test_update_rejected () {
 	test_cmp unchanged actual
 }
 
-Q="'"
-
 # Test adding and deleting D/F-conflicting references in a single
 # transaction.
 df_test() {
@@ -93,7 +91,7 @@ df_test() {
 		delname="$delref"
 	fi &&
 	cat >expected-err <<-EOF &&
-	fatal: cannot lock ref $Q$addname$Q: $Q$delref$Q exists; cannot create $Q$addref$Q
+	fatal: cannot lock ref $SQ$addname$SQ: $SQ$delref$SQ exists; cannot create $SQ$addref$SQ
 	EOF
 	$pack &&
 	if $add_del
@@ -123,7 +121,7 @@ test_expect_success 'existing loose ref is a simple prefix of new' '
 
 	prefix=refs/1l &&
 	test_update_rejected "a c e" false "b c/x d" \
-		"$Q$prefix/c$Q exists; cannot create $Q$prefix/c/x$Q"
+		"$SQ$prefix/c$SQ exists; cannot create $SQ$prefix/c/x$SQ"
 
 '
 
@@ -131,7 +129,7 @@ test_expect_success 'existing packed ref is a simple prefix of new' '
 
 	prefix=refs/1p &&
 	test_update_rejected "a c e" true "b c/x d" \
-		"$Q$prefix/c$Q exists; cannot create $Q$prefix/c/x$Q"
+		"$SQ$prefix/c$SQ exists; cannot create $SQ$prefix/c/x$SQ"
 
 '
 
@@ -139,7 +137,7 @@ test_expect_success 'existing loose ref is a deeper prefix of new' '
 
 	prefix=refs/2l &&
 	test_update_rejected "a c e" false "b c/x/y d" \
-		"$Q$prefix/c$Q exists; cannot create $Q$prefix/c/x/y$Q"
+		"$SQ$prefix/c$SQ exists; cannot create $SQ$prefix/c/x/y$SQ"
 
 '
 
@@ -147,7 +145,7 @@ test_expect_success 'existing packed ref is a deeper prefix of new' '
 
 	prefix=refs/2p &&
 	test_update_rejected "a c e" true "b c/x/y d" \
-		"$Q$prefix/c$Q exists; cannot create $Q$prefix/c/x/y$Q"
+		"$SQ$prefix/c$SQ exists; cannot create $SQ$prefix/c/x/y$SQ"
 
 '
 
@@ -155,7 +153,7 @@ test_expect_success 'new ref is a simple prefix of existing loose' '
 
 	prefix=refs/3l &&
 	test_update_rejected "a c/x e" false "b c d" \
-		"$Q$prefix/c/x$Q exists; cannot create $Q$prefix/c$Q"
+		"$SQ$prefix/c/x$SQ exists; cannot create $SQ$prefix/c$SQ"
 
 '
 
@@ -163,7 +161,7 @@ test_expect_success 'new ref is a simple prefix of existing packed' '
 
 	prefix=refs/3p &&
 	test_update_rejected "a c/x e" true "b c d" \
-		"$Q$prefix/c/x$Q exists; cannot create $Q$prefix/c$Q"
+		"$SQ$prefix/c/x$SQ exists; cannot create $SQ$prefix/c$SQ"
 
 '
 
@@ -171,7 +169,7 @@ test_expect_success 'new ref is a deeper prefix of existing loose' '
 
 	prefix=refs/4l &&
 	test_update_rejected "a c/x/y e" false "b c d" \
-		"$Q$prefix/c/x/y$Q exists; cannot create $Q$prefix/c$Q"
+		"$SQ$prefix/c/x/y$SQ exists; cannot create $SQ$prefix/c$SQ"
 
 '
 
@@ -179,7 +177,7 @@ test_expect_success 'new ref is a deeper prefix of existing packed' '
 
 	prefix=refs/4p &&
 	test_update_rejected "a c/x/y e" true "b c d" \
-		"$Q$prefix/c/x/y$Q exists; cannot create $Q$prefix/c$Q"
+		"$SQ$prefix/c/x/y$SQ exists; cannot create $SQ$prefix/c$SQ"
 
 '
 
@@ -187,7 +185,7 @@ test_expect_success 'one new ref is a simple prefix of another' '
 
 	prefix=refs/5 &&
 	test_update_rejected "a e" false "b c c/x d" \
-		"cannot process $Q$prefix/c$Q and $Q$prefix/c/x$Q at the same time"
+		"cannot process $SQ$prefix/c$SQ and $SQ$prefix/c/x$SQ at the same time"
 
 '
 
@@ -334,7 +332,7 @@ test_expect_success 'D/F conflict prevents indirect delete long packed + indirec
 test_expect_success 'missing old value blocks update' '
 	prefix=refs/missing-update &&
 	cat >expected <<-EOF &&
-	fatal: cannot lock ref $Q$prefix/foo$Q: unable to resolve reference $Q$prefix/foo$Q
+	fatal: cannot lock ref $SQ$prefix/foo$SQ: unable to resolve reference $SQ$prefix/foo$SQ
 	EOF
 	printf "%s\n" "update $prefix/foo $E $D" |
 	test_must_fail git update-ref --stdin 2>output.err &&
@@ -345,7 +343,7 @@ test_expect_success 'incorrect old value blocks update' '
 	prefix=refs/incorrect-update &&
 	git update-ref $prefix/foo $C &&
 	cat >expected <<-EOF &&
-	fatal: cannot lock ref $Q$prefix/foo$Q: is at $C but expected $D
+	fatal: cannot lock ref $SQ$prefix/foo$SQ: is at $C but expected $D
 	EOF
 	printf "%s\n" "update $prefix/foo $E $D" |
 	test_must_fail git update-ref --stdin 2>output.err &&
@@ -356,7 +354,7 @@ test_expect_success 'existing old value blocks create' '
 	prefix=refs/existing-create &&
 	git update-ref $prefix/foo $C &&
 	cat >expected <<-EOF &&
-	fatal: cannot lock ref $Q$prefix/foo$Q: reference already exists
+	fatal: cannot lock ref $SQ$prefix/foo$SQ: reference already exists
 	EOF
 	printf "%s\n" "create $prefix/foo $E" |
 	test_must_fail git update-ref --stdin 2>output.err &&
@@ -367,7 +365,7 @@ test_expect_success 'incorrect old value blocks delete' '
 	prefix=refs/incorrect-delete &&
 	git update-ref $prefix/foo $C &&
 	cat >expected <<-EOF &&
-	fatal: cannot lock ref $Q$prefix/foo$Q: is at $C but expected $D
+	fatal: cannot lock ref $SQ$prefix/foo$SQ: is at $C but expected $D
 	EOF
 	printf "%s\n" "delete $prefix/foo $D" |
 	test_must_fail git update-ref --stdin 2>output.err &&
@@ -378,7 +376,7 @@ test_expect_success 'missing old value blocks indirect update' '
 	prefix=refs/missing-indirect-update &&
 	git symbolic-ref $prefix/symref $prefix/foo &&
 	cat >expected <<-EOF &&
-	fatal: cannot lock ref $Q$prefix/symref$Q: unable to resolve reference $Q$prefix/foo$Q
+	fatal: cannot lock ref $SQ$prefix/symref$SQ: unable to resolve reference $SQ$prefix/foo$SQ
 	EOF
 	printf "%s\n" "update $prefix/symref $E $D" |
 	test_must_fail git update-ref --stdin 2>output.err &&
@@ -390,7 +388,7 @@ test_expect_success 'incorrect old value blocks indirect update' '
 	git symbolic-ref $prefix/symref $prefix/foo &&
 	git update-ref $prefix/foo $C &&
 	cat >expected <<-EOF &&
-	fatal: cannot lock ref $Q$prefix/symref$Q: is at $C but expected $D
+	fatal: cannot lock ref $SQ$prefix/symref$SQ: is at $C but expected $D
 	EOF
 	printf "%s\n" "update $prefix/symref $E $D" |
 	test_must_fail git update-ref --stdin 2>output.err &&
@@ -402,7 +400,7 @@ test_expect_success 'existing old value blocks indirect create' '
 	git symbolic-ref $prefix/symref $prefix/foo &&
 	git update-ref $prefix/foo $C &&
 	cat >expected <<-EOF &&
-	fatal: cannot lock ref $Q$prefix/symref$Q: reference already exists
+	fatal: cannot lock ref $SQ$prefix/symref$SQ: reference already exists
 	EOF
 	printf "%s\n" "create $prefix/symref $E" |
 	test_must_fail git update-ref --stdin 2>output.err &&
@@ -414,7 +412,7 @@ test_expect_success 'incorrect old value blocks indirect delete' '
 	git symbolic-ref $prefix/symref $prefix/foo &&
 	git update-ref $prefix/foo $C &&
 	cat >expected <<-EOF &&
-	fatal: cannot lock ref $Q$prefix/symref$Q: is at $C but expected $D
+	fatal: cannot lock ref $SQ$prefix/symref$SQ: is at $C but expected $D
 	EOF
 	printf "%s\n" "delete $prefix/symref $D" |
 	test_must_fail git update-ref --stdin 2>output.err &&
@@ -425,7 +423,7 @@ test_expect_success 'missing old value blocks indirect no-deref update' '
 	prefix=refs/missing-noderef-update &&
 	git symbolic-ref $prefix/symref $prefix/foo &&
 	cat >expected <<-EOF &&
-	fatal: cannot lock ref $Q$prefix/symref$Q: reference is missing but expected $D
+	fatal: cannot lock ref $SQ$prefix/symref$SQ: reference is missing but expected $D
 	EOF
 	printf "%s\n" "option no-deref" "update $prefix/symref $E $D" |
 	test_must_fail git update-ref --stdin 2>output.err &&
@@ -437,7 +435,7 @@ test_expect_success 'incorrect old value blocks indirect no-deref update' '
 	git symbolic-ref $prefix/symref $prefix/foo &&
 	git update-ref $prefix/foo $C &&
 	cat >expected <<-EOF &&
-	fatal: cannot lock ref $Q$prefix/symref$Q: is at $C but expected $D
+	fatal: cannot lock ref $SQ$prefix/symref$SQ: is at $C but expected $D
 	EOF
 	printf "%s\n" "option no-deref" "update $prefix/symref $E $D" |
 	test_must_fail git update-ref --stdin 2>output.err &&
@@ -449,7 +447,7 @@ test_expect_success 'existing old value blocks indirect no-deref create' '
 	git symbolic-ref $prefix/symref $prefix/foo &&
 	git update-ref $prefix/foo $C &&
 	cat >expected <<-EOF &&
-	fatal: cannot lock ref $Q$prefix/symref$Q: reference already exists
+	fatal: cannot lock ref $SQ$prefix/symref$SQ: reference already exists
 	EOF
 	printf "%s\n" "option no-deref" "create $prefix/symref $E" |
 	test_must_fail git update-ref --stdin 2>output.err &&
@@ -461,7 +459,7 @@ test_expect_success 'incorrect old value blocks indirect no-deref delete' '
 	git symbolic-ref $prefix/symref $prefix/foo &&
 	git update-ref $prefix/foo $C &&
 	cat >expected <<-EOF &&
-	fatal: cannot lock ref $Q$prefix/symref$Q: is at $C but expected $D
+	fatal: cannot lock ref $SQ$prefix/symref$SQ: is at $C but expected $D
 	EOF
 	printf "%s\n" "option no-deref" "delete $prefix/symref $D" |
 	test_must_fail git update-ref --stdin 2>output.err &&
@@ -474,13 +472,13 @@ test_expect_success 'non-empty directory blocks create' '
 	: >.git/$prefix/foo/bar/baz.lock &&
 	test_when_finished "rm -f .git/$prefix/foo/bar/baz.lock" &&
 	cat >expected <<-EOF &&
-	fatal: cannot lock ref $Q$prefix/foo$Q: there is a non-empty directory $Q.git/$prefix/foo$Q blocking reference $Q$prefix/foo$Q
+	fatal: cannot lock ref $SQ$prefix/foo$SQ: there is a non-empty directory $SQ.git/$prefix/foo$SQ blocking reference $SQ$prefix/foo$SQ
 	EOF
 	printf "%s\n" "update $prefix/foo $C" |
 	test_must_fail git update-ref --stdin 2>output.err &&
 	test_cmp expected output.err &&
 	cat >expected <<-EOF &&
-	fatal: cannot lock ref $Q$prefix/foo$Q: unable to resolve reference $Q$prefix/foo$Q
+	fatal: cannot lock ref $SQ$prefix/foo$SQ: unable to resolve reference $SQ$prefix/foo$SQ
 	EOF
 	printf "%s\n" "update $prefix/foo $D $C" |
 	test_must_fail git update-ref --stdin 2>output.err &&
@@ -493,13 +491,13 @@ test_expect_success 'broken reference blocks create' '
 	echo "gobbledigook" >.git/$prefix/foo &&
 	test_when_finished "rm -f .git/$prefix/foo" &&
 	cat >expected <<-EOF &&
-	fatal: cannot lock ref $Q$prefix/foo$Q: unable to resolve reference $Q$prefix/foo$Q: reference broken
+	fatal: cannot lock ref $SQ$prefix/foo$SQ: unable to resolve reference $SQ$prefix/foo$SQ: reference broken
 	EOF
 	printf "%s\n" "update $prefix/foo $C" |
 	test_must_fail git update-ref --stdin 2>output.err &&
 	test_cmp expected output.err &&
 	cat >expected <<-EOF &&
-	fatal: cannot lock ref $Q$prefix/foo$Q: unable to resolve reference $Q$prefix/foo$Q: reference broken
+	fatal: cannot lock ref $SQ$prefix/foo$SQ: unable to resolve reference $SQ$prefix/foo$SQ: reference broken
 	EOF
 	printf "%s\n" "update $prefix/foo $D $C" |
 	test_must_fail git update-ref --stdin 2>output.err &&
@@ -513,13 +511,13 @@ test_expect_success 'non-empty directory blocks indirect create' '
 	: >.git/$prefix/foo/bar/baz.lock &&
 	test_when_finished "rm -f .git/$prefix/foo/bar/baz.lock" &&
 	cat >expected <<-EOF &&
-	fatal: cannot lock ref $Q$prefix/symref$Q: there is a non-empty directory $Q.git/$prefix/foo$Q blocking reference $Q$prefix/foo$Q
+	fatal: cannot lock ref $SQ$prefix/symref$SQ: there is a non-empty directory $SQ.git/$prefix/foo$SQ blocking reference $SQ$prefix/foo$SQ
 	EOF
 	printf "%s\n" "update $prefix/symref $C" |
 	test_must_fail git update-ref --stdin 2>output.err &&
 	test_cmp expected output.err &&
 	cat >expected <<-EOF &&
-	fatal: cannot lock ref $Q$prefix/symref$Q: unable to resolve reference $Q$prefix/foo$Q
+	fatal: cannot lock ref $SQ$prefix/symref$SQ: unable to resolve reference $SQ$prefix/foo$SQ
 	EOF
 	printf "%s\n" "update $prefix/symref $D $C" |
 	test_must_fail git update-ref --stdin 2>output.err &&
@@ -532,13 +530,13 @@ test_expect_success 'broken reference blocks indirect create' '
 	echo "gobbledigook" >.git/$prefix/foo &&
 	test_when_finished "rm -f .git/$prefix/foo" &&
 	cat >expected <<-EOF &&
-	fatal: cannot lock ref $Q$prefix/symref$Q: unable to resolve reference $Q$prefix/foo$Q: reference broken
+	fatal: cannot lock ref $SQ$prefix/symref$SQ: unable to resolve reference $SQ$prefix/foo$SQ: reference broken
 	EOF
 	printf "%s\n" "update $prefix/symref $C" |
 	test_must_fail git update-ref --stdin 2>output.err &&
 	test_cmp expected output.err &&
 	cat >expected <<-EOF &&
-	fatal: cannot lock ref $Q$prefix/symref$Q: unable to resolve reference $Q$prefix/foo$Q: reference broken
+	fatal: cannot lock ref $SQ$prefix/symref$SQ: unable to resolve reference $SQ$prefix/foo$SQ: reference broken
 	EOF
 	printf "%s\n" "update $prefix/symref $D $C" |
 	test_must_fail git update-ref --stdin 2>output.err &&
@@ -614,7 +612,7 @@ test_expect_success 'delete fails cleanly if packed-refs file is locked' '
 	test_when_finished "rm -f .git/packed-refs.lock" &&
 	test_must_fail git update-ref -d $prefix/foo >out 2>err &&
 	git for-each-ref $prefix >actual &&
-	test_i18ngrep "Unable to create $Q.*packed-refs.lock$Q: " err &&
+	test_i18ngrep "Unable to create $SQ.*packed-refs.lock$SQ: " err &&
 	test_cmp unchanged actual
 '
 
diff --git a/third_party/git/t/t1405-main-ref-store.sh b/third_party/git/t/t1405-main-ref-store.sh
index 331899ddc4..74af927fba 100755
--- a/third_party/git/t/t1405-main-ref-store.sh
+++ b/third_party/git/t/t1405-main-ref-store.sh
@@ -31,7 +31,10 @@ test_expect_success 'create_symref(FOO, refs/heads/master)' '
 test_expect_success 'delete_refs(FOO, refs/tags/new-tag)' '
 	git rev-parse FOO -- &&
 	git rev-parse refs/tags/new-tag -- &&
-	$RUN delete-refs 0 nothing FOO refs/tags/new-tag &&
+	m=$(git rev-parse master) &&
+	REF_NO_DEREF=1 &&
+	$RUN delete-refs $REF_NO_DEREF nothing FOO refs/tags/new-tag &&
+	test_must_fail git rev-parse --symbolic-full-name FOO &&
 	test_must_fail git rev-parse FOO -- &&
 	test_must_fail git rev-parse refs/tags/new-tag --
 '
diff --git a/third_party/git/t/t1406-submodule-ref-store.sh b/third_party/git/t/t1406-submodule-ref-store.sh
index d199d872fb..36b7ef5046 100755
--- a/third_party/git/t/t1406-submodule-ref-store.sh
+++ b/third_party/git/t/t1406-submodule-ref-store.sh
@@ -75,7 +75,7 @@ test_expect_success 'for_each_reflog()' '
 '
 
 test_expect_success 'for_each_reflog_ent()' '
-	$RUN for-each-reflog-ent HEAD >actual && cat actual &&
+	$RUN for-each-reflog-ent HEAD >actual &&
 	head -n1 actual | grep first &&
 	tail -n2 actual | head -n1 | grep master.to.new
 '
diff --git a/third_party/git/t/t1409-avoid-packing-refs.sh b/third_party/git/t/t1409-avoid-packing-refs.sh
index e5cb8a252d..be12fb6350 100755
--- a/third_party/git/t/t1409-avoid-packing-refs.sh
+++ b/third_party/git/t/t1409-avoid-packing-refs.sh
@@ -8,7 +8,7 @@ test_description='avoid rewriting packed-refs unnecessarily'
 # shouldn't upset readers, and it should be omitted if the file is
 # ever rewritten.
 mark_packed_refs () {
-	sed -e "s/^\(#.*\)/\1 t1409 /" <.git/packed-refs >.git/packed-refs.new &&
+	sed -e "s/^\(#.*\)/\1 t1409 /" .git/packed-refs >.git/packed-refs.new &&
 	mv .git/packed-refs.new .git/packed-refs
 }
 
@@ -27,15 +27,15 @@ test_expect_success 'setup' '
 '
 
 test_expect_success 'do not create packed-refs file gratuitously' '
-	test_must_fail test -f .git/packed-refs &&
+	test_path_is_missing .git/packed-refs &&
 	git update-ref refs/heads/foo $A &&
-	test_must_fail test -f .git/packed-refs &&
+	test_path_is_missing .git/packed-refs &&
 	git update-ref refs/heads/foo $B &&
-	test_must_fail test -f .git/packed-refs &&
+	test_path_is_missing .git/packed-refs &&
 	git update-ref refs/heads/foo $C $B &&
-	test_must_fail test -f .git/packed-refs &&
+	test_path_is_missing .git/packed-refs &&
 	git update-ref -d refs/heads/foo &&
-	test_must_fail test -f .git/packed-refs
+	test_path_is_missing .git/packed-refs
 '
 
 test_expect_success 'check that marking the packed-refs file works' '
@@ -46,7 +46,7 @@ test_expect_success 'check that marking the packed-refs file works' '
 	git for-each-ref >actual &&
 	test_cmp expected actual &&
 	git pack-refs --all &&
-	test_must_fail check_packed_refs_marked &&
+	! check_packed_refs_marked &&
 	git for-each-ref >actual2 &&
 	test_cmp expected actual2
 '
@@ -80,7 +80,7 @@ test_expect_success 'touch packed-refs on delete of packed' '
 	git pack-refs --all &&
 	mark_packed_refs &&
 	git update-ref -d refs/heads/packed-delete &&
-	test_must_fail check_packed_refs_marked
+	! check_packed_refs_marked
 '
 
 test_expect_success 'leave packed-refs untouched on update of loose' '
diff --git a/third_party/git/t/t1410-reflog.sh b/third_party/git/t/t1410-reflog.sh
index 82950c0282..730a43d9dd 100755
--- a/third_party/git/t/t1410-reflog.sh
+++ b/third_party/git/t/t1410-reflog.sh
@@ -54,7 +54,6 @@ check_dont_have () {
 }
 
 test_expect_success setup '
-	test_oid_init &&
 	mkdir -p A/B &&
 	echo rat >C &&
 	echo ox >A/D &&
@@ -195,7 +194,7 @@ test_expect_success 'delete' '
 
 	git reflog delete master@{1} &&
 	git reflog show master > output &&
-	test $(($master_entry_count - 1)) = $(wc -l < output) &&
+	test_line_count = $(($master_entry_count - 1)) output &&
 	test $HEAD_entry_count = $(git reflog | wc -l) &&
 	! grep ox < output &&
 
@@ -209,7 +208,7 @@ test_expect_success 'delete' '
 
 	git reflog delete master@{07.04.2005.15:15:00.-0700} &&
 	git reflog show master > output &&
-	test $(($master_entry_count - 1)) = $(wc -l < output) &&
+	test_line_count = $(($master_entry_count - 1)) output &&
 	! grep dragon < output
 
 '
diff --git a/third_party/git/t/t1414-reflog-walk.sh b/third_party/git/t/t1414-reflog-walk.sh
index feb1efd8ff..1181a9fb28 100755
--- a/third_party/git/t/t1414-reflog-walk.sh
+++ b/third_party/git/t/t1414-reflog-walk.sh
@@ -18,10 +18,9 @@ do_walk () {
 	git log -g --format="%gd %gs" "$@"
 }
 
-sq="'"
 test_expect_success 'set up expected reflog' '
 	cat >expect.all <<-EOF
-	HEAD@{0} commit (merge): Merge branch ${sq}master${sq} into side
+	HEAD@{0} commit (merge): Merge branch ${SQ}master${SQ} into side
 	HEAD@{1} commit: three
 	HEAD@{2} checkout: moving from master to side
 	HEAD@{3} commit: two
diff --git a/third_party/git/t/t1415-worktree-refs.sh b/third_party/git/t/t1415-worktree-refs.sh
index bb2c7572a3..7ab91241ab 100755
--- a/third_party/git/t/t1415-worktree-refs.sh
+++ b/third_party/git/t/t1415-worktree-refs.sh
@@ -76,24 +76,24 @@ test_expect_success 'reflog of worktrees/xx/HEAD' '
 	test_cmp expected actual.wt2
 '
 
-test_expect_success 'for-each-ref from main repo' '
+test_expect_success 'for-each-ref from main worktree' '
 	mkdir fer1 &&
 	git -C fer1 init repo &&
 	test_commit -C fer1/repo initial &&
 	git -C fer1/repo worktree add ../second &&
-	git -C fer1/repo update-ref refs/bisect/main HEAD &&
-	git -C fer1/repo update-ref refs/rewritten/main HEAD &&
-	git -C fer1/repo update-ref refs/worktree/main HEAD &&
-	git -C fer1/repo for-each-ref --format="%(refname)" | grep main >actual &&
+	git -C fer1/repo update-ref refs/bisect/first HEAD &&
+	git -C fer1/repo update-ref refs/rewritten/first HEAD &&
+	git -C fer1/repo update-ref refs/worktree/first HEAD &&
+	git -C fer1/repo for-each-ref --format="%(refname)" | grep first >actual &&
 	cat >expected <<-\EOF &&
-	refs/bisect/main
-	refs/rewritten/main
-	refs/worktree/main
+	refs/bisect/first
+	refs/rewritten/first
+	refs/worktree/first
 	EOF
 	test_cmp expected actual
 '
 
-test_expect_success 'for-each-ref from linked repo' '
+test_expect_success 'for-each-ref from linked worktree' '
 	mkdir fer2 &&
 	git -C fer2 init repo &&
 	test_commit -C fer2/repo initial &&
diff --git a/third_party/git/t/t1416-ref-transaction-hooks.sh b/third_party/git/t/t1416-ref-transaction-hooks.sh
new file mode 100755
index 0000000000..f6e741c6c0
--- /dev/null
+++ b/third_party/git/t/t1416-ref-transaction-hooks.sh
@@ -0,0 +1,136 @@
+#!/bin/sh
+
+test_description='reference transaction hooks'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+	mkdir -p .git/hooks &&
+	test_commit PRE &&
+	PRE_OID=$(git rev-parse PRE) &&
+	test_commit POST &&
+	POST_OID=$(git rev-parse POST)
+'
+
+test_expect_success 'hook allows updating ref if successful' '
+	test_when_finished "rm .git/hooks/reference-transaction" &&
+	git reset --hard PRE &&
+	write_script .git/hooks/reference-transaction <<-\EOF &&
+		echo "$*" >>actual
+	EOF
+	cat >expect <<-EOF &&
+		prepared
+		committed
+	EOF
+	git update-ref HEAD POST &&
+	test_cmp expect actual
+'
+
+test_expect_success 'hook aborts updating ref in prepared state' '
+	test_when_finished "rm .git/hooks/reference-transaction" &&
+	git reset --hard PRE &&
+	write_script .git/hooks/reference-transaction <<-\EOF &&
+		if test "$1" = prepared
+		then
+			exit 1
+		fi
+	EOF
+	test_must_fail git update-ref HEAD POST 2>err &&
+	test_i18ngrep "ref updates aborted by hook" err
+'
+
+test_expect_success 'hook gets all queued updates in prepared state' '
+	test_when_finished "rm .git/hooks/reference-transaction actual" &&
+	git reset --hard PRE &&
+	write_script .git/hooks/reference-transaction <<-\EOF &&
+		if test "$1" = prepared
+		then
+			while read -r line
+			do
+				printf "%s\n" "$line"
+			done >actual
+		fi
+	EOF
+	cat >expect <<-EOF &&
+		$ZERO_OID $POST_OID HEAD
+		$ZERO_OID $POST_OID refs/heads/master
+	EOF
+	git update-ref HEAD POST <<-EOF &&
+		update HEAD $ZERO_OID $POST_OID
+		update refs/heads/master $ZERO_OID $POST_OID
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'hook gets all queued updates in committed state' '
+	test_when_finished "rm .git/hooks/reference-transaction actual" &&
+	git reset --hard PRE &&
+	write_script .git/hooks/reference-transaction <<-\EOF &&
+		if test "$1" = committed
+		then
+			while read -r line
+			do
+				printf "%s\n" "$line"
+			done >actual
+		fi
+	EOF
+	cat >expect <<-EOF &&
+		$ZERO_OID $POST_OID HEAD
+		$ZERO_OID $POST_OID refs/heads/master
+	EOF
+	git update-ref HEAD POST &&
+	test_cmp expect actual
+'
+
+test_expect_success 'hook gets all queued updates in aborted state' '
+	test_when_finished "rm .git/hooks/reference-transaction actual" &&
+	git reset --hard PRE &&
+	write_script .git/hooks/reference-transaction <<-\EOF &&
+		if test "$1" = aborted
+		then
+			while read -r line
+			do
+				printf "%s\n" "$line"
+			done >actual
+		fi
+	EOF
+	cat >expect <<-EOF &&
+		$ZERO_OID $POST_OID HEAD
+		$ZERO_OID $POST_OID refs/heads/master
+	EOF
+	git update-ref --stdin <<-EOF &&
+		start
+		update HEAD POST $ZERO_OID
+		update refs/heads/master POST $ZERO_OID
+		abort
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'interleaving hook calls succeed' '
+	test_when_finished "rm -r target-repo.git" &&
+
+	git init --bare target-repo.git &&
+
+	write_script target-repo.git/hooks/reference-transaction <<-\EOF &&
+		echo $0 "$@" >>actual
+	EOF
+
+	write_script target-repo.git/hooks/update <<-\EOF &&
+		echo $0 "$@" >>actual
+	EOF
+
+	cat >expect <<-EOF &&
+		hooks/update refs/tags/PRE $ZERO_OID $PRE_OID
+		hooks/reference-transaction prepared
+		hooks/reference-transaction committed
+		hooks/update refs/tags/POST $ZERO_OID $POST_OID
+		hooks/reference-transaction prepared
+		hooks/reference-transaction committed
+	EOF
+
+	git push ./target-repo.git PRE POST &&
+	test_cmp expect target-repo.git/actual
+'
+
+test_done
diff --git a/third_party/git/t/t1450-fsck.sh b/third_party/git/t/t1450-fsck.sh
index b36e0528d0..b17f5c21fb 100755
--- a/third_party/git/t/t1450-fsck.sh
+++ b/third_party/git/t/t1450-fsck.sh
@@ -9,7 +9,6 @@ test_description='git fsck random collection of tests
 . ./test-lib.sh
 
 test_expect_success setup '
-	test_oid_init &&
 	git config gc.auto 0 &&
 	git config i18n.commitencoding ISO-8859-1 &&
 	test_commit A fileA one &&
@@ -70,7 +69,6 @@ test_expect_success 'object with bad sha1' '
 	test_when_finished "git update-ref -d refs/heads/bogus" &&
 
 	test_must_fail git fsck 2>out &&
-	cat out &&
 	test_i18ngrep "$sha.*corrupt" out
 '
 
@@ -78,7 +76,6 @@ test_expect_success 'branch pointing to non-commit' '
 	git rev-parse HEAD^{tree} >.git/refs/heads/invalid &&
 	test_when_finished "git update-ref -d refs/heads/invalid" &&
 	test_must_fail git fsck 2>out &&
-	cat out &&
 	test_i18ngrep "not a commit" out
 '
 
@@ -88,7 +85,6 @@ test_expect_success 'HEAD link pointing at a funny object' '
 	echo $ZERO_OID >.git/HEAD &&
 	# avoid corrupt/broken HEAD from interfering with repo discovery
 	test_must_fail env GIT_DIR=.git git fsck 2>out &&
-	cat out &&
 	test_i18ngrep "detached HEAD points" out
 '
 
@@ -98,7 +94,6 @@ test_expect_success 'HEAD link pointing at a funny place' '
 	echo "ref: refs/funny/place" >.git/HEAD &&
 	# avoid corrupt/broken HEAD from interfering with repo discovery
 	test_must_fail env GIT_DIR=.git git fsck 2>out &&
-	cat out &&
 	test_i18ngrep "HEAD points to something strange" out
 '
 
@@ -137,6 +132,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 &&
@@ -145,7 +164,6 @@ test_expect_success 'email without @ is okay' '
 	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
 '
 
@@ -157,7 +175,6 @@ test_expect_success 'email with embedded > is not okay' '
 	git update-ref refs/heads/bogus "$new" &&
 	test_when_finished "git update-ref -d refs/heads/bogus" &&
 	test_must_fail git fsck 2>out &&
-	cat out &&
 	test_i18ngrep "error in commit $new" out
 '
 
@@ -169,7 +186,6 @@ test_expect_success 'missing < email delimiter is reported nicely' '
 	git update-ref refs/heads/bogus "$new" &&
 	test_when_finished "git update-ref -d refs/heads/bogus" &&
 	test_must_fail git fsck 2>out &&
-	cat out &&
 	test_i18ngrep "error in commit $new.* - bad name" out
 '
 
@@ -181,7 +197,6 @@ test_expect_success 'missing email is reported nicely' '
 	git update-ref refs/heads/bogus "$new" &&
 	test_when_finished "git update-ref -d refs/heads/bogus" &&
 	test_must_fail git fsck 2>out &&
-	cat out &&
 	test_i18ngrep "error in commit $new.* - missing email" out
 '
 
@@ -193,7 +208,6 @@ test_expect_success '> in name is reported' '
 	git update-ref refs/heads/bogus "$new" &&
 	test_when_finished "git update-ref -d refs/heads/bogus" &&
 	test_must_fail git fsck 2>out &&
-	cat out &&
 	test_i18ngrep "error in commit $new" out
 '
 
@@ -207,7 +221,6 @@ test_expect_success 'integer overflow in timestamps is reported' '
 	git update-ref refs/heads/bogus "$new" &&
 	test_when_finished "git update-ref -d refs/heads/bogus" &&
 	test_must_fail git fsck 2>out &&
-	cat out &&
 	test_i18ngrep "error in commit $new.*integer overflow" out
 '
 
@@ -219,7 +232,6 @@ test_expect_success 'commit with NUL in header' '
 	git update-ref refs/heads/bogus "$new" &&
 	test_when_finished "git update-ref -d refs/heads/bogus" &&
 	test_must_fail git fsck 2>out &&
-	cat out &&
 	test_i18ngrep "error in commit $new.*unterminated header: NUL at offset" out
 '
 
@@ -244,6 +256,35 @@ test_expect_success 'tree object with duplicate entries' '
 	test_i18ngrep "error in tree .*contains duplicate file entries" out
 '
 
+check_duplicate_names () {
+	expect=$1 &&
+	shift &&
+	names=$@ &&
+	test_expect_$expect "tree object with duplicate names: $names" '
+		test_when_finished "remove_object \$blob" &&
+		test_when_finished "remove_object \$tree" &&
+		test_when_finished "remove_object \$badtree" &&
+		blob=$(echo blob | git hash-object -w --stdin) &&
+		printf "100644 blob %s\t%s\n" $blob x.2 >tree &&
+		tree=$(git mktree <tree) &&
+		for name in $names
+		do
+			case "$name" in
+			*/) printf "040000 tree %s\t%s\n" $tree "${name%/}" ;;
+			*)  printf "100644 blob %s\t%s\n" $blob "$name" ;;
+			esac
+		done >badtree &&
+		badtree=$(git mktree <badtree) &&
+		test_must_fail git fsck 2>out &&
+		test_i18ngrep "$badtree" out &&
+		test_i18ngrep "error in tree .*contains duplicate file entries" out
+	'
+}
+
+check_duplicate_names success x x.1 x/
+check_duplicate_names success x x.1.2 x.1/ x/
+check_duplicate_names success x x.1 x.1.2 x/
+
 test_expect_success 'unparseable tree object' '
 	test_oid_cache <<-\EOF &&
 	junk sha1:twenty-bytes-of-junk
@@ -297,7 +338,6 @@ test_expect_success 'tag pointing to nonexistent' '
 	echo $tag >.git/refs/tags/invalid &&
 	test_when_finished "git update-ref -d refs/tags/invalid" &&
 	test_must_fail git fsck --tags >out &&
-	cat out &&
 	test_i18ngrep "broken link" out
 '
 
@@ -378,7 +418,6 @@ test_expect_success 'tag with NUL in header' '
 	echo $tag >.git/refs/tags/wrong &&
 	test_when_finished "git update-ref -d refs/tags/wrong" &&
 	test_must_fail git fsck --tags 2>out &&
-	cat out &&
 	test_i18ngrep "error in tag $tag.*unterminated header: NUL at offset" out
 '
 
@@ -409,7 +448,6 @@ test_expect_success 'rev-list --verify-objects with bad sha1' '
 	test_when_finished "git update-ref -d refs/heads/bogus" &&
 
 	test_might_fail git rev-list --verify-objects refs/heads/bogus >/dev/null 2>out &&
-	cat out &&
 	test_i18ngrep -q "error: hash mismatch $(dirname $new)$(test_oid ff_2)" out
 '
 
@@ -433,7 +471,6 @@ test_expect_success 'fsck notices blob entry pointing to null sha1' '
 	 sha=$(printf "100644 file$_bz$_bzoid" |
 	       git hash-object -w --stdin -t tree) &&
 	  git fsck 2>out &&
-	  cat out &&
 	  test_i18ngrep "warning.*null sha1" out
 	)
 '
@@ -444,7 +481,6 @@ test_expect_success 'fsck notices submodule entry pointing to null sha1' '
 	 sha=$(printf "160000 submodule$_bz$_bzoid" |
 	       git hash-object -w --stdin -t tree) &&
 	  git fsck 2>out &&
-	  cat out &&
 	  test_i18ngrep "warning.*null sha1" out
 	)
 '
@@ -456,6 +492,7 @@ while read name path pretty; do
 		(
 			git init $name-$type &&
 			cd $name-$type &&
+			git config core.protectNTFS false &&
 			echo content >file &&
 			git add file &&
 			git commit -m base &&
@@ -465,7 +502,6 @@ while read name path pretty; do
 			printf "$mode $type %s\t%s" "$value" "$path" >bad &&
 			bad_tree=$(git mktree <bad) &&
 			git fsck 2>out &&
-			cat out &&
 			test_i18ngrep "warning.*tree $bad_tree" out
 		)'
 	done <<-\EOF
@@ -632,7 +668,7 @@ test_expect_success 'fsck --name-objects' '
 		remove_object $(git rev-parse julius:caesar.t) &&
 		test_must_fail git fsck --name-objects >out &&
 		tree=$(git rev-parse --verify julius:) &&
-		test_i18ngrep -E "$tree \((refs/heads/master|HEAD)@\{[0-9]*\}:" out
+		test_i18ngrep "$tree (refs/tags/julius:" out
 	)
 '
 
@@ -677,7 +713,7 @@ test_expect_success 'fsck fails on corrupt packfile' '
 	# at least one of which is not zero, so setting the first byte to 0 is
 	# sufficient.)
 	chmod a+w .git/objects/pack/pack-$pack.pack &&
-	printf '\0' | dd of=.git/objects/pack/pack-$pack.pack bs=1 conv=notrunc seek=12 &&
+	printf "\0" | dd of=.git/objects/pack/pack-$pack.pack bs=1 conv=notrunc seek=12 &&
 
 	test_when_finished "rm -f .git/objects/pack/pack-$pack.*" &&
 	remove_object $hsh &&
diff --git a/third_party/git/t/t1500-rev-parse.sh b/third_party/git/t/t1500-rev-parse.sh
index 01abee533d..408b97d5af 100755
--- a/third_party/git/t/t1500-rev-parse.sh
+++ b/third_party/git/t/t1500-rev-parse.sh
@@ -131,6 +131,30 @@ test_expect_success 'rev-parse --is-shallow-repository in non-shallow repo' '
 	test_cmp expect actual
 '
 
+test_expect_success 'rev-parse --show-object-format in repo' '
+	echo "$(test_oid algo)" >expect &&
+	git rev-parse --show-object-format >actual &&
+	test_cmp expect actual &&
+	git rev-parse --show-object-format=storage >actual &&
+	test_cmp expect actual &&
+	git rev-parse --show-object-format=input >actual &&
+	test_cmp expect actual &&
+	git rev-parse --show-object-format=output >actual &&
+	test_cmp expect actual &&
+	test_must_fail git rev-parse --show-object-format=squeamish-ossifrage 2>err &&
+	grep "unknown mode for --show-object-format: squeamish-ossifrage" err
+'
+
+test_expect_success '--show-toplevel from subdir of working tree' '
+	pwd >expect &&
+	git -C sub/dir rev-parse --show-toplevel >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success '--show-toplevel from inside .git' '
+	test_must_fail git -C .git rev-parse --show-toplevel
+'
+
 test_expect_success 'showing the superproject correctly' '
 	git rev-parse --show-superproject-working-tree >out &&
 	test_must_be_empty out &&
diff --git a/third_party/git/t/t1501-work-tree.sh b/third_party/git/t/t1501-work-tree.sh
index 3498d3d55e..b75558040f 100755
--- a/third_party/git/t/t1501-work-tree.sh
+++ b/third_party/git/t/t1501-work-tree.sh
@@ -350,7 +350,7 @@ test_expect_success 'Multi-worktree setup' '
 	mkdir work &&
 	mkdir -p repo.git/repos/foo &&
 	cp repo.git/HEAD repo.git/index repo.git/repos/foo &&
-	test_might_fail cp repo.git/sharedindex.* repo.git/repos/foo &&
+	{ cp repo.git/sharedindex.* repo.git/repos/foo || :; } &&
 	sane_unset GIT_DIR GIT_CONFIG GIT_WORK_TREE
 '
 
diff --git a/third_party/git/t/t1506-rev-parse-diagnosis.sh b/third_party/git/t/t1506-rev-parse-diagnosis.sh
index 4ee009da66..3e657e693b 100755
--- a/third_party/git/t/t1506-rev-parse-diagnosis.sh
+++ b/third_party/git/t/t1506-rev-parse-diagnosis.sh
@@ -8,12 +8,11 @@ exec </dev/null
 
 test_did_you_mean ()
 {
-	sq="'" &&
 	cat >expected <<-EOF &&
-	fatal: Path '$2$3' $4, but not ${5:-$sq$3$sq}.
-	Did you mean '$1:$2$3'${2:+ aka $sq$1:./$3$sq}?
+	fatal: path '$2$3' $4, but not ${5:-$SQ$3$SQ}
+	hint: Did you mean '$1:$2$3'${2:+ aka $SQ$1:./$3$SQ}?
 	EOF
-	test_cmp expected error
+	test_i18ncmp expected error
 }
 
 HASH_file=
@@ -104,66 +103,66 @@ test_expect_success 'correct relative file objects (6)' '
 
 test_expect_success 'incorrect revision id' '
 	test_must_fail git rev-parse foobar:file.txt 2>error &&
-	grep "Invalid object name '"'"'foobar'"'"'." error &&
-	test_must_fail git rev-parse foobar 2> error &&
+	test_i18ngrep "invalid object name .foobar." error &&
+	test_must_fail git rev-parse foobar 2>error &&
 	test_i18ngrep "unknown revision or path not in the working tree." error
 '
 
 test_expect_success 'incorrect file in sha1:path' '
-	test_must_fail git rev-parse HEAD:nothing.txt 2> error &&
-	grep "fatal: Path '"'"'nothing.txt'"'"' does not exist in '"'"'HEAD'"'"'" error &&
-	test_must_fail git rev-parse HEAD:index-only.txt 2> error &&
-	grep "fatal: Path '"'"'index-only.txt'"'"' exists on disk, but not in '"'"'HEAD'"'"'." error &&
+	test_must_fail git rev-parse HEAD:nothing.txt 2>error &&
+	test_i18ngrep "path .nothing.txt. does not exist in .HEAD." error &&
+	test_must_fail git rev-parse HEAD:index-only.txt 2>error &&
+	test_i18ngrep "path .index-only.txt. exists on disk, but not in .HEAD." error &&
 	(cd subdir &&
-	 test_must_fail git rev-parse HEAD:file2.txt 2> error &&
+	 test_must_fail git rev-parse HEAD:file2.txt 2>error &&
 	 test_did_you_mean HEAD subdir/ file2.txt exists )
 '
 
 test_expect_success 'incorrect file in :path and :N:path' '
-	test_must_fail git rev-parse :nothing.txt 2> error &&
-	grep "fatal: Path '"'"'nothing.txt'"'"' does not exist (neither on disk nor in the index)." error &&
-	test_must_fail git rev-parse :1:nothing.txt 2> error &&
-	grep "Path '"'"'nothing.txt'"'"' does not exist (neither on disk nor in the index)." error &&
-	test_must_fail git rev-parse :1:file.txt 2> error &&
+	test_must_fail git rev-parse :nothing.txt 2>error &&
+	test_i18ngrep "path .nothing.txt. does not exist (neither on disk nor in the index)" error &&
+	test_must_fail git rev-parse :1:nothing.txt 2>error &&
+	test_i18ngrep "path .nothing.txt. does not exist (neither on disk nor in the index)" error &&
+	test_must_fail git rev-parse :1:file.txt 2>error &&
 	test_did_you_mean ":0" "" file.txt "is in the index" "at stage 1" &&
 	(cd subdir &&
-	 test_must_fail git rev-parse :1:file.txt 2> error &&
+	 test_must_fail git rev-parse :1:file.txt 2>error &&
 	 test_did_you_mean ":0" "" file.txt "is in the index" "at stage 1" &&
-	 test_must_fail git rev-parse :file2.txt 2> error &&
+	 test_must_fail git rev-parse :file2.txt 2>error &&
 	 test_did_you_mean ":0" subdir/ file2.txt "is in the index" &&
-	 test_must_fail git rev-parse :2:file2.txt 2> error &&
+	 test_must_fail git rev-parse :2:file2.txt 2>error &&
 	 test_did_you_mean :0 subdir/ file2.txt "is in the index") &&
-	test_must_fail git rev-parse :disk-only.txt 2> error &&
-	grep "fatal: Path '"'"'disk-only.txt'"'"' exists on disk, but not in the index." error
+	test_must_fail git rev-parse :disk-only.txt 2>error &&
+	test_i18ngrep "path .disk-only.txt. exists on disk, but not in the index" error
 '
 
 test_expect_success 'invalid @{n} reference' '
 	test_must_fail git rev-parse master@{99999} >output 2>error &&
-	test -z "$(cat output)" &&
-	grep "fatal: Log for [^ ]* only has [0-9][0-9]* entries." error  &&
+	test_must_be_empty output &&
+	test_i18ngrep "log for [^ ]* only has [0-9][0-9]* entries" error  &&
 	test_must_fail git rev-parse --verify master@{99999} >output 2>error &&
-	test -z "$(cat output)" &&
-	grep "fatal: Log for [^ ]* only has [0-9][0-9]* entries." error
+	test_must_be_empty output &&
+	test_i18ngrep "log for [^ ]* only has [0-9][0-9]* entries" error
 '
 
 test_expect_success 'relative path not found' '
 	(
 		cd subdir &&
 		test_must_fail git rev-parse HEAD:./nonexistent.txt 2>error &&
-		grep subdir/nonexistent.txt error
+		test_i18ngrep subdir/nonexistent.txt error
 	)
 '
 
 test_expect_success 'relative path outside worktree' '
 	test_must_fail git rev-parse HEAD:../file.txt >output 2>error &&
-	test -z "$(cat output)" &&
+	test_must_be_empty output &&
 	test_i18ngrep "outside repository" error
 '
 
 test_expect_success 'relative path when cwd is outside worktree' '
 	test_must_fail git --git-dir=.git --work-tree=subdir rev-parse HEAD:./file.txt >output 2>error &&
-	test -z "$(cat output)" &&
-	grep "relative path syntax can.t be used outside working tree." error
+	test_must_be_empty output &&
+	test_i18ngrep "relative path syntax can.t be used outside working tree" error
 '
 
 test_expect_success '<commit>:file correctly diagnosed after a pathname' '
@@ -191,6 +190,24 @@ test_expect_success 'dotdot is not an empty set' '
 	test_cmp expect actual
 '
 
+test_expect_success 'dotdot does not peel endpoints' '
+	git tag -a -m "annote" annotated HEAD &&
+	A=$(git rev-parse annotated) &&
+	H=$(git rev-parse annotated^0) &&
+	{
+		echo $A && echo ^$A
+	} >expect-with-two-dots &&
+	{
+		echo $A && echo $A && echo ^$H
+	} >expect-with-merge-base &&
+
+	git rev-parse annotated..annotated >actual-with-two-dots &&
+	test_cmp expect-with-two-dots actual-with-two-dots &&
+
+	git rev-parse annotated...annotated >actual-with-merge-base &&
+	test_cmp expect-with-merge-base actual-with-merge-base
+'
+
 test_expect_success 'arg before dashdash must be a revision (missing)' '
 	test_must_fail git rev-parse foobar -- 2>stderr &&
 	test_i18ngrep "bad revision" stderr
@@ -208,11 +225,33 @@ test_expect_success 'arg before dashdash must be a revision (ambiguous)' '
 	{
 		# we do not want to use rev-parse here, because
 		# we are testing it
-		cat .git/refs/heads/foobar &&
+		git show-ref -s refs/heads/foobar &&
 		printf "%s\n" --
 	} >expect &&
 	git rev-parse foobar -- >actual &&
 	test_cmp expect actual
 '
 
+test_expect_success 'reject Nth parent if N is too high' '
+	test_must_fail git rev-parse HEAD^100000000000000000000000000000000
+'
+
+test_expect_success 'reject Nth ancestor if N is too high' '
+	test_must_fail git rev-parse HEAD~100000000000000000000000000000000
+'
+
+test_expect_success 'pathspecs with wildcards are not ambiguous' '
+	echo "*.c" >expect &&
+	git rev-parse "*.c" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'backslash does not trigger wildcard rule' '
+	test_must_fail git rev-parse "foo\\bar"
+'
+
+test_expect_success 'escaped char does not trigger wildcard rule' '
+	test_must_fail git rev-parse "foo\\*bar"
+'
+
 test_done
diff --git a/third_party/git/t/t1507-rev-parse-upstream.sh b/third_party/git/t/t1507-rev-parse-upstream.sh
index fa3e499641..dfc0d96d8a 100755
--- a/third_party/git/t/t1507-rev-parse-upstream.sh
+++ b/third_party/git/t/t1507-rev-parse-upstream.sh
@@ -28,16 +28,9 @@ test_expect_success 'setup' '
 	)
 '
 
-sq="'"
-
-full_name () {
-	(cd clone &&
-	 git rev-parse --symbolic-full-name "$@")
-}
-
 commit_subject () {
 	(cd clone &&
-	 git show -s --pretty=format:%s "$@")
+	 git show -s --pretty=tformat:%s "$@")
 }
 
 error_message () {
@@ -46,63 +39,78 @@ error_message () {
 }
 
 test_expect_success '@{upstream} resolves to correct full name' '
-	test refs/remotes/origin/master = "$(full_name @{upstream})" &&
-	test refs/remotes/origin/master = "$(full_name @{UPSTREAM})" &&
-	test refs/remotes/origin/master = "$(full_name @{UpSTReam})"
+	echo refs/remotes/origin/master >expect &&
+	git -C clone rev-parse --symbolic-full-name @{upstream} >actual &&
+	test_cmp expect actual &&
+	git -C clone rev-parse --symbolic-full-name @{UPSTREAM} >actual &&
+	test_cmp expect actual &&
+	git -C clone rev-parse --symbolic-full-name @{UpSTReam} >actual &&
+	test_cmp expect actual
 '
 
 test_expect_success '@{u} resolves to correct full name' '
-	test refs/remotes/origin/master = "$(full_name @{u})" &&
-	test refs/remotes/origin/master = "$(full_name @{U})"
+	echo refs/remotes/origin/master >expect &&
+	git -C clone rev-parse --symbolic-full-name @{u} >actual &&
+	test_cmp expect actual &&
+	git -C clone rev-parse --symbolic-full-name @{U} >actual &&
+	test_cmp expect actual
 '
 
 test_expect_success 'my-side@{upstream} resolves to correct full name' '
-	test refs/remotes/origin/side = "$(full_name my-side@{u})"
+	echo refs/remotes/origin/side >expect &&
+	git -C clone rev-parse --symbolic-full-name my-side@{u} >actual &&
+	test_cmp expect actual
 '
 
 test_expect_success 'upstream of branch with @ in middle' '
-	full_name fun@ny@{u} >actual &&
+	git -C clone rev-parse --symbolic-full-name fun@ny@{u} >actual &&
 	echo refs/remotes/origin/side >expect &&
 	test_cmp expect actual &&
-	full_name fun@ny@{U} >actual &&
+	git -C clone rev-parse --symbolic-full-name fun@ny@{U} >actual &&
 	test_cmp expect actual
 '
 
 test_expect_success 'upstream of branch with @ at start' '
-	full_name @funny@{u} >actual &&
+	git -C clone rev-parse --symbolic-full-name @funny@{u} >actual &&
 	echo refs/remotes/origin/side >expect &&
 	test_cmp expect actual
 '
 
 test_expect_success 'upstream of branch with @ at end' '
-	full_name funny@@{u} >actual &&
+	git -C clone rev-parse --symbolic-full-name funny@@{u} >actual &&
 	echo refs/remotes/origin/side >expect &&
 	test_cmp expect actual
 '
 
 test_expect_success 'refs/heads/my-side@{upstream} does not resolve to my-side{upstream}' '
-	test_must_fail full_name refs/heads/my-side@{upstream}
+	test_must_fail git -C clone rev-parse --symbolic-full-name refs/heads/my-side@{upstream}
 '
 
 test_expect_success 'my-side@{u} resolves to correct commit' '
 	git checkout side &&
 	test_commit 5 &&
 	(cd clone && git fetch) &&
-	test 2 = "$(commit_subject my-side)" &&
-	test 5 = "$(commit_subject my-side@{u})"
+	echo 2 >expect &&
+	commit_subject my-side >actual &&
+	test_cmp expect actual &&
+	echo 5 >expect &&
+	commit_subject my-side@{u} >actual
 '
 
 test_expect_success 'not-tracking@{u} fails' '
-	test_must_fail full_name non-tracking@{u} &&
+	test_must_fail git -C clone rev-parse --symbolic-full-name non-tracking@{u} &&
 	(cd clone && git checkout --no-track -b non-tracking) &&
-	test_must_fail full_name non-tracking@{u}
+	test_must_fail git -C clone rev-parse --symbolic-full-name non-tracking@{u}
 '
 
 test_expect_success '<branch>@{u}@{1} resolves correctly' '
 	test_commit 6 &&
 	(cd clone && git fetch) &&
-	test 5 = $(commit_subject my-side@{u}@{1}) &&
-	test 5 = $(commit_subject my-side@{U}@{1})
+	echo 5 >expect &&
+	commit_subject my-side@{u}@{1} >actual &&
+	test_cmp expect actual &&
+	commit_subject my-side@{U}@{1} >actual &&
+	test_cmp expect actual
 '
 
 test_expect_success '@{u} without specifying branch fails on a detached HEAD' '
@@ -129,7 +137,7 @@ test_expect_success 'merge my-side@{u} records the correct name' '
 	git branch -t new my-side@{u} &&
 	git merge -s ours new@{u} &&
 	git show -s --pretty=tformat:%s >actual &&
-	echo "Merge remote-tracking branch ${sq}origin/side${sq}" >expect &&
+	echo "Merge remote-tracking branch ${SQ}origin/side${SQ}" >expect &&
 	test_cmp expect actual
 )
 '
@@ -151,12 +159,14 @@ test_expect_success 'checkout other@{u}' '
 '
 
 test_expect_success 'branch@{u} works when tracking a local branch' '
-	test refs/heads/master = "$(full_name local-master@{u})"
+	echo refs/heads/master >expect &&
+	git -C clone rev-parse --symbolic-full-name local-master@{u} >actual &&
+	test_cmp expect actual
 '
 
 test_expect_success 'branch@{u} error message when no upstream' '
 	cat >expect <<-EOF &&
-	fatal: no upstream configured for branch ${sq}non-tracking${sq}
+	fatal: no upstream configured for branch ${SQ}non-tracking${SQ}
 	EOF
 	error_message non-tracking@{u} &&
 	test_i18ncmp expect error
@@ -164,7 +174,7 @@ test_expect_success 'branch@{u} error message when no upstream' '
 
 test_expect_success '@{u} error message when no upstream' '
 	cat >expect <<-EOF &&
-	fatal: no upstream configured for branch ${sq}master${sq}
+	fatal: no upstream configured for branch ${SQ}master${SQ}
 	EOF
 	test_must_fail git rev-parse --verify @{u} 2>actual &&
 	test_i18ncmp expect actual
@@ -172,7 +182,7 @@ test_expect_success '@{u} error message when no upstream' '
 
 test_expect_success 'branch@{u} error message with misspelt branch' '
 	cat >expect <<-EOF &&
-	fatal: no such branch: ${sq}no-such-branch${sq}
+	fatal: no such branch: ${SQ}no-such-branch${SQ}
 	EOF
 	error_message no-such-branch@{u} &&
 	test_i18ncmp expect error
@@ -189,7 +199,7 @@ test_expect_success '@{u} error message when not on a branch' '
 
 test_expect_success 'branch@{u} error message if upstream branch not fetched' '
 	cat >expect <<-EOF &&
-	fatal: upstream branch ${sq}refs/heads/side${sq} not stored as a remote-tracking branch
+	fatal: upstream branch ${SQ}refs/heads/side${SQ} not stored as a remote-tracking branch
 	EOF
 	error_message bad-upstream@{u} &&
 	test_i18ncmp expect error
@@ -205,35 +215,37 @@ test_expect_success 'pull works when tracking a local branch' '
 
 # makes sense if the previous one succeeded
 test_expect_success '@{u} works when tracking a local branch' '
-	test refs/heads/master = "$(full_name @{u})"
+	echo refs/heads/master >expect &&
+	git -C clone rev-parse --symbolic-full-name @{u} >actual &&
+	test_cmp expect actual
 '
 
-commit=$(git rev-parse HEAD)
-cat >expect <<EOF
-commit $commit
-Reflog: master@{0} (C O Mitter <committer@example.com>)
-Reflog message: branch: Created from HEAD
-Author: A U Thor <author@example.com>
-Date:   Thu Apr 7 15:15:13 2005 -0700
-
-    3
-EOF
 test_expect_success 'log -g other@{u}' '
+	commit=$(git rev-parse HEAD) &&
+	cat >expect <<-EOF &&
+	commit $commit
+	Reflog: master@{0} (C O Mitter <committer@example.com>)
+	Reflog message: branch: Created from HEAD
+	Author: A U Thor <author@example.com>
+	Date:   Thu Apr 7 15:15:13 2005 -0700
+
+	    3
+	EOF
 	git log -1 -g other@{u} >actual &&
 	test_cmp expect actual
 '
 
-cat >expect <<EOF
-commit $commit
-Reflog: master@{Thu Apr 7 15:17:13 2005 -0700} (C O Mitter <committer@example.com>)
-Reflog message: branch: Created from HEAD
-Author: A U Thor <author@example.com>
-Date:   Thu Apr 7 15:15:13 2005 -0700
-
-    3
-EOF
-
 test_expect_success 'log -g other@{u}@{now}' '
+	commit=$(git rev-parse HEAD) &&
+	cat >expect <<-EOF &&
+	commit $commit
+	Reflog: master@{Thu Apr 7 15:17:13 2005 -0700} (C O Mitter <committer@example.com>)
+	Reflog message: branch: Created from HEAD
+	Author: A U Thor <author@example.com>
+	Date:   Thu Apr 7 15:15:13 2005 -0700
+
+	    3
+	EOF
 	git log -1 -g other@{u}@{now} >actual &&
 	test_cmp expect actual
 '
diff --git a/third_party/git/t/t1509-root-work-tree.sh b/third_party/git/t/t1509-root-work-tree.sh
index 553a3f601b..fd2f7abf1c 100755
--- a/third_party/git/t/t1509-root-work-tree.sh
+++ b/third_party/git/t/t1509-root-work-tree.sh
@@ -221,7 +221,7 @@ test_expect_success 'setup' '
 	rm -rf /.git &&
 	echo "Initialized empty Git repository in /.git/" > expected &&
 	git init > result &&
-	test_cmp expected result
+	test_i18ncmp expected result
 '
 
 test_vars 'auto gitdir, root' ".git" "/" ""
@@ -246,7 +246,7 @@ test_expect_success 'setup' '
 	cd / &&
 	echo "Initialized empty Git repository in /" > expected &&
 	git init --bare > result &&
-	test_cmp expected result
+	test_i18ncmp expected result
 '
 
 test_vars 'auto gitdir, root' "." "" ""
diff --git a/third_party/git/t/t1512-rev-parse-disambiguation.sh b/third_party/git/t/t1512-rev-parse-disambiguation.sh
index c19fb500cb..18fa6cf40d 100755
--- a/third_party/git/t/t1512-rev-parse-disambiguation.sh
+++ b/third_party/git/t/t1512-rev-parse-disambiguation.sh
@@ -282,7 +282,7 @@ test_expect_success 'rev-parse --disambiguate' '
 	# commits created by commit-tree in earlier tests share a
 	# different prefix.
 	git rev-parse --disambiguate=000000000 >actual &&
-	test $(wc -l <actual) = 16 &&
+	test_line_count = 16 actual &&
 	test "$(sed -e "s/^\(.........\).*/\1/" actual | sort -u)" = 000000000
 '
 
@@ -339,7 +339,7 @@ test_expect_success C_LOCALE_OUTPUT 'ambiguity hints' '
 test_expect_success C_LOCALE_OUTPUT 'ambiguity hints respect type' '
 	test_must_fail git rev-parse 000000000^{commit} 2>stderr &&
 	grep ^hint: stderr >hints &&
-	# 5 commits, 1 tag (which is a commitish), plus intro line
+	# 5 commits, 1 tag (which is a committish), plus intro line
 	test_line_count = 7 hints
 '
 
diff --git a/third_party/git/t/t1600-index.sh b/third_party/git/t/t1600-index.sh
index 42962ed7d4..b7c31aa86a 100755
--- a/third_party/git/t/t1600-index.sh
+++ b/third_party/git/t/t1600-index.sh
@@ -59,17 +59,42 @@ test_expect_success 'out of bounds index.version issues warning' '
 	)
 '
 
-test_expect_success 'GIT_INDEX_VERSION takes precedence over config' '
+test_index_version () {
+	INDEX_VERSION_CONFIG=$1 &&
+	FEATURE_MANY_FILES=$2 &&
+	ENV_VAR_VERSION=$3
+	EXPECTED_OUTPUT_VERSION=$4 &&
 	(
 		rm -f .git/index &&
-		GIT_INDEX_VERSION=4 &&
-		export GIT_INDEX_VERSION &&
-		git config --add index.version 2 &&
+		rm -f .git/config &&
+		if test "$INDEX_VERSION_CONFIG" -ne 0
+		then
+			git config --add index.version $INDEX_VERSION_CONFIG
+		fi &&
+		git config --add feature.manyFiles $FEATURE_MANY_FILES
+		if test "$ENV_VAR_VERSION" -ne 0
+		then
+			GIT_INDEX_VERSION=$ENV_VAR_VERSION &&
+			export GIT_INDEX_VERSION
+		else
+			unset GIT_INDEX_VERSION
+		fi &&
 		git add a 2>&1 &&
-		echo 4 >expect &&
+		echo $EXPECTED_OUTPUT_VERSION >expect &&
 		test-tool index-version <.git/index >actual &&
 		test_cmp expect actual
 	)
+}
+
+test_expect_success 'index version config precedence' '
+	test_index_version 0 false 0 2 &&
+	test_index_version 2 false 0 2 &&
+	test_index_version 3 false 0 2 &&
+	test_index_version 4 false 0 4 &&
+	test_index_version 2 false 4 4 &&
+	test_index_version 2 true 0 2 &&
+	test_index_version 0 true 0 4 &&
+	test_index_version 0 true 2 2
 '
 
 test_done
diff --git a/third_party/git/t/t2013-checkout-submodule.sh b/third_party/git/t/t2013-checkout-submodule.sh
index 8f86b5f4b2..b2bdd1fcb4 100755
--- a/third_party/git/t/t2013-checkout-submodule.sh
+++ b/third_party/git/t/t2013-checkout-submodule.sh
@@ -68,8 +68,8 @@ test_submodule_switch_recursing_with_args "checkout"
 
 test_submodule_forced_switch_recursing_with_args "checkout -f"
 
-test_submodule_switch "git checkout"
+test_submodule_switch "checkout"
 
-test_submodule_forced_switch "git checkout -f"
+test_submodule_forced_switch "checkout -f"
 
 test_done
diff --git a/third_party/git/t/t2018-checkout-branch.sh b/third_party/git/t/t2018-checkout-branch.sh
index 822381dd9d..5f761bc616 100755
--- a/third_party/git/t/t2018-checkout-branch.sh
+++ b/third_party/git/t/t2018-checkout-branch.sh
@@ -1,50 +1,76 @@
 #!/bin/sh
 
-test_description='checkout '
+test_description='checkout'
 
 . ./test-lib.sh
 
-# Arguments: <branch> <sha> [<checkout options>]
+# Arguments: [!] <branch> <oid> [<checkout options>]
 #
 # Runs "git checkout" to switch to <branch>, testing that
 #
 #   1) we are on the specified branch, <branch>;
-#   2) HEAD is <sha>; if <sha> is not specified, the old HEAD is used.
+#   2) HEAD is <oid>; if <oid> is not specified, the old HEAD is used.
 #
 # If <checkout options> is not specified, "git checkout" is run with -b.
-do_checkout() {
+#
+# If the first argument is `!`, "git checkout" is expected to fail when
+# it is run.
+do_checkout () {
+	should_fail= &&
+	if test "x$1" = "x!"
+	then
+		should_fail=yes &&
+		shift
+	fi &&
 	exp_branch=$1 &&
 	exp_ref="refs/heads/$exp_branch" &&
 
-	# if <sha> is not specified, use HEAD.
-	exp_sha=${2:-$(git rev-parse --verify HEAD)} &&
+	# if <oid> is not specified, use HEAD.
+	exp_oid=${2:-$(git rev-parse --verify HEAD)} &&
 
 	# default options for git checkout: -b
-	if [ -z "$3" ]; then
+	if test -z "$3"
+	then
 		opts="-b"
 	else
 		opts="$3"
 	fi
 
-	git checkout $opts $exp_branch $exp_sha &&
+	if test -n "$should_fail"
+	then
+		test_must_fail git checkout $opts $exp_branch $exp_oid
+	else
+		git checkout $opts $exp_branch $exp_oid &&
+		echo "$exp_ref" >ref.expect &&
+		git rev-parse --symbolic-full-name HEAD >ref.actual &&
+		test_cmp ref.expect ref.actual &&
+		echo "$exp_oid" >oid.expect &&
+		git rev-parse --verify HEAD >oid.actual &&
+		test_cmp oid.expect oid.actual
+	fi
+}
 
-	test $exp_ref = $(git rev-parse --symbolic-full-name HEAD) &&
-	test $exp_sha = $(git rev-parse --verify HEAD)
+test_dirty_unmergeable () {
+	test_expect_code 1 git diff --exit-code
 }
 
-test_dirty_unmergeable() {
-	! git diff --exit-code >/dev/null
+test_dirty_unmergeable_discards_changes () {
+	git diff --exit-code
 }
 
-setup_dirty_unmergeable() {
+setup_dirty_unmergeable () {
 	echo >>file1 change2
 }
 
-test_dirty_mergeable() {
-	! git diff --cached --exit-code >/dev/null
+test_dirty_mergeable () {
+	test_expect_code 1 git diff --cached --exit-code
+}
+
+test_dirty_mergeable_discards_changes () {
+	git diff --cached --exit-code
 }
 
-setup_dirty_mergeable() {
+setup_dirty_mergeable () {
 	echo >file2 file2 &&
 	git add file2
 }
@@ -82,7 +108,7 @@ test_expect_success 'checkout -b to a new branch, set to an explicit ref' '
 
 test_expect_success 'checkout -b to a new branch with unmergeable changes fails' '
 	setup_dirty_unmergeable &&
-	test_must_fail do_checkout branch2 $HEAD1 &&
+	do_checkout ! branch2 $HEAD1 &&
 	test_dirty_unmergeable
 '
 
@@ -93,7 +119,7 @@ test_expect_success 'checkout -f -b to a new branch with unmergeable changes dis
 
 	# still dirty and on branch1
 	do_checkout branch2 $HEAD1 "-f -b" &&
-	test_must_fail test_dirty_unmergeable
+	test_dirty_unmergeable_discards_changes
 '
 
 test_expect_success 'checkout -b to a new branch preserves mergeable changes' '
@@ -111,12 +137,12 @@ test_expect_success 'checkout -f -b to a new branch with mergeable changes disca
 	test_when_finished git reset --hard HEAD &&
 	setup_dirty_mergeable &&
 	do_checkout branch2 $HEAD1 "-f -b" &&
-	test_must_fail test_dirty_mergeable
+	test_dirty_mergeable_discards_changes
 '
 
 test_expect_success 'checkout -b to an existing branch fails' '
 	test_when_finished git reset --hard HEAD &&
-	test_must_fail do_checkout branch2 $HEAD2
+	do_checkout ! branch2 $HEAD2
 '
 
 test_expect_success 'checkout -b to @{-1} fails with the right branch name' '
@@ -140,7 +166,8 @@ test_expect_success 'checkout -B to a merge base' '
 '
 
 test_expect_success 'checkout -B to an existing branch from detached HEAD resets branch to HEAD' '
-	git checkout $(git rev-parse --verify HEAD) &&
+	head=$(git rev-parse --verify HEAD) &&
+	git checkout "$head" &&
 
 	do_checkout branch2 "" -B
 '
@@ -155,14 +182,14 @@ test_expect_success 'checkout -B to an existing branch with unmergeable changes
 	git checkout branch1 &&
 
 	setup_dirty_unmergeable &&
-	test_must_fail do_checkout branch2 $HEAD1 -B &&
+	do_checkout ! branch2 $HEAD1 -B &&
 	test_dirty_unmergeable
 '
 
 test_expect_success 'checkout -f -B to an existing branch with unmergeable changes discards changes' '
 	# still dirty and on branch1
 	do_checkout branch2 $HEAD1 "-f -B" &&
-	test_must_fail test_dirty_unmergeable
+	test_dirty_unmergeable_discards_changes
 '
 
 test_expect_success 'checkout -B to an existing branch preserves mergeable changes' '
@@ -179,7 +206,7 @@ test_expect_success 'checkout -f -B to an existing branch with mergeable changes
 
 	setup_dirty_mergeable &&
 	do_checkout branch2 $HEAD1 "-f -B" &&
-	test_must_fail test_dirty_mergeable
+	test_dirty_mergeable_discards_changes
 '
 
 test_expect_success 'checkout -b <describe>' '
@@ -211,4 +238,36 @@ test_expect_success 'checkout -b after clone --no-checkout does a checkout of HE
 	test_path_is_file dest/a.t
 '
 
+test_expect_success 'checkout -b to a new branch preserves mergeable changes despite sparse-checkout' '
+	test_when_finished "
+		git reset --hard &&
+		git checkout branch1-scratch &&
+		test_might_fail git branch -D branch3 &&
+		git config core.sparseCheckout false &&
+		rm .git/info/sparse-checkout" &&
+
+	test_commit file2 &&
+
+	echo stuff >>file1 &&
+	echo file2 >.git/info/sparse-checkout &&
+	git config core.sparseCheckout true &&
+
+	CURHEAD=$(git rev-parse HEAD) &&
+	do_checkout branch3 $CURHEAD &&
+
+	echo file1 >expect &&
+	git diff --name-only >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'checkout -b rejects an invalid start point' '
+	test_must_fail git checkout -b branch4 file1 2>err &&
+	test_i18ngrep "is not a commit" err
+'
+
+test_expect_success 'checkout -b rejects an extra path argument' '
+	test_must_fail git checkout -b branch5 branch1 file1 2>err &&
+	test_i18ngrep "Cannot update paths and switch to branch" err
+'
+
 test_done
diff --git a/third_party/git/t/t2022-checkout-paths.sh b/third_party/git/t/t2022-checkout-paths.sh
index fc3eb43b89..6844afafc0 100755
--- a/third_party/git/t/t2022-checkout-paths.sh
+++ b/third_party/git/t/t2022-checkout-paths.sh
@@ -78,4 +78,15 @@ test_expect_success 'do not touch files that are already up-to-date' '
 	test_cmp expect actual
 '
 
+test_expect_success 'checkout HEAD adds deleted intent-to-add file back to index' '
+	echo "nonempty" >nonempty &&
+	>empty &&
+	git add nonempty empty &&
+	git commit -m "create files to be deleted" &&
+	git rm --cached nonempty empty &&
+	git add -N nonempty empty &&
+	git checkout HEAD nonempty empty &&
+	git diff --cached --exit-code
+'
+
 test_done
diff --git a/third_party/git/t/t2024-checkout-dwim.sh b/third_party/git/t/t2024-checkout-dwim.sh
index fa0718c730..accfa9aa4b 100755
--- a/third_party/git/t/t2024-checkout-dwim.sh
+++ b/third_party/git/t/t2024-checkout-dwim.sh
@@ -37,7 +37,9 @@ test_expect_success 'setup' '
 		git checkout -b foo &&
 		test_commit a_foo &&
 		git checkout -b bar &&
-		test_commit a_bar
+		test_commit a_bar &&
+		git checkout -b ambiguous_branch_and_file &&
+		test_commit a_ambiguous_branch_and_file
 	) &&
 	git init repo_b &&
 	(
@@ -46,7 +48,9 @@ test_expect_success 'setup' '
 		git checkout -b foo &&
 		test_commit b_foo &&
 		git checkout -b baz &&
-		test_commit b_baz
+		test_commit b_baz &&
+		git checkout -b ambiguous_branch_and_file &&
+		test_commit b_ambiguous_branch_and_file
 	) &&
 	git remote add repo_a repo_a &&
 	git remote add repo_b repo_b &&
@@ -75,6 +79,26 @@ test_expect_success 'checkout of branch from multiple remotes fails #1' '
 	test_branch master
 '
 
+test_expect_success 'when arg matches multiple remotes, do not fallback to interpreting as pathspec' '
+	# create a file with name matching remote branch name
+	git checkout -b t_ambiguous_branch_and_file &&
+	>ambiguous_branch_and_file &&
+	git add ambiguous_branch_and_file &&
+	git commit -m "ambiguous_branch_and_file" &&
+
+	# modify file to verify that it will not be touched by checkout
+	test_when_finished "git checkout -- ambiguous_branch_and_file" &&
+	echo "file contents" >ambiguous_branch_and_file &&
+	cp ambiguous_branch_and_file expect &&
+
+	test_must_fail git checkout ambiguous_branch_and_file 2>err &&
+
+	test_i18ngrep "matched multiple (2) remote tracking branches" err &&
+
+	# file must not be altered
+	test_cmp expect ambiguous_branch_and_file
+'
+
 test_expect_success 'checkout of branch from multiple remotes fails with advice' '
 	git checkout -B master &&
 	test_might_fail git branch -D foo &&
diff --git a/third_party/git/t/t2025-checkout-no-overlay.sh b/third_party/git/t/t2025-checkout-no-overlay.sh
index 76330cb5ab..fa9e098706 100755
--- a/third_party/git/t/t2025-checkout-no-overlay.sh
+++ b/third_party/git/t/t2025-checkout-no-overlay.sh
@@ -44,4 +44,16 @@ test_expect_success '--no-overlay --theirs with D/F conflict deletes file' '
 	test_path_is_missing file1
 '
 
+test_expect_success 'wildcard pathspec matches file in subdirectory' '
+	git reset --hard &&
+	mkdir subdir &&
+	test_commit file3-1 subdir/file3 &&
+	test_commit file3-2 subdir/file3 &&
+
+	git checkout --no-overlay file3-1 "*file3" &&
+	echo file3-1 >expect &&
+	test_path_is_file subdir/file3 &&
+	test_cmp expect subdir/file3
+'
+
 test_done
diff --git a/third_party/git/t/t2026-checkout-pathspec-file.sh b/third_party/git/t/t2026-checkout-pathspec-file.sh
new file mode 100755
index 0000000000..43d31d7948
--- /dev/null
+++ b/third_party/git/t/t2026-checkout-pathspec-file.sh
@@ -0,0 +1,163 @@
+#!/bin/sh
+
+test_description='checkout --pathspec-from-file'
+
+. ./test-lib.sh
+
+test_tick
+
+test_expect_success setup '
+	test_commit file0 &&
+
+	echo 1 >fileA.t &&
+	echo 1 >fileB.t &&
+	echo 1 >fileC.t &&
+	echo 1 >fileD.t &&
+	git add fileA.t fileB.t fileC.t fileD.t &&
+	git commit -m "files 1" &&
+
+	echo 2 >fileA.t &&
+	echo 2 >fileB.t &&
+	echo 2 >fileC.t &&
+	echo 2 >fileD.t &&
+	git add fileA.t fileB.t fileC.t fileD.t &&
+	git commit -m "files 2" &&
+
+	git tag checkpoint
+'
+
+restore_checkpoint () {
+	git reset --hard checkpoint
+}
+
+verify_expect () {
+	git status --porcelain --untracked-files=no -- fileA.t fileB.t fileC.t fileD.t >actual &&
+	test_cmp expect actual
+}
+
+test_expect_success '--pathspec-from-file from stdin' '
+	restore_checkpoint &&
+
+	echo fileA.t | git checkout --pathspec-from-file=- HEAD^1 &&
+
+	cat >expect <<-\EOF &&
+	M  fileA.t
+	EOF
+	verify_expect
+'
+
+test_expect_success '--pathspec-from-file from file' '
+	restore_checkpoint &&
+
+	echo fileA.t >list &&
+	git checkout --pathspec-from-file=list HEAD^1 &&
+
+	cat >expect <<-\EOF &&
+	M  fileA.t
+	EOF
+	verify_expect
+'
+
+test_expect_success 'NUL delimiters' '
+	restore_checkpoint &&
+
+	printf "fileA.t\0fileB.t\0" | git checkout --pathspec-from-file=- --pathspec-file-nul HEAD^1 &&
+
+	cat >expect <<-\EOF &&
+	M  fileA.t
+	M  fileB.t
+	EOF
+	verify_expect
+'
+
+test_expect_success 'LF delimiters' '
+	restore_checkpoint &&
+
+	printf "fileA.t\nfileB.t\n" | git checkout --pathspec-from-file=- HEAD^1 &&
+
+	cat >expect <<-\EOF &&
+	M  fileA.t
+	M  fileB.t
+	EOF
+	verify_expect
+'
+
+test_expect_success 'no trailing delimiter' '
+	restore_checkpoint &&
+
+	printf "fileA.t\nfileB.t" | git checkout --pathspec-from-file=- HEAD^1 &&
+
+	cat >expect <<-\EOF &&
+	M  fileA.t
+	M  fileB.t
+	EOF
+	verify_expect
+'
+
+test_expect_success 'CRLF delimiters' '
+	restore_checkpoint &&
+
+	printf "fileA.t\r\nfileB.t\r\n" | git checkout --pathspec-from-file=- HEAD^1 &&
+
+	cat >expect <<-\EOF &&
+	M  fileA.t
+	M  fileB.t
+	EOF
+	verify_expect
+'
+
+test_expect_success 'quotes' '
+	restore_checkpoint &&
+
+	cat >list <<-\EOF &&
+	"file\101.t"
+	EOF
+
+	git checkout --pathspec-from-file=list HEAD^1 &&
+
+	cat >expect <<-\EOF &&
+	M  fileA.t
+	EOF
+	verify_expect
+'
+
+test_expect_success 'quotes not compatible with --pathspec-file-nul' '
+	restore_checkpoint &&
+
+	cat >list <<-\EOF &&
+	"file\101.t"
+	EOF
+
+	test_must_fail git checkout --pathspec-from-file=list --pathspec-file-nul HEAD^1
+'
+
+test_expect_success 'only touches what was listed' '
+	restore_checkpoint &&
+
+	printf "fileB.t\nfileC.t\n" | git checkout --pathspec-from-file=- HEAD^1 &&
+
+	cat >expect <<-\EOF &&
+	M  fileB.t
+	M  fileC.t
+	EOF
+	verify_expect
+'
+
+test_expect_success 'error conditions' '
+	restore_checkpoint &&
+	echo fileA.t >list &&
+
+	test_must_fail git checkout --pathspec-from-file=list --detach 2>err &&
+	test_i18ngrep -e "--pathspec-from-file is incompatible with --detach" err &&
+
+	test_must_fail git checkout --pathspec-from-file=list --patch 2>err &&
+	test_i18ngrep -e "--pathspec-from-file is incompatible with --patch" err &&
+
+	test_must_fail git checkout --pathspec-from-file=list -- fileA.t 2>err &&
+	test_i18ngrep -e "--pathspec-from-file is incompatible with pathspec arguments" err &&
+
+	test_must_fail git checkout --pathspec-file-nul 2>err &&
+	test_i18ngrep -e "--pathspec-file-nul requires --pathspec-from-file" err
+'
+
+test_done
diff --git a/third_party/git/t/t2027-checkout-track.sh b/third_party/git/t/t2027-checkout-track.sh
new file mode 100755
index 0000000000..bcba1bf90c
--- /dev/null
+++ b/third_party/git/t/t2027-checkout-track.sh
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+test_description='tests for git branch --track'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	test_commit one &&
+	test_commit two
+'
+
+test_expect_success 'checkout --track -b creates a new tracking branch' '
+	git checkout --track -b branch1 master &&
+	test $(git rev-parse --abbrev-ref HEAD) = branch1 &&
+	test $(git config --get branch.branch1.remote) = . &&
+	test $(git config --get branch.branch1.merge) = refs/heads/master
+'
+
+test_expect_success 'checkout --track -b rejects an extra path argument' '
+	test_must_fail git checkout --track -b branch2 master one.t 2>err &&
+	test_i18ngrep "cannot be used with updating paths" err
+'
+
+test_done
diff --git a/third_party/git/t/t2060-switch.sh b/third_party/git/t/t2060-switch.sh
index f9efa29dfb..2c1b8c0d6d 100755
--- a/third_party/git/t/t2060-switch.sh
+++ b/third_party/git/t/t2060-switch.sh
@@ -68,6 +68,14 @@ test_expect_success 'new orphan branch from empty' '
 	test_cmp expected tracked-files
 '
 
+test_expect_success 'orphan branch works with --discard-changes' '
+	test_when_finished git switch master &&
+	echo foo >foo.txt &&
+	git switch --discard-changes --orphan new-orphan2 &&
+	git ls-files >tracked-files &&
+	test_must_be_empty tracked-files
+'
+
 test_expect_success 'switching ignores file of same branch name' '
 	test_when_finished git switch master &&
 	: >first-branch &&
diff --git a/third_party/git/t/t2070-restore.sh b/third_party/git/t/t2070-restore.sh
index 2650df1966..89e5a142c9 100755
--- a/third_party/git/t/t2070-restore.sh
+++ b/third_party/git/t/t2070-restore.sh
@@ -69,6 +69,17 @@ test_expect_success 'restore --staged uses HEAD as source' '
 	test_cmp expected actual
 '
 
+test_expect_success 'restore --worktree --staged uses HEAD as source' '
+	test_when_finished git reset --hard &&
+	git show HEAD:./first.t >expected &&
+	echo dirty >>first.t &&
+	git add first.t &&
+	git restore --worktree --staged first.t &&
+	git show :./first.t >actual &&
+	test_cmp expected actual &&
+	test_cmp expected first.t
+'
+
 test_expect_success 'restore --ignore-unmerged ignores unmerged entries' '
 	git init unmerged &&
 	(
@@ -95,4 +106,32 @@ test_expect_success 'restore --ignore-unmerged ignores unmerged entries' '
 	)
 '
 
+test_expect_success 'restore --staged adds deleted intent-to-add file back to index' '
+	echo "nonempty" >nonempty &&
+	>empty &&
+	git add nonempty empty &&
+	git commit -m "create files to be deleted" &&
+	git rm --cached nonempty empty &&
+	git add -N nonempty empty &&
+	git restore --staged nonempty empty &&
+	git diff --cached --exit-code
+'
+
+test_expect_success 'restore --staged invalidates cache tree for deletions' '
+	test_when_finished git reset --hard &&
+	>new1 &&
+	>new2 &&
+	git add new1 new2 &&
+
+	# It is important to commit and then reset here, so that the index
+	# contains a valid cache-tree for the "both" tree.
+	git commit -m both &&
+	git reset --soft HEAD^ &&
+
+	git restore --staged new1 &&
+	git commit -m "just new2" &&
+	git rev-parse HEAD:new2 &&
+	test_must_fail git rev-parse HEAD:new1
+'
+
 test_done
diff --git a/third_party/git/t/t2072-restore-pathspec-file.sh b/third_party/git/t/t2072-restore-pathspec-file.sh
new file mode 100755
index 0000000000..b48345bf95
--- /dev/null
+++ b/third_party/git/t/t2072-restore-pathspec-file.sh
@@ -0,0 +1,177 @@
+#!/bin/sh
+
+test_description='restore --pathspec-from-file'
+
+. ./test-lib.sh
+
+test_tick
+
+test_expect_success setup '
+	test_commit file0 &&
+
+	mkdir dir1 &&
+	echo 1 >dir1/file &&
+	echo 1 >fileA.t &&
+	echo 1 >fileB.t &&
+	echo 1 >fileC.t &&
+	echo 1 >fileD.t &&
+	git add dir1 fileA.t fileB.t fileC.t fileD.t &&
+	git commit -m "files 1" &&
+
+	echo 2 >dir1/file &&
+	echo 2 >fileA.t &&
+	echo 2 >fileB.t &&
+	echo 2 >fileC.t &&
+	echo 2 >fileD.t &&
+	git add dir1 fileA.t fileB.t fileC.t fileD.t &&
+	git commit -m "files 2" &&
+
+	git tag checkpoint
+'
+
+restore_checkpoint () {
+	git reset --hard checkpoint
+}
+
+verify_expect () {
+	git status --porcelain --untracked-files=no -- dir1 fileA.t fileB.t fileC.t fileD.t >actual &&
+	test_cmp expect actual
+}
+
+test_expect_success '--pathspec-from-file from stdin' '
+	restore_checkpoint &&
+
+	echo fileA.t | git restore --pathspec-from-file=- --source=HEAD^1 &&
+
+	cat >expect <<-\EOF &&
+	 M fileA.t
+	EOF
+	verify_expect
+'
+
+test_expect_success '--pathspec-from-file from file' '
+	restore_checkpoint &&
+
+	echo fileA.t >list &&
+	git restore --pathspec-from-file=list --source=HEAD^1 &&
+
+	cat >expect <<-\EOF &&
+	 M fileA.t
+	EOF
+	verify_expect
+'
+
+test_expect_success 'NUL delimiters' '
+	restore_checkpoint &&
+
+	printf "fileA.t\0fileB.t\0" | git restore --pathspec-from-file=- --pathspec-file-nul --source=HEAD^1 &&
+
+	cat >expect <<-\EOF &&
+	 M fileA.t
+	 M fileB.t
+	EOF
+	verify_expect
+'
+
+test_expect_success 'LF delimiters' '
+	restore_checkpoint &&
+
+	printf "fileA.t\nfileB.t\n" | git restore --pathspec-from-file=- --source=HEAD^1 &&
+
+	cat >expect <<-\EOF &&
+	 M fileA.t
+	 M fileB.t
+	EOF
+	verify_expect
+'
+
+test_expect_success 'no trailing delimiter' '
+	restore_checkpoint &&
+
+	printf "fileA.t\nfileB.t" | git restore --pathspec-from-file=- --source=HEAD^1 &&
+
+	cat >expect <<-\EOF &&
+	 M fileA.t
+	 M fileB.t
+	EOF
+	verify_expect
+'
+
+test_expect_success 'CRLF delimiters' '
+	restore_checkpoint &&
+
+	printf "fileA.t\r\nfileB.t\r\n" | git restore --pathspec-from-file=- --source=HEAD^1 &&
+
+	cat >expect <<-\EOF &&
+	 M fileA.t
+	 M fileB.t
+	EOF
+	verify_expect
+'
+
+test_expect_success 'quotes' '
+	restore_checkpoint &&
+
+	cat >list <<-\EOF &&
+	"file\101.t"
+	EOF
+
+	git restore --pathspec-from-file=list --source=HEAD^1 &&
+
+	cat >expect <<-\EOF &&
+	 M fileA.t
+	EOF
+	verify_expect
+'
+
+test_expect_success 'quotes not compatible with --pathspec-file-nul' '
+	restore_checkpoint &&
+
+	cat >list <<-\EOF &&
+	"file\101.t"
+	EOF
+
+	test_must_fail git restore --pathspec-from-file=list --pathspec-file-nul --source=HEAD^1
+'
+
+test_expect_success 'only touches what was listed' '
+	restore_checkpoint &&
+
+	printf "fileB.t\nfileC.t\n" | git restore --pathspec-from-file=- --source=HEAD^1 &&
+
+	cat >expect <<-\EOF &&
+	 M fileB.t
+	 M fileC.t
+	EOF
+	verify_expect
+'
+
+test_expect_success 'error conditions' '
+	restore_checkpoint &&
+	echo fileA.t >list &&
+	>empty_list &&
+
+	test_must_fail git restore --pathspec-from-file=list --patch --source=HEAD^1 2>err &&
+	test_i18ngrep -e "--pathspec-from-file is incompatible with --patch" err &&
+
+	test_must_fail git restore --pathspec-from-file=list --source=HEAD^1 -- fileA.t 2>err &&
+	test_i18ngrep -e "--pathspec-from-file is incompatible with pathspec arguments" err &&
+
+	test_must_fail git restore --pathspec-file-nul --source=HEAD^1 2>err &&
+	test_i18ngrep -e "--pathspec-file-nul requires --pathspec-from-file" err &&
+
+	test_must_fail git restore --pathspec-from-file=empty_list --source=HEAD^1 2>err &&
+	test_i18ngrep -e "you must specify path(s) to restore" err
+'
+
+test_expect_success 'wildcard pathspec matches file in subdirectory' '
+	restore_checkpoint &&
+
+	echo "*file" | git restore --pathspec-from-file=- --source=HEAD^1 &&
+	cat >expect <<-\EOF &&
+	 M dir1/file
+	EOF
+	verify_expect
+'
+
+test_done
diff --git a/third_party/git/t/t2107-update-index-basic.sh b/third_party/git/t/t2107-update-index-basic.sh
index 2242cd098e..a30b7ca6bc 100755
--- a/third_party/git/t/t2107-update-index-basic.sh
+++ b/third_party/git/t/t2107-update-index-basic.sh
@@ -9,7 +9,6 @@ Tests for command-line parsing and basic operation.
 
 test_expect_success 'update-index --nonsense fails' '
 	test_must_fail git update-index --nonsense 2>msg &&
-	cat msg &&
 	test -s msg
 '
 
diff --git a/third_party/git/t/t2203-add-intent.sh b/third_party/git/t/t2203-add-intent.sh
index 5bbe8dcce4..cf0175ad6e 100755
--- a/third_party/git/t/t2203-add-intent.sh
+++ b/third_party/git/t/t2203-add-intent.sh
@@ -232,17 +232,53 @@ test_expect_success 'double rename detection in status' '
 	)
 '
 
-test_expect_success 'diff-files/diff-cached shows ita as new/not-new files' '
+test_expect_success 'i-t-a files shown as new for "diff", "diff-files"; not-new for "diff --cached"' '
 	git reset --hard &&
-	echo new >new-ita &&
-	git add -N new-ita &&
+	: >empty &&
+	content="foo" &&
+	echo "$content" >not-empty &&
+
+	hash_e=$(git hash-object empty) &&
+	hash_n=$(git hash-object not-empty) &&
+
+	cat >expect.diff_p <<-EOF &&
+	diff --git a/empty b/empty
+	new file mode 100644
+	index 0000000..$(git rev-parse --short $hash_e)
+	diff --git a/not-empty b/not-empty
+	new file mode 100644
+	index 0000000..$(git rev-parse --short $hash_n)
+	--- /dev/null
+	+++ b/not-empty
+	@@ -0,0 +1 @@
+	+$content
+	EOF
+	cat >expect.diff_s <<-EOF &&
+	 create mode 100644 empty
+	 create mode 100644 not-empty
+	EOF
+	cat >expect.diff_a <<-EOF &&
+	:000000 100644 0000000 0000000 A$(printf "\t")empty
+	:000000 100644 0000000 0000000 A$(printf "\t")not-empty
+	EOF
+
+	git add -N empty not-empty &&
+
+	git diff >actual &&
+	test_cmp expect.diff_p actual &&
+
 	git diff --summary >actual &&
-	echo " create mode 100644 new-ita" >expected &&
-	test_cmp expected actual &&
-	git diff --cached --summary >actual2 &&
-	test_must_be_empty actual2
-'
+	test_cmp expect.diff_s actual &&
+
+	git diff-files -p >actual &&
+	test_cmp expect.diff_p actual &&
 
+	git diff-files --abbrev >actual &&
+	test_cmp expect.diff_a actual &&
+
+	git diff --cached >actual &&
+	test_must_be_empty actual
+'
 
 test_expect_success '"diff HEAD" includes ita as new files' '
 	git reset --hard &&
diff --git a/third_party/git/t/t2400-worktree-add.sh b/third_party/git/t/t2400-worktree-add.sh
index e819ba741e..5a7495474a 100755
--- a/third_party/git/t/t2400-worktree-add.sh
+++ b/third_party/git/t/t2400-worktree-add.sh
@@ -438,7 +438,7 @@ test_expect_success 'git worktree add does not match remote' '
 		cd foo &&
 		test_must_fail git config "branch.foo.remote" &&
 		test_must_fail git config "branch.foo.merge" &&
-		! test_cmp_rev refs/remotes/repo_a/foo refs/heads/foo
+		test_cmp_rev ! refs/remotes/repo_a/foo refs/heads/foo
 	)
 '
 
@@ -483,7 +483,7 @@ test_expect_success 'git worktree --no-guess-remote option overrides config' '
 		cd foo &&
 		test_must_fail git config "branch.foo.remote" &&
 		test_must_fail git config "branch.foo.merge" &&
-		! test_cmp_rev refs/remotes/repo_a/foo refs/heads/foo
+		test_cmp_rev ! refs/remotes/repo_a/foo refs/heads/foo
 	)
 '
 
@@ -570,6 +570,15 @@ test_expect_success '"add" an existing locked but missing worktree' '
 	git worktree add --force --force --detach gnoo
 '
 
+test_expect_success '"add" not tripped up by magic worktree matching"' '
+	# if worktree "sub1/bar" exists, "git worktree add bar" in distinct
+	# directory `sub2` should not mistakenly complain that `bar` is an
+	# already-registered worktree
+	mkdir sub1 sub2 &&
+	git -C sub1 --git-dir=../.git worktree add --detach bozo &&
+	git -C sub2 --git-dir=../.git worktree add --detach bozo
+'
+
 test_expect_success FUNNYNAMES 'sanitize generated worktree name' '
 	git worktree add --detach ".  weird*..?.lock.lock" &&
 	test -d .git/worktrees/---weird-.-
@@ -587,4 +596,28 @@ test_expect_success '"add" should not fail because of another bad worktree' '
 	)
 '
 
+test_expect_success '"add" with uninitialized submodule, with submodule.recurse unset' '
+	test_create_repo submodule &&
+	test_commit -C submodule first &&
+	test_create_repo project &&
+	git -C project submodule add ../submodule &&
+	git -C project add submodule &&
+	test_tick &&
+	git -C project commit -m add_sub &&
+	git clone project project-clone &&
+	git -C project-clone worktree add ../project-2
+'
+test_expect_success '"add" with uninitialized submodule, with submodule.recurse set' '
+	git -C project-clone -c submodule.recurse worktree add ../project-3
+'
+
+test_expect_success '"add" with initialized submodule, with submodule.recurse unset' '
+	git -C project-clone submodule update --init &&
+	git -C project-clone worktree add ../project-4
+'
+
+test_expect_success '"add" with initialized submodule, with submodule.recurse set' '
+	git -C project-clone -c submodule.recurse worktree add ../project-5
+'
+
 test_done
diff --git a/third_party/git/t/t2401-worktree-prune.sh b/third_party/git/t/t2401-worktree-prune.sh
index b7d6d5d45a..a6ce7f590b 100755
--- a/third_party/git/t/t2401-worktree-prune.sh
+++ b/third_party/git/t/t2401-worktree-prune.sh
@@ -92,4 +92,28 @@ test_expect_success 'not prune proper checkouts' '
 	test -d .git/worktrees/nop
 '
 
+test_expect_success 'prune duplicate (linked/linked)' '
+	test_when_finished rm -fr .git/worktrees w1 w2 &&
+	git worktree add --detach w1 &&
+	git worktree add --detach w2 &&
+	sed "s/w2/w1/" .git/worktrees/w2/gitdir >.git/worktrees/w2/gitdir.new &&
+	mv .git/worktrees/w2/gitdir.new .git/worktrees/w2/gitdir &&
+	git worktree prune --verbose >actual &&
+	test_i18ngrep "duplicate entry" actual &&
+	test -d .git/worktrees/w1 &&
+	! test -d .git/worktrees/w2
+'
+
+test_expect_success 'prune duplicate (main/linked)' '
+	test_when_finished rm -fr repo wt &&
+	test_create_repo repo &&
+	test_commit -C repo x &&
+	git -C repo worktree add --detach ../wt &&
+	rm -fr wt &&
+	mv repo wt &&
+	git -C wt worktree prune --verbose >actual &&
+	test_i18ngrep "duplicate entry" actual &&
+	! test -d .git/worktrees/wt
+'
+
 test_done
diff --git a/third_party/git/t/t2402-worktree-list.sh b/third_party/git/t/t2402-worktree-list.sh
index bb6fb9b12c..52585ec2aa 100755
--- a/third_party/git/t/t2402-worktree-list.sh
+++ b/third_party/git/t/t2402-worktree-list.sh
@@ -151,4 +151,10 @@ test_expect_success 'linked worktrees are sorted' '
 	test_cmp expected sorted/main/actual
 '
 
+test_expect_success 'worktree path when called in .git directory' '
+	git worktree list >list1 &&
+	git -C .git worktree list >list2 &&
+	test_cmp list1 list2
+'
+
 test_done
diff --git a/third_party/git/t/t2403-worktree-move.sh b/third_party/git/t/t2403-worktree-move.sh
index 939d18d728..a4e1a178e0 100755
--- a/third_party/git/t/t2403-worktree-move.sh
+++ b/third_party/git/t/t2403-worktree-move.sh
@@ -112,6 +112,27 @@ test_expect_success 'move locked worktree (force)' '
 	git worktree move --force --force flump ploof
 '
 
+test_expect_success 'refuse to move worktree atop existing path' '
+	>bobble &&
+	git worktree add --detach beeble &&
+	test_must_fail git worktree move beeble bobble
+'
+
+test_expect_success 'move atop existing but missing worktree' '
+	git worktree add --detach gnoo &&
+	git worktree add --detach pneu &&
+	rm -fr pneu &&
+	test_must_fail git worktree move gnoo pneu &&
+	git worktree move --force gnoo pneu &&
+
+	git worktree add --detach nu &&
+	git worktree lock nu &&
+	rm -fr nu &&
+	test_must_fail git worktree move pneu nu &&
+	test_must_fail git worktree --force move pneu nu &&
+	git worktree move --force --force pneu nu
+'
+
 test_expect_success 'move a repo with uninitialized submodule' '
 	git init withsub &&
 	(
diff --git a/third_party/git/t/t2404-worktree-config.sh b/third_party/git/t/t2404-worktree-config.sh
index 286121d8de..9536d10919 100755
--- a/third_party/git/t/t2404-worktree-config.sh
+++ b/third_party/git/t/t2404-worktree-config.sh
@@ -23,8 +23,10 @@ test_expect_success 'config --worktree without extension' '
 '
 
 test_expect_success 'enable worktreeConfig extension' '
+	git config core.repositoryformatversion 1 &&
 	git config extensions.worktreeConfig true &&
-	test_cmp_config true extensions.worktreeConfig
+	test_cmp_config true extensions.worktreeConfig &&
+	test_cmp_config 1 core.repositoryformatversion
 '
 
 test_expect_success 'config is shared as before' '
diff --git a/third_party/git/t/t2405-worktree-submodule.sh b/third_party/git/t/t2405-worktree-submodule.sh
new file mode 100755
index 0000000000..e1b2bfd87e
--- /dev/null
+++ b/third_party/git/t/t2405-worktree-submodule.sh
@@ -0,0 +1,90 @@
+#!/bin/sh
+
+test_description='Combination of submodules and multiple worktrees'
+
+. ./test-lib.sh
+
+base_path=$(pwd -P)
+
+test_expect_success 'setup: create origin repos'  '
+	git init origin/sub &&
+	test_commit -C origin/sub file1 &&
+	git init origin/main &&
+	test_commit -C origin/main first &&
+	git -C origin/main submodule add ../sub &&
+	git -C origin/main commit -m "add sub" &&
+	test_commit -C origin/sub "file1 updated" file1 file1updated file1updated &&
+	git -C origin/main/sub pull &&
+	git -C origin/main add sub &&
+	git -C origin/main commit -m "sub updated"
+'
+
+test_expect_success 'setup: clone superproject to create main worktree' '
+	git clone --recursive "$base_path/origin/main" main
+'
+
+rev1_hash_main=$(git --git-dir=origin/main/.git show --pretty=format:%h -q "HEAD~1")
+rev1_hash_sub=$(git --git-dir=origin/sub/.git show --pretty=format:%h -q "HEAD~1")
+
+test_expect_success 'add superproject worktree' '
+	git -C main worktree add "$base_path/worktree" "$rev1_hash_main"
+'
+
+test_expect_failure 'submodule is checked out just after worktree add' '
+	git -C worktree diff --submodule master"^!" >out &&
+	grep "file1 updated" out
+'
+
+test_expect_success 'add superproject worktree and initialize submodules' '
+	git -C main worktree add "$base_path/worktree-submodule-update" "$rev1_hash_main" &&
+	git -C worktree-submodule-update submodule update
+'
+
+test_expect_success 'submodule is checked out just after submodule update in linked worktree' '
+	git -C worktree-submodule-update diff --submodule master"^!" >out &&
+	grep "file1 updated" out
+'
+
+test_expect_success 'add superproject worktree and manually add submodule worktree' '
+	git -C main worktree add "$base_path/linked_submodule" "$rev1_hash_main" &&
+	git -C main/sub worktree add "$base_path/linked_submodule/sub" "$rev1_hash_sub"
+'
+
+test_expect_success 'submodule is checked out after manually adding submodule worktree' '
+	git -C linked_submodule diff --submodule master"^!" >out &&
+	grep "file1 updated" out
+'
+
+test_expect_success 'checkout --recurse-submodules uses $GIT_DIR for submodules in a linked worktree' '
+	git -C main worktree add "$base_path/checkout-recurse" --detach  &&
+	git -C checkout-recurse submodule update --init &&
+	echo "gitdir: ../../main/.git/worktrees/checkout-recurse/modules/sub" >expect-gitfile &&
+	cat checkout-recurse/sub/.git >actual-gitfile &&
+	test_cmp expect-gitfile actual-gitfile &&
+	git -C main/sub rev-parse HEAD >expect-head-main &&
+	git -C checkout-recurse checkout --recurse-submodules HEAD~1 &&
+	cat checkout-recurse/sub/.git >actual-gitfile &&
+	git -C main/sub rev-parse HEAD >actual-head-main &&
+	test_cmp expect-gitfile actual-gitfile &&
+	test_cmp expect-head-main actual-head-main
+'
+
+test_expect_success 'core.worktree is removed in $GIT_DIR/modules/<name>/config, not in $GIT_COMMON_DIR/modules/<name>/config' '
+	echo "../../../sub" >expect-main &&
+	git -C main/sub config --get core.worktree >actual-main &&
+	test_cmp expect-main actual-main &&
+	echo "../../../../../../checkout-recurse/sub" >expect-linked &&
+	git -C checkout-recurse/sub config --get core.worktree >actual-linked &&
+	test_cmp expect-linked actual-linked &&
+	git -C checkout-recurse checkout --recurse-submodules first &&
+	test_expect_code 1 git -C main/.git/worktrees/checkout-recurse/modules/sub config --get core.worktree >linked-config &&
+	test_must_be_empty linked-config &&
+	git -C main/sub config --get core.worktree >actual-main &&
+	test_cmp expect-main actual-main
+'
+
+test_expect_success 'unsetting core.worktree does not prevent running commands directly against the submodule repository' '
+	git -C main/.git/worktrees/checkout-recurse/modules/sub log
+'
+
+test_done
diff --git a/third_party/git/t/t2406-worktree-repair.sh b/third_party/git/t/t2406-worktree-repair.sh
new file mode 100755
index 0000000000..1fe468bfe8
--- /dev/null
+++ b/third_party/git/t/t2406-worktree-repair.sh
@@ -0,0 +1,179 @@
+#!/bin/sh
+
+test_description='test git worktree repair'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+	test_commit init
+'
+
+test_expect_success 'skip missing worktree' '
+	test_when_finished "git worktree prune" &&
+	git worktree add --detach missing &&
+	rm -rf missing &&
+	git worktree repair >out 2>err &&
+	test_must_be_empty out &&
+	test_must_be_empty err
+'
+
+test_expect_success 'worktree path not directory' '
+	test_when_finished "git worktree prune" &&
+	git worktree add --detach notdir &&
+	rm -rf notdir &&
+	>notdir &&
+	test_must_fail git worktree repair >out 2>err &&
+	test_must_be_empty out &&
+	test_i18ngrep "not a directory" err
+'
+
+test_expect_success "don't clobber .git repo" '
+	test_when_finished "rm -rf repo && git worktree prune" &&
+	git worktree add --detach repo &&
+	rm -rf repo &&
+	test_create_repo repo &&
+	test_must_fail git worktree repair >out 2>err &&
+	test_must_be_empty out &&
+	test_i18ngrep ".git is not a file" err
+'
+
+test_corrupt_gitfile () {
+	butcher=$1 &&
+	problem=$2 &&
+	repairdir=${3:-.} &&
+	test_when_finished 'rm -rf corrupt && git worktree prune' &&
+	git worktree add --detach corrupt &&
+	git -C corrupt rev-parse --absolute-git-dir >expect &&
+	eval "$butcher" &&
+	git -C "$repairdir" worktree repair >out 2>err &&
+	test_i18ngrep "$problem" out &&
+	test_must_be_empty err &&
+	git -C corrupt rev-parse --absolute-git-dir >actual &&
+	test_cmp expect actual
+}
+
+test_expect_success 'repair missing .git file' '
+	test_corrupt_gitfile "rm -f corrupt/.git" ".git file broken"
+'
+
+test_expect_success 'repair bogus .git file' '
+	test_corrupt_gitfile "echo \"gitdir: /nowhere\" >corrupt/.git" \
+		".git file broken"
+'
+
+test_expect_success 'repair incorrect .git file' '
+	test_when_finished "rm -rf other && git worktree prune" &&
+	test_create_repo other &&
+	other=$(git -C other rev-parse --absolute-git-dir) &&
+	test_corrupt_gitfile "echo \"gitdir: $other\" >corrupt/.git" \
+		".git file incorrect"
+'
+
+test_expect_success 'repair .git file from main/.git' '
+	test_corrupt_gitfile "rm -f corrupt/.git" ".git file broken" .git
+'
+
+test_expect_success 'repair .git file from linked worktree' '
+	test_when_finished "rm -rf other && git worktree prune" &&
+	git worktree add --detach other &&
+	test_corrupt_gitfile "rm -f corrupt/.git" ".git file broken" other
+'
+
+test_expect_success 'repair .git file from bare.git' '
+	test_when_finished "rm -rf bare.git corrupt && git worktree prune" &&
+	git clone --bare . bare.git &&
+	git -C bare.git worktree add --detach ../corrupt &&
+	git -C corrupt rev-parse --absolute-git-dir >expect &&
+	rm -f corrupt/.git &&
+	git -C bare.git worktree repair &&
+	git -C corrupt rev-parse --absolute-git-dir >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'invalid worktree path' '
+	test_must_fail git worktree repair /notvalid >out 2>err &&
+	test_must_be_empty out &&
+	test_i18ngrep "not a valid path" err
+'
+
+test_expect_success 'repo not found; .git not file' '
+	test_when_finished "rm -rf not-a-worktree" &&
+	test_create_repo not-a-worktree &&
+	test_must_fail git worktree repair not-a-worktree >out 2>err &&
+	test_must_be_empty out &&
+	test_i18ngrep ".git is not a file" err
+'
+
+test_expect_success 'repo not found; .git file broken' '
+	test_when_finished "rm -rf orig moved && git worktree prune" &&
+	git worktree add --detach orig &&
+	echo /invalid >orig/.git &&
+	mv orig moved &&
+	test_must_fail git worktree repair moved >out 2>err &&
+	test_must_be_empty out &&
+	test_i18ngrep ".git file broken" err
+'
+
+test_expect_success 'repair broken gitdir' '
+	test_when_finished "rm -rf orig moved && git worktree prune" &&
+	git worktree add --detach orig &&
+	sed s,orig/\.git$,moved/.git, .git/worktrees/orig/gitdir >expect &&
+	rm .git/worktrees/orig/gitdir &&
+	mv orig moved &&
+	git worktree repair moved >out 2>err &&
+	test_cmp expect .git/worktrees/orig/gitdir &&
+	test_i18ngrep "gitdir unreadable" out &&
+	test_must_be_empty err
+'
+
+test_expect_success 'repair incorrect gitdir' '
+	test_when_finished "rm -rf orig moved && git worktree prune" &&
+	git worktree add --detach orig &&
+	sed s,orig/\.git$,moved/.git, .git/worktrees/orig/gitdir >expect &&
+	mv orig moved &&
+	git worktree repair moved >out 2>err &&
+	test_cmp expect .git/worktrees/orig/gitdir &&
+	test_i18ngrep "gitdir incorrect" out &&
+	test_must_be_empty err
+'
+
+test_expect_success 'repair gitdir (implicit) from linked worktree' '
+	test_when_finished "rm -rf orig moved && git worktree prune" &&
+	git worktree add --detach orig &&
+	sed s,orig/\.git$,moved/.git, .git/worktrees/orig/gitdir >expect &&
+	mv orig moved &&
+	git -C moved worktree repair >out 2>err &&
+	test_cmp expect .git/worktrees/orig/gitdir &&
+	test_i18ngrep "gitdir incorrect" out &&
+	test_must_be_empty err
+'
+
+test_expect_success 'unable to repair gitdir (implicit) from main worktree' '
+	test_when_finished "rm -rf orig moved && git worktree prune" &&
+	git worktree add --detach orig &&
+	cat .git/worktrees/orig/gitdir >expect &&
+	mv orig moved &&
+	git worktree repair >out 2>err &&
+	test_cmp expect .git/worktrees/orig/gitdir &&
+	test_must_be_empty out &&
+	test_must_be_empty err
+'
+
+test_expect_success 'repair multiple gitdir files' '
+	test_when_finished "rm -rf orig1 orig2 moved1 moved2 &&
+		git worktree prune" &&
+	git worktree add --detach orig1 &&
+	git worktree add --detach orig2 &&
+	sed s,orig1/\.git$,moved1/.git, .git/worktrees/orig1/gitdir >expect1 &&
+	sed s,orig2/\.git$,moved2/.git, .git/worktrees/orig2/gitdir >expect2 &&
+	mv orig1 moved1 &&
+	mv orig2 moved2 &&
+	git worktree repair moved1 moved2 >out 2>err &&
+	test_cmp expect1 .git/worktrees/orig1/gitdir &&
+	test_cmp expect2 .git/worktrees/orig2/gitdir &&
+	test_i18ngrep "gitdir incorrect:.*orig1/gitdir$" out &&
+	test_i18ngrep "gitdir incorrect:.*orig2/gitdir$" out &&
+	test_must_be_empty err
+'
+
+test_done
diff --git a/third_party/git/t/t3000-ls-files-others.sh b/third_party/git/t/t3000-ls-files-others.sh
index 0aefadacb0..740ce56eab 100755
--- a/third_party/git/t/t3000-ls-files-others.sh
+++ b/third_party/git/t/t3000-ls-files-others.sh
@@ -91,4 +91,141 @@ test_expect_success SYMLINKS 'ls-files --others with symlinked submodule' '
 	test_cmp expect actual
 '
 
+test_expect_success 'setup nested pathspec search' '
+	test_create_repo nested &&
+	(
+		cd nested &&
+
+		mkdir -p partially_tracked/untracked_dir &&
+		> partially_tracked/content &&
+		> partially_tracked/untracked_dir/file &&
+
+		mkdir -p untracked/deep &&
+		> untracked/deep/path &&
+		> untracked/deep/foo.c &&
+
+		git add partially_tracked/content
+	)
+'
+
+test_expect_success 'ls-files -o --directory with single deep dir pathspec' '
+	(
+		cd nested &&
+
+		git ls-files -o --directory untracked/deep/ >actual &&
+
+		cat <<-EOF >expect &&
+		untracked/deep/
+		EOF
+
+		test_cmp expect actual
+	)
+'
+
+test_expect_success 'ls-files -o --directory with multiple dir pathspecs' '
+	(
+		cd nested &&
+
+		git ls-files -o --directory partially_tracked/ untracked/ >actual &&
+
+		cat <<-EOF >expect &&
+		partially_tracked/untracked_dir/
+		untracked/
+		EOF
+
+		test_cmp expect actual
+	)
+'
+
+test_expect_success 'ls-files -o --directory with mix dir/file pathspecs' '
+	(
+		cd nested &&
+
+		git ls-files -o --directory partially_tracked/ untracked/deep/path >actual &&
+
+		cat <<-EOF >expect &&
+		partially_tracked/untracked_dir/
+		untracked/deep/path
+		EOF
+
+		test_cmp expect actual
+	)
+'
+
+test_expect_success 'ls-files -o --directory with glob filetype match' '
+	(
+		cd nested &&
+
+		# globs kinda defeat --directory, but only for that pathspec
+		git ls-files --others --directory partially_tracked "untracked/*.c" >actual &&
+
+		cat <<-EOF >expect &&
+		partially_tracked/untracked_dir/
+		untracked/deep/foo.c
+		EOF
+
+		test_cmp expect actual
+	)
+'
+
+test_expect_success 'ls-files -o --directory with mix of tracked states' '
+	(
+		cd nested &&
+
+		# globs kinda defeat --directory, but only for that pathspec
+		git ls-files --others --directory partially_tracked/ "untracked/?*" >actual &&
+
+		cat <<-EOF >expect &&
+		partially_tracked/untracked_dir/
+		untracked/deep/
+		EOF
+
+		test_cmp expect actual
+	)
+'
+
+test_expect_success 'ls-files -o --directory with glob filetype match only' '
+	(
+		cd nested &&
+
+		git ls-files --others --directory "untracked/*.c" >actual &&
+
+		cat <<-EOF >expect &&
+		untracked/deep/foo.c
+		EOF
+
+		test_cmp expect actual
+	)
+'
+
+test_expect_success 'ls-files -o --directory to get immediate paths under one dir only' '
+	(
+		cd nested &&
+
+		git ls-files --others --directory "untracked/?*" >actual &&
+
+		cat <<-EOF >expect &&
+		untracked/deep/
+		EOF
+
+		test_cmp expect actual
+	)
+'
+
+test_expect_success 'ls-files -o avoids listing untracked non-matching gitdir' '
+	test_when_finished "rm -rf nested/untracked/deep/empty" &&
+	(
+		cd nested &&
+
+		git init untracked/deep/empty &&
+		git ls-files --others "untracked/*.c" >actual &&
+
+		cat <<-EOF >expect &&
+		untracked/deep/foo.c
+		EOF
+
+		test_cmp expect actual
+	)
+'
+
 test_done
diff --git a/third_party/git/t/t3005-ls-files-relative.sh b/third_party/git/t/t3005-ls-files-relative.sh
index 209b4c7cd8..2ec69a8a26 100755
--- a/third_party/git/t/t3005-ls-files-relative.sh
+++ b/third_party/git/t/t3005-ls-files-relative.sh
@@ -7,10 +7,6 @@ This test runs git ls-files with various relative path arguments.
 
 . ./test-lib.sh
 
-new_line='
-'
-sq=\'
-
 test_expect_success 'prepare' '
 	: >never-mind-me &&
 	git add never-mind-me &&
@@ -44,9 +40,9 @@ test_expect_success 'ls-files -c' '
 		cd top/sub &&
 		for f in ../y*
 		do
-			echo "error: pathspec $sq$f$sq did not match any file(s) known to git"
+			echo "error: pathspec $SQ$f$SQ did not match any file(s) known to git"
 		done >expect.err &&
-		echo "Did you forget to ${sq}git add${sq}?" >>expect.err &&
+		echo "Did you forget to ${SQ}git add${SQ}?" >>expect.err &&
 		ls ../x* >expect.out &&
 		test_must_fail git ls-files -c --error-unmatch ../[xy]* >actual.out 2>actual.err &&
 		test_cmp expect.out actual.out &&
@@ -59,9 +55,9 @@ test_expect_success 'ls-files -o' '
 		cd top/sub &&
 		for f in ../x*
 		do
-			echo "error: pathspec $sq$f$sq did not match any file(s) known to git"
+			echo "error: pathspec $SQ$f$SQ did not match any file(s) known to git"
 		done >expect.err &&
-		echo "Did you forget to ${sq}git add${sq}?" >>expect.err &&
+		echo "Did you forget to ${SQ}git add${SQ}?" >>expect.err &&
 		ls ../y* >expect.out &&
 		test_must_fail git ls-files -o --error-unmatch ../[xy]* >actual.out 2>actual.err &&
 		test_cmp expect.out actual.out &&
diff --git a/third_party/git/t/t3007-ls-files-recurse-submodules.sh b/third_party/git/t/t3007-ls-files-recurse-submodules.sh
index 318b5bce7e..4a08000713 100755
--- a/third_party/git/t/t3007-ls-files-recurse-submodules.sh
+++ b/third_party/git/t/t3007-ls-files-recurse-submodules.sh
@@ -130,7 +130,6 @@ test_expect_success '--recurse-submodules and pathspecs setup' '
 
 	git ls-files --recurse-submodules >actual &&
 	test_cmp expect actual &&
-	cat actual &&
 	git ls-files --recurse-submodules "*" >actual &&
 	test_cmp expect actual
 '
diff --git a/third_party/git/t/t3008-ls-files-lazy-init-name-hash.sh b/third_party/git/t/t3008-ls-files-lazy-init-name-hash.sh
index 64f047332b..85f3704958 100755
--- a/third_party/git/t/t3008-ls-files-lazy-init-name-hash.sh
+++ b/third_party/git/t/t3008-ls-files-lazy-init-name-hash.sh
@@ -4,7 +4,7 @@ test_description='Test the lazy init name hash with various folder structures'
 
 . ./test-lib.sh
 
-if test 1 -eq $($GIT_BUILD_DIR/t/helper/test-tool online-cpus)
+if test 1 -eq $(test-tool online-cpus)
 then
 	skip_all='skipping lazy-init tests, single cpu'
 	test_done
diff --git a/third_party/git/t/t3011-common-prefixes-and-directory-traversal.sh b/third_party/git/t/t3011-common-prefixes-and-directory-traversal.sh
new file mode 100755
index 0000000000..3da5b2b6e7
--- /dev/null
+++ b/third_party/git/t/t3011-common-prefixes-and-directory-traversal.sh
@@ -0,0 +1,209 @@
+#!/bin/sh
+
+test_description='directory traversal handling, especially with common prefixes'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	test_commit hello &&
+
+	>empty &&
+	mkdir untracked_dir &&
+	>untracked_dir/empty &&
+	git init untracked_repo &&
+	>untracked_repo/empty &&
+
+	cat <<-EOF >.gitignore &&
+	ignored
+	an_ignored_dir/
+	EOF
+	mkdir an_ignored_dir &&
+	mkdir an_untracked_dir &&
+	>an_ignored_dir/ignored &&
+	>an_ignored_dir/untracked &&
+	>an_untracked_dir/ignored &&
+	>an_untracked_dir/untracked
+'
+
+test_expect_success 'git ls-files -o shows the right entries' '
+	cat <<-EOF >expect &&
+	.gitignore
+	actual
+	an_ignored_dir/ignored
+	an_ignored_dir/untracked
+	an_untracked_dir/ignored
+	an_untracked_dir/untracked
+	empty
+	expect
+	untracked_dir/empty
+	untracked_repo/
+	EOF
+	git ls-files -o >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'git ls-files -o --exclude-standard shows the right entries' '
+	cat <<-EOF >expect &&
+	.gitignore
+	actual
+	an_untracked_dir/untracked
+	empty
+	expect
+	untracked_dir/empty
+	untracked_repo/
+	EOF
+	git ls-files -o --exclude-standard >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'git ls-files -o untracked_dir recurses' '
+	echo untracked_dir/empty >expect &&
+	git ls-files -o untracked_dir >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'git ls-files -o untracked_dir/ recurses' '
+	echo untracked_dir/empty >expect &&
+	git ls-files -o untracked_dir/ >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'git ls-files -o --directory untracked_dir does not recurse' '
+	echo untracked_dir/ >expect &&
+	git ls-files -o --directory untracked_dir >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'git ls-files -o --directory untracked_dir/ does not recurse' '
+	echo untracked_dir/ >expect &&
+	git ls-files -o --directory untracked_dir/ >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'git ls-files -o untracked_repo does not recurse' '
+	echo untracked_repo/ >expect &&
+	git ls-files -o untracked_repo >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'git ls-files -o untracked_repo/ does not recurse' '
+	echo untracked_repo/ >expect &&
+	git ls-files -o untracked_repo/ >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'git ls-files -o untracked_dir untracked_repo recurses into untracked_dir only' '
+	cat <<-EOF >expect &&
+	untracked_dir/empty
+	untracked_repo/
+	EOF
+	git ls-files -o untracked_dir untracked_repo >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'git ls-files -o untracked_dir/ untracked_repo/ recurses into untracked_dir only' '
+	cat <<-EOF >expect &&
+	untracked_dir/empty
+	untracked_repo/
+	EOF
+	git ls-files -o untracked_dir/ untracked_repo/ >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'git ls-files -o --directory untracked_dir untracked_repo does not recurse' '
+	cat <<-EOF >expect &&
+	untracked_dir/
+	untracked_repo/
+	EOF
+	git ls-files -o --directory untracked_dir untracked_repo >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'git ls-files -o --directory untracked_dir/ untracked_repo/ does not recurse' '
+	cat <<-EOF >expect &&
+	untracked_dir/
+	untracked_repo/
+	EOF
+	git ls-files -o --directory untracked_dir/ untracked_repo/ >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'git ls-files -o .git shows nothing' '
+	git ls-files -o .git >actual &&
+	test_must_be_empty actual
+'
+
+test_expect_success 'git ls-files -o .git/ shows nothing' '
+	git ls-files -o .git/ >actual &&
+	test_must_be_empty actual
+'
+
+test_expect_success FUNNYNAMES 'git ls-files -o untracked_* recurses appropriately' '
+	mkdir "untracked_*" &&
+	>"untracked_*/empty" &&
+
+	cat <<-EOF >expect &&
+	untracked_*/empty
+	untracked_dir/empty
+	untracked_repo/
+	EOF
+	git ls-files -o "untracked_*" >actual &&
+	test_cmp expect actual
+'
+
+# It turns out fill_directory returns the right paths, but ls-files' post-call
+# filtering in show_dir_entry() via calling dir_path_match() which ends up
+# in git_fnmatch() has logic for PATHSPEC_ONESTAR that assumes the pathspec
+# must match the full path; it doesn't check it for matching a leading
+# directory.
+test_expect_failure FUNNYNAMES 'git ls-files -o untracked_*/ recurses appropriately' '
+	cat <<-EOF >expect &&
+	untracked_*/empty
+	untracked_dir/empty
+	untracked_repo/
+	EOF
+	git ls-files -o "untracked_*/" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success FUNNYNAMES 'git ls-files -o --directory untracked_* does not recurse' '
+	cat <<-EOF >expect &&
+	untracked_*/
+	untracked_dir/
+	untracked_repo/
+	EOF
+	git ls-files -o --directory "untracked_*" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success FUNNYNAMES 'git ls-files -o --directory untracked_*/ does not recurse' '
+	cat <<-EOF >expect &&
+	untracked_*/
+	untracked_dir/
+	untracked_repo/
+	EOF
+	git ls-files -o --directory "untracked_*/" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'git ls-files -o consistent between one or two dirs' '
+	git ls-files -o --exclude-standard an_ignored_dir/ an_untracked_dir/ >tmp &&
+	! grep ^an_ignored_dir/ tmp >expect &&
+	git ls-files -o --exclude-standard an_ignored_dir/ >actual &&
+	test_cmp expect actual
+'
+
+# ls-files doesn't have a way to request showing both untracked and ignored
+# files at the same time, so use `git status --ignored`
+test_expect_success 'git status --ignored shows same files under dir with or without pathspec' '
+	cat <<-EOF >expect &&
+	?? an_untracked_dir/
+	!! an_untracked_dir/ignored
+	EOF
+	git status --porcelain --ignored >output &&
+	grep an_untracked_dir output >expect &&
+	git status --porcelain --ignored an_untracked_dir/ >actual &&
+	test_cmp expect actual
+'
+
+test_done
diff --git a/third_party/git/t/t3060-ls-files-with-tree.sh b/third_party/git/t/t3060-ls-files-with-tree.sh
index 44f378ce41..52ed665fcd 100755
--- a/third_party/git/t/t3060-ls-files-with-tree.sh
+++ b/third_party/git/t/t3060-ls-files-with-tree.sh
@@ -47,7 +47,7 @@ test_expect_success setup '
 	git add .
 '
 
-test_expect_success 'git -ls-files --with-tree should succeed from subdir' '
+test_expect_success 'git ls-files --with-tree should succeed from subdir' '
 	# We have to run from a sub-directory to trigger prune_path
 	# Then we finally get to run our --with-tree test
 	(
@@ -57,7 +57,7 @@ test_expect_success 'git -ls-files --with-tree should succeed from subdir' '
 '
 
 test_expect_success \
-    'git -ls-files --with-tree should add entries from named tree.' \
+    'git ls-files --with-tree should add entries from named tree.' \
     'test_cmp expected output'
 
 test_done
diff --git a/third_party/git/t/t3200-branch.sh b/third_party/git/t/t3200-branch.sh
index 411a70b0ce..6efe7a44bc 100755
--- a/third_party/git/t/t3200-branch.sh
+++ b/third_party/git/t/t3200-branch.sh
@@ -234,10 +234,10 @@ test_expect_success 'git branch -M master master should work when master is chec
 	git branch -M master master
 '
 
-test_expect_success 'git branch -M master2 master2 should work when master is checked out' '
+test_expect_success 'git branch -M topic topic should work when master is checked out' '
 	git checkout master &&
-	git branch master2 &&
-	git branch -M master2 master2
+	git branch topic &&
+	git branch -M topic topic
 '
 
 test_expect_success 'git branch -v -d t should work' '
@@ -323,11 +323,11 @@ test_expect_success 'git branch --list -v with --abbrev' '
 
 test_expect_success 'git branch --column' '
 	COLUMNS=81 git branch --column=column >actual &&
-	cat >expected <<\EOF &&
-  a/b/c     bam       foo       l       * master    mb        o/o       q
-  abc       bar       j/k       m/m       master2   n         o/p       r
+	cat >expect <<\EOF &&
+  a/b/c    bam      foo      l      * master   n        o/p      r
+  abc      bar      j/k      m/m      mb       o/o      q        topic
 EOF
-	test_cmp expected actual
+	test_cmp expect actual
 '
 
 test_expect_success 'git branch --column with an extremely long branch name' '
@@ -336,7 +336,7 @@ test_expect_success 'git branch --column with an extremely long branch name' '
 	test_when_finished "git branch -d $long" &&
 	git branch $long &&
 	COLUMNS=80 git branch --column=column >actual &&
-	cat >expected <<EOF &&
+	cat >expect <<EOF &&
   a/b/c
   abc
   bam
@@ -346,16 +346,16 @@ test_expect_success 'git branch --column with an extremely long branch name' '
   l
   m/m
 * master
-  master2
   mb
   n
   o/o
   o/p
   q
   r
+  topic
   $long
 EOF
-	test_cmp expected actual
+	test_cmp expect actual
 '
 
 test_expect_success 'git branch with column.*' '
@@ -364,11 +364,11 @@ test_expect_success 'git branch with column.*' '
 	COLUMNS=80 git branch >actual &&
 	git config --unset column.branch &&
 	git config --unset column.ui &&
-	cat >expected <<\EOF &&
-  a/b/c   bam   foo   l   * master    mb   o/o   q
-  abc     bar   j/k   m/m   master2   n    o/p   r
+	cat >expect <<\EOF &&
+  a/b/c   bam   foo   l   * master   n     o/p   r
+  abc     bar   j/k   m/m   mb       o/o   q     topic
 EOF
-	test_cmp expected actual
+	test_cmp expect actual
 '
 
 test_expect_success 'git branch --column -v should fail' '
@@ -377,9 +377,9 @@ test_expect_success 'git branch --column -v should fail' '
 
 test_expect_success 'git branch -v with column.ui ignored' '
 	git config column.ui column &&
-	COLUMNS=80 git branch -v | cut -c -10 | sed "s/ *$//" >actual &&
+	COLUMNS=80 git branch -v | cut -c -9 | sed "s/ *$//" >actual &&
 	git config --unset column.ui &&
-	cat >expected <<\EOF &&
+	cat >expect <<\EOF &&
   a/b/c
   abc
   bam
@@ -389,20 +389,20 @@ test_expect_success 'git branch -v with column.ui ignored' '
   l
   m/m
 * master
-  master2
   mb
   n
   o/o
   o/p
   q
   r
+  topic
 EOF
-	test_cmp expected actual
+	test_cmp expect actual
 '
 
 mv .git/config .git/config-saved
 
-test_expect_success 'git branch -m q q2 without config should succeed' '
+test_expect_success SHA1 'git branch -m q q2 without config should succeed' '
 	git branch -m q q2 &&
 	git branch -m q2 q
 '
@@ -597,10 +597,10 @@ test_expect_success 'git branch -C master master should work when master is chec
 	git branch -C master master
 '
 
-test_expect_success 'git branch -C master5 master5 should work when master is checked out' '
+test_expect_success 'git branch -C main5 main5 should work when master is checked out' '
 	git checkout master &&
-	git branch master5 &&
-	git branch -C master5 master5
+	git branch main5 &&
+	git branch -C main5 main5
 '
 
 test_expect_success 'git branch -C ab cd should overwrite existing config for cd' '
@@ -711,11 +711,11 @@ test_expect_success 'deleting a self-referential symref' '
 '
 
 test_expect_success 'renaming a symref is not allowed' '
-	git symbolic-ref refs/heads/master2 refs/heads/master &&
-	test_must_fail git branch -m master2 master3 &&
-	git symbolic-ref refs/heads/master2 &&
+	git symbolic-ref refs/heads/topic refs/heads/master &&
+	test_must_fail git branch -m topic new-topic &&
+	git symbolic-ref refs/heads/topic &&
 	test_path_is_file .git/refs/heads/master &&
-	test_path_is_missing .git/refs/heads/master3
+	test_path_is_missing .git/refs/heads/new-topic
 '
 
 test_expect_success SYMLINKS 'git branch -m u v should fail when the reflog for u is a symlink' '
@@ -835,32 +835,42 @@ test_expect_success 'branch from tag w/--track causes failure' '
 '
 
 test_expect_success '--set-upstream-to fails on multiple branches' '
-	test_must_fail git branch --set-upstream-to master a b c
+	echo "fatal: too many arguments to set new upstream" >expect &&
+	test_must_fail git branch --set-upstream-to master a b c 2>err &&
+	test_i18ncmp expect err
 '
 
 test_expect_success '--set-upstream-to fails on detached HEAD' '
 	git checkout HEAD^{} &&
-	test_must_fail git branch --set-upstream-to master &&
-	git checkout -
+	test_when_finished git checkout - &&
+	echo "fatal: could not set upstream of HEAD to master when it does not point to any branch." >expect &&
+	test_must_fail git branch --set-upstream-to master 2>err &&
+	test_i18ncmp expect err
 '
 
 test_expect_success '--set-upstream-to fails on a missing dst branch' '
-	test_must_fail git branch --set-upstream-to master does-not-exist
+	echo "fatal: branch '"'"'does-not-exist'"'"' does not exist" >expect &&
+	test_must_fail git branch --set-upstream-to master does-not-exist 2>err &&
+	test_i18ncmp expect err
 '
 
 test_expect_success '--set-upstream-to fails on a missing src branch' '
-	test_must_fail git branch --set-upstream-to does-not-exist master
+	test_must_fail git branch --set-upstream-to does-not-exist master 2>err &&
+	test_i18ngrep "the requested upstream branch '"'"'does-not-exist'"'"' does not exist" err
 '
 
 test_expect_success '--set-upstream-to fails on a non-ref' '
-	test_must_fail git branch --set-upstream-to HEAD^{}
+	echo "fatal: Cannot setup tracking information; starting point '"'"'HEAD^{}'"'"' is not a branch." >expect &&
+	test_must_fail git branch --set-upstream-to HEAD^{} 2>err &&
+	test_i18ncmp expect err
 '
 
 test_expect_success '--set-upstream-to fails on locked config' '
 	test_when_finished "rm -f .git/config.lock" &&
 	>.git/config.lock &&
 	git branch locked &&
-	test_must_fail git branch --set-upstream-to locked
+	test_must_fail git branch --set-upstream-to locked 2>err &&
+	test_i18ngrep "could not lock config file .git/config" err
 '
 
 test_expect_success 'use --set-upstream-to modify HEAD' '
@@ -881,14 +891,17 @@ test_expect_success 'use --set-upstream-to modify a particular branch' '
 '
 
 test_expect_success '--unset-upstream should fail if given a non-existent branch' '
-	test_must_fail git branch --unset-upstream i-dont-exist
+	echo "fatal: Branch '"'"'i-dont-exist'"'"' has no upstream information" >expect &&
+	test_must_fail git branch --unset-upstream i-dont-exist 2>err &&
+	test_i18ncmp expect err
 '
 
 test_expect_success '--unset-upstream should fail if config is locked' '
 	test_when_finished "rm -f .git/config.lock" &&
 	git branch --set-upstream-to locked &&
 	>.git/config.lock &&
-	test_must_fail git branch --unset-upstream
+	test_must_fail git branch --unset-upstream 2>err &&
+	test_i18ngrep "could not lock config file .git/config" err
 '
 
 test_expect_success 'test --unset-upstream on HEAD' '
@@ -900,17 +913,23 @@ test_expect_success 'test --unset-upstream on HEAD' '
 	test_must_fail git config branch.master.remote &&
 	test_must_fail git config branch.master.merge &&
 	# fail for a branch without upstream set
-	test_must_fail git branch --unset-upstream
+	echo "fatal: Branch '"'"'master'"'"' has no upstream information" >expect &&
+	test_must_fail git branch --unset-upstream 2>err &&
+	test_i18ncmp expect err
 '
 
 test_expect_success '--unset-upstream should fail on multiple branches' '
-	test_must_fail git branch --unset-upstream a b c
+	echo "fatal: too many arguments to unset upstream" >expect &&
+	test_must_fail git branch --unset-upstream a b c 2>err &&
+	test_i18ncmp expect err
 '
 
 test_expect_success '--unset-upstream should fail on detached HEAD' '
 	git checkout HEAD^{} &&
-	test_must_fail git branch --unset-upstream &&
-	git checkout -
+	test_when_finished git checkout - &&
+	echo "fatal: could not unset upstream of HEAD when it does not point to any branch." >expect &&
+	test_must_fail git branch --unset-upstream 2>err &&
+	test_i18ncmp expect err
 '
 
 test_expect_success 'test --unset-upstream on a particular branch' '
@@ -922,17 +941,17 @@ test_expect_success 'test --unset-upstream on a particular branch' '
 '
 
 test_expect_success 'disabled option --set-upstream fails' '
-    test_must_fail git branch --set-upstream origin/master
+	test_must_fail git branch --set-upstream origin/master
 '
 
 test_expect_success '--set-upstream-to notices an error to set branch as own upstream' '
 	git branch --set-upstream-to refs/heads/my13 my13 2>actual &&
-	cat >expected <<-\EOF &&
+	cat >expect <<-\EOF &&
 	warning: Not setting branch my13 as its own upstream.
 	EOF
 	test_expect_code 1 git config branch.my13.remote &&
 	test_expect_code 1 git config branch.my13.merge &&
-	test_i18ncmp expected actual
+	test_i18ncmp expect actual
 '
 
 # Keep this test last, as it changes the current branch
@@ -1268,6 +1287,7 @@ test_expect_success 'detect typo in branch name when using --edit-description' '
 '
 
 test_expect_success 'refuse --edit-description on unborn branch for now' '
+	test_when_finished "git checkout master" &&
 	write_script editor <<-\EOF &&
 		echo "New contents" >"$1"
 	EOF
@@ -1279,10 +1299,6 @@ test_expect_success '--merged catches invalid object names' '
 	test_must_fail git branch --merged 0000000000000000000000000000000000000000
 '
 
-test_expect_success '--merged is incompatible with --no-merged' '
-	test_must_fail git branch --merged HEAD --no-merged HEAD
-'
-
 test_expect_success '--list during rebase' '
 	test_when_finished "reset_rebase" &&
 	git checkout master &&
diff --git a/third_party/git/t/t3201-branch-contains.sh b/third_party/git/t/t3201-branch-contains.sh
index 0ea4fc4694..efea5c4971 100755
--- a/third_party/git/t/t3201-branch-contains.sh
+++ b/third_party/git/t/t3201-branch-contains.sh
@@ -171,6 +171,69 @@ test_expect_success 'Assert that --contains only works on commits, not trees & b
 	test_must_fail git branch --no-contains $blob
 '
 
+test_expect_success 'multiple branch --contains' '
+	git checkout -b side2 master &&
+	>feature &&
+	git add feature &&
+	git commit -m "add feature" &&
+	git checkout -b next master &&
+	git merge side &&
+	git branch --contains side --contains side2 >actual &&
+	cat >expect <<-\EOF &&
+	* next
+	  side
+	  side2
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'multiple branch --merged' '
+	git branch --merged next --merged master >actual &&
+	cat >expect <<-\EOF &&
+	  master
+	* next
+	  side
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'multiple branch --no-contains' '
+	git branch --no-contains side --no-contains side2 >actual &&
+	cat >expect <<-\EOF &&
+	  master
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'multiple branch --no-merged' '
+	git branch --no-merged next --no-merged master >actual &&
+	cat >expect <<-\EOF &&
+	  side2
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'branch --contains combined with --no-contains' '
+	git checkout -b seen master &&
+	git merge side &&
+	git merge side2 &&
+	git branch --contains side --no-contains side2 >actual &&
+	cat >expect <<-\EOF &&
+	  next
+	  side
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'branch --merged combined with --no-merged' '
+	git branch --merged seen --no-merged next >actual &&
+	cat >expect <<-\EOF &&
+	* seen
+	  side2
+	EOF
+	test_cmp expect actual
+'
+
 # We want to set up a case where the walk for the tracking info
 # of one branch crosses the tip of another branch (and make sure
 # that the latter walk does not mess up our flag to see if it was
@@ -192,23 +255,12 @@ test_expect_success 'branch --merged with --verbose' '
 	EOF
 	test_cmp expect actual &&
 	git branch --verbose --merged topic >actual &&
-	cat >expect <<-\EOF &&
-	  master c77a0a9 second on master
-	* topic  2c939f4 [ahead 1] foo
-	  zzz    c77a0a9 second on master
+	cat >expect <<-EOF &&
+	  master $(git rev-parse --short master) second on master
+	* topic  $(git rev-parse --short topic ) [ahead 1] foo
+	  zzz    $(git rev-parse --short zzz   ) second on master
 	EOF
 	test_i18ncmp expect actual
 '
 
-test_expect_success 'branch --contains combined with --no-contains' '
-	git branch --contains zzz --no-contains topic >actual &&
-	cat >expect <<-\EOF &&
-	  master
-	  side
-	  zzz
-	EOF
-	test_cmp expect actual
-
-'
-
 test_done
diff --git a/third_party/git/t/t3206-range-diff.sh b/third_party/git/t/t3206-range-diff.sh
index ec548654ce..6eb344be03 100755
--- a/third_party/git/t/t3206-range-diff.sh
+++ b/third_party/git/t/t3206-range-diff.sh
@@ -8,97 +8,212 @@ test_description='range-diff tests'
 # harm than good.  We need some real history.
 
 test_expect_success 'setup' '
-	git fast-import < "$TEST_DIRECTORY"/t3206/history.export
+	git fast-import <"$TEST_DIRECTORY"/t3206/history.export &&
+	test_oid_cache <<-\EOF
+	# topic
+	t1 sha1:4de457d
+	t2 sha1:fccce22
+	t3 sha1:147e64e
+	t4 sha1:a63e992
+	t1 sha256:b89f8b9
+	t2 sha256:5f12aad
+	t3 sha256:ea8b273
+	t4 sha256:14b7336
+
+	# unmodified
+	u1 sha1:35b9b25
+	u2 sha1:de345ab
+	u3 sha1:9af6654
+	u4 sha1:2901f77
+	u1 sha256:e3731be
+	u2 sha256:14fadf8
+	u3 sha256:736c4bc
+	u4 sha256:673e77d
+
+	# reordered
+	r1 sha1:aca177a
+	r2 sha1:14ad629
+	r3 sha1:ee58208
+	r4 sha1:307b27a
+	r1 sha256:f59d3aa
+	r2 sha256:fb261a8
+	r3 sha256:cb2649b
+	r4 sha256:958577e
+
+	# removed (deleted)
+	d1 sha1:7657159
+	d2 sha1:43d84d3
+	d3 sha1:a740396
+	d1 sha256:e312513
+	d2 sha256:eb19258
+	d3 sha256:1ccb3c1
+
+	# added
+	a1 sha1:2716022
+	a2 sha1:b62accd
+	a3 sha1:df46cfa
+	a4 sha1:3e64548
+	a5 sha1:12b4063
+	a1 sha256:d724f4d
+	a2 sha256:1de7762
+	a3 sha256:e159431
+	a4 sha256:b3e483c
+	a5 sha256:90866a7
+
+	# rebased
+	b1 sha1:cc9c443
+	b2 sha1:c5d9641
+	b3 sha1:28cc2b6
+	b4 sha1:5628ab7
+	b5 sha1:a31b12e
+	b1 sha256:a1a8717
+	b2 sha256:20a5862
+	b3 sha256:587172a
+	b4 sha256:2721c5d
+	b5 sha256:7b57864
+
+	# changed
+	c1 sha1:a4b3333
+	c2 sha1:f51d370
+	c3 sha1:0559556
+	c4 sha1:d966c5c
+	c1 sha256:f8c2b9d
+	c2 sha256:3fb6318
+	c3 sha256:168ab68
+	c4 sha256:3526539
+
+	# changed-message
+	m1 sha1:f686024
+	m2 sha1:4ab067d
+	m3 sha1:b9cb956
+	m4 sha1:8add5f1
+	m1 sha256:31e6281
+	m2 sha256:a06bf1b
+	m3 sha256:82dc654
+	m4 sha256:48470c5
+
+	# renamed
+	n1 sha1:f258d75
+	n2 sha1:017b62d
+	n3 sha1:3ce7af6
+	n4 sha1:1e6226b
+	n1 sha256:ad52114
+	n2 sha256:3b54c8f
+	n3 sha256:3b0a644
+	n4 sha256:e461653
+
+	# mode change
+	o1 sha1:4d39cb3
+	o2 sha1:26c107f
+	o3 sha1:4c1e0f5
+	o1 sha256:d0dd598
+	o2 sha256:c4a279e
+	o3 sha256:78459d7
+
+	# added and removed
+	s1 sha1:096b1ba
+	s2 sha1:d92e698
+	s3 sha1:9a1db4d
+	s4 sha1:fea3b5c
+	s1 sha256:a7f9134
+	s2 sha256:b4c2580
+	s3 sha256:1d62aa2
+	s4 sha256:48160e8
+
+	# Empty delimiter (included so lines match neatly)
+	__ sha1:-------
+	__ sha256:-------
+	EOF
 '
 
 test_expect_success 'simple A..B A..C (unmodified)' '
 	git range-diff --no-color master..topic master..unmodified \
 		>actual &&
-	cat >expected <<-EOF &&
-	1:  4de457d = 1:  35b9b25 s/5/A/
-	2:  fccce22 = 2:  de345ab s/4/A/
-	3:  147e64e = 3:  9af6654 s/11/B/
-	4:  a63e992 = 4:  2901f77 s/12/B/
+	cat >expect <<-EOF &&
+	1:  $(test_oid t1) = 1:  $(test_oid u1) s/5/A/
+	2:  $(test_oid t2) = 2:  $(test_oid u2) s/4/A/
+	3:  $(test_oid t3) = 3:  $(test_oid u3) s/11/B/
+	4:  $(test_oid t4) = 4:  $(test_oid u4) s/12/B/
 	EOF
-	test_cmp expected actual
+	test_cmp expect actual
 '
 
 test_expect_success 'simple B...C (unmodified)' '
 	git range-diff --no-color topic...unmodified >actual &&
-	# same "expected" as above
-	test_cmp expected actual
+	# same "expect" as above
+	test_cmp expect actual
 '
 
 test_expect_success 'simple A B C (unmodified)' '
 	git range-diff --no-color master topic unmodified >actual &&
-	# same "expected" as above
-	test_cmp expected actual
+	# same "expect" as above
+	test_cmp expect actual
 '
 
 test_expect_success 'trivial reordering' '
 	git range-diff --no-color master topic reordered >actual &&
-	cat >expected <<-EOF &&
-	1:  4de457d = 1:  aca177a s/5/A/
-	3:  147e64e = 2:  14ad629 s/11/B/
-	4:  a63e992 = 3:  ee58208 s/12/B/
-	2:  fccce22 = 4:  307b27a s/4/A/
+	cat >expect <<-EOF &&
+	1:  $(test_oid t1) = 1:  $(test_oid r1) s/5/A/
+	3:  $(test_oid t3) = 2:  $(test_oid r2) s/11/B/
+	4:  $(test_oid t4) = 3:  $(test_oid r3) s/12/B/
+	2:  $(test_oid t2) = 4:  $(test_oid r4) s/4/A/
 	EOF
-	test_cmp expected actual
+	test_cmp expect actual
 '
 
 test_expect_success 'removed a commit' '
 	git range-diff --no-color master topic removed >actual &&
-	cat >expected <<-EOF &&
-	1:  4de457d = 1:  7657159 s/5/A/
-	2:  fccce22 < -:  ------- s/4/A/
-	3:  147e64e = 2:  43d84d3 s/11/B/
-	4:  a63e992 = 3:  a740396 s/12/B/
+	cat >expect <<-EOF &&
+	1:  $(test_oid t1) = 1:  $(test_oid d1) s/5/A/
+	2:  $(test_oid t2) < -:  $(test_oid __) s/4/A/
+	3:  $(test_oid t3) = 2:  $(test_oid d2) s/11/B/
+	4:  $(test_oid t4) = 3:  $(test_oid d3) s/12/B/
 	EOF
-	test_cmp expected actual
+	test_cmp expect actual
 '
 
 test_expect_success 'added a commit' '
 	git range-diff --no-color master topic added >actual &&
-	cat >expected <<-EOF &&
-	1:  4de457d = 1:  2716022 s/5/A/
-	2:  fccce22 = 2:  b62accd s/4/A/
-	-:  ------- > 3:  df46cfa s/6/A/
-	3:  147e64e = 4:  3e64548 s/11/B/
-	4:  a63e992 = 5:  12b4063 s/12/B/
+	cat >expect <<-EOF &&
+	1:  $(test_oid t1) = 1:  $(test_oid a1) s/5/A/
+	2:  $(test_oid t2) = 2:  $(test_oid a2) s/4/A/
+	-:  $(test_oid __) > 3:  $(test_oid a3) s/6/A/
+	3:  $(test_oid t3) = 4:  $(test_oid a4) s/11/B/
+	4:  $(test_oid t4) = 5:  $(test_oid a5) s/12/B/
 	EOF
-	test_cmp expected actual
+	test_cmp expect actual
 '
 
 test_expect_success 'new base, A B C' '
 	git range-diff --no-color master topic rebased >actual &&
-	cat >expected <<-EOF &&
-	1:  4de457d = 1:  cc9c443 s/5/A/
-	2:  fccce22 = 2:  c5d9641 s/4/A/
-	3:  147e64e = 3:  28cc2b6 s/11/B/
-	4:  a63e992 = 4:  5628ab7 s/12/B/
+	cat >expect <<-EOF &&
+	1:  $(test_oid t1) = 1:  $(test_oid b1) s/5/A/
+	2:  $(test_oid t2) = 2:  $(test_oid b2) s/4/A/
+	3:  $(test_oid t3) = 3:  $(test_oid b3) s/11/B/
+	4:  $(test_oid t4) = 4:  $(test_oid b4) s/12/B/
 	EOF
-	test_cmp expected actual
+	test_cmp expect actual
 '
 
 test_expect_success 'new base, B...C' '
 	# this syntax includes the commits from master!
 	git range-diff --no-color topic...rebased >actual &&
-	cat >expected <<-EOF &&
-	-:  ------- > 1:  a31b12e unrelated
-	1:  4de457d = 2:  cc9c443 s/5/A/
-	2:  fccce22 = 3:  c5d9641 s/4/A/
-	3:  147e64e = 4:  28cc2b6 s/11/B/
-	4:  a63e992 = 5:  5628ab7 s/12/B/
+	cat >expect <<-EOF &&
+	-:  $(test_oid __) > 1:  $(test_oid b5) unrelated
+	1:  $(test_oid t1) = 2:  $(test_oid b1) s/5/A/
+	2:  $(test_oid t2) = 3:  $(test_oid b2) s/4/A/
+	3:  $(test_oid t3) = 4:  $(test_oid b3) s/11/B/
+	4:  $(test_oid t4) = 5:  $(test_oid b4) s/12/B/
 	EOF
-	test_cmp expected actual
+	test_cmp expect actual
 '
 
 test_expect_success 'changed commit' '
 	git range-diff --no-color topic...changed >actual &&
-	cat >expected <<-EOF &&
-	1:  4de457d = 1:  a4b3333 s/5/A/
-	2:  fccce22 = 2:  f51d370 s/4/A/
-	3:  147e64e ! 3:  0559556 s/11/B/
+	cat >expect <<-EOF &&
+	1:  $(test_oid t1) = 1:  $(test_oid c1) s/5/A/
+	2:  $(test_oid t2) = 2:  $(test_oid c2) s/4/A/
+	3:  $(test_oid t3) ! 3:  $(test_oid c3) s/11/B/
 	    @@ file: A
 	      9
 	      10
@@ -108,7 +223,7 @@ test_expect_success 'changed commit' '
 	      12
 	      13
 	      14
-	4:  a63e992 ! 4:  d966c5c s/12/B/
+	4:  $(test_oid t4) ! 4:  $(test_oid c4) s/12/B/
 	    @@ file
 	     @@ file: A
 	      9
@@ -119,45 +234,41 @@ test_expect_success 'changed commit' '
 	     +B
 	      13
 	EOF
-	test_cmp expected actual
+	test_cmp expect actual
 '
 
 test_expect_success 'changed commit with --no-patch diff option' '
 	git range-diff --no-color --no-patch topic...changed >actual &&
-	cat >expected <<-EOF &&
-	1:  4de457d = 1:  a4b3333 s/5/A/
-	2:  fccce22 = 2:  f51d370 s/4/A/
-	3:  147e64e ! 3:  0559556 s/11/B/
-	4:  a63e992 ! 4:  d966c5c s/12/B/
+	cat >expect <<-EOF &&
+	1:  $(test_oid t1) = 1:  $(test_oid c1) s/5/A/
+	2:  $(test_oid t2) = 2:  $(test_oid c2) s/4/A/
+	3:  $(test_oid t3) ! 3:  $(test_oid c3) s/11/B/
+	4:  $(test_oid t4) ! 4:  $(test_oid c4) s/12/B/
 	EOF
-	test_cmp expected actual
+	test_cmp expect actual
 '
 
 test_expect_success 'changed commit with --stat diff option' '
 	git range-diff --no-color --stat topic...changed >actual &&
-	cat >expected <<-EOF &&
-	1:  4de457d = 1:  a4b3333 s/5/A/
-	     a => b | 0
-	     1 file changed, 0 insertions(+), 0 deletions(-)
-	2:  fccce22 = 2:  f51d370 s/4/A/
-	     a => b | 0
-	     1 file changed, 0 insertions(+), 0 deletions(-)
-	3:  147e64e ! 3:  0559556 s/11/B/
-	     a => b | 0
-	     1 file changed, 0 insertions(+), 0 deletions(-)
-	4:  a63e992 ! 4:  d966c5c s/12/B/
-	     a => b | 0
-	     1 file changed, 0 insertions(+), 0 deletions(-)
+	cat >expect <<-EOF &&
+	1:  $(test_oid t1) = 1:  $(test_oid c1) s/5/A/
+	2:  $(test_oid t2) = 2:  $(test_oid c2) s/4/A/
+	3:  $(test_oid t3) ! 3:  $(test_oid c3) s/11/B/
+	     a => b | 2 +-
+	     1 file changed, 1 insertion(+), 1 deletion(-)
+	4:  $(test_oid t4) ! 4:  $(test_oid c4) s/12/B/
+	     a => b | 2 +-
+	     1 file changed, 1 insertion(+), 1 deletion(-)
 	EOF
-	test_cmp expected actual
+	test_cmp expect actual
 '
 
 test_expect_success 'changed commit with sm config' '
 	git range-diff --no-color --submodule=log topic...changed >actual &&
-	cat >expected <<-EOF &&
-	1:  4de457d = 1:  a4b3333 s/5/A/
-	2:  fccce22 = 2:  f51d370 s/4/A/
-	3:  147e64e ! 3:  0559556 s/11/B/
+	cat >expect <<-EOF &&
+	1:  $(test_oid t1) = 1:  $(test_oid c1) s/5/A/
+	2:  $(test_oid t2) = 2:  $(test_oid c2) s/4/A/
+	3:  $(test_oid t3) ! 3:  $(test_oid c3) s/11/B/
 	    @@ file: A
 	      9
 	      10
@@ -167,7 +278,7 @@ test_expect_success 'changed commit with sm config' '
 	      12
 	      13
 	      14
-	4:  a63e992 ! 4:  d966c5c s/12/B/
+	4:  $(test_oid t4) ! 4:  $(test_oid c4) s/12/B/
 	    @@ file
 	     @@ file: A
 	      9
@@ -178,14 +289,14 @@ test_expect_success 'changed commit with sm config' '
 	     +B
 	      13
 	EOF
-	test_cmp expected actual
+	test_cmp expect actual
 '
 
 test_expect_success 'renamed file' '
 	git range-diff --no-color --submodule=log topic...renamed-file >actual &&
-	sed s/Z/\ /g >expected <<-EOF &&
-	1:  4de457d = 1:  f258d75 s/5/A/
-	2:  fccce22 ! 2:  017b62d s/4/A/
+	sed s/Z/\ /g >expect <<-EOF &&
+	1:  $(test_oid t1) = 1:  $(test_oid n1) s/5/A/
+	2:  $(test_oid t2) ! 2:  $(test_oid n2) s/4/A/
 	    @@ Metadata
 	    ZAuthor: Thomas Rast <trast@inf.ethz.ch>
 	    Z
@@ -198,7 +309,7 @@ test_expect_success 'renamed file' '
 	    Z@@
 	    Z 1
 	    Z 2
-	3:  147e64e ! 3:  3ce7af6 s/11/B/
+	3:  $(test_oid t3) ! 3:  $(test_oid n3) s/11/B/
 	    @@ Metadata
 	    Z ## Commit message ##
 	    Z    s/11/B/
@@ -210,7 +321,7 @@ test_expect_success 'renamed file' '
 	    Z 8
 	    Z 9
 	    Z 10
-	4:  a63e992 ! 4:  1e6226b s/12/B/
+	4:  $(test_oid t4) ! 4:  $(test_oid n4) s/12/B/
 	    @@ Metadata
 	    Z ## Commit message ##
 	    Z    s/12/B/
@@ -223,14 +334,54 @@ test_expect_success 'renamed file' '
 	    Z 10
 	    Z B
 	EOF
-	test_cmp expected actual
+	test_cmp expect actual
+'
+
+test_expect_success 'file with mode only change' '
+	git range-diff --no-color --submodule=log topic...mode-only-change >actual &&
+	sed s/Z/\ /g >expect <<-EOF &&
+	1:  $(test_oid t2) ! 1:  $(test_oid o1) s/4/A/
+	    @@ Metadata
+	    ZAuthor: Thomas Rast <trast@inf.ethz.ch>
+	    Z
+	    Z ## Commit message ##
+	    -    s/4/A/
+	    +    s/4/A/ + add other-file
+	    Z
+	    Z ## file ##
+	    Z@@
+	    @@ file
+	    Z A
+	    Z 6
+	    Z 7
+	    +
+	    + ## other-file (new) ##
+	2:  $(test_oid t3) ! 2:  $(test_oid o2) s/11/B/
+	    @@ Metadata
+	    ZAuthor: Thomas Rast <trast@inf.ethz.ch>
+	    Z
+	    Z ## Commit message ##
+	    -    s/11/B/
+	    +    s/11/B/ + mode change other-file
+	    Z
+	    Z ## file ##
+	    Z@@ file: A
+	    @@ file: A
+	    Z 12
+	    Z 13
+	    Z 14
+	    +
+	    + ## other-file (mode change 100644 => 100755) ##
+	3:  $(test_oid t4) = 3:  $(test_oid o3) s/12/B/
+	EOF
+	test_cmp expect actual
 '
 
 test_expect_success 'file added and later removed' '
 	git range-diff --no-color --submodule=log topic...added-removed >actual &&
-	sed s/Z/\ /g >expected <<-EOF &&
-	1:  4de457d = 1:  096b1ba s/5/A/
-	2:  fccce22 ! 2:  d92e698 s/4/A/
+	sed s/Z/\ /g >expect <<-EOF &&
+	1:  $(test_oid t1) = 1:  $(test_oid s1) s/5/A/
+	2:  $(test_oid t2) ! 2:  $(test_oid s2) s/4/A/
 	    @@ Metadata
 	    ZAuthor: Thomas Rast <trast@inf.ethz.ch>
 	    Z
@@ -246,7 +397,7 @@ test_expect_success 'file added and later removed' '
 	    Z 7
 	    +
 	    + ## new-file (new) ##
-	3:  147e64e ! 3:  9a1db4d s/11/B/
+	3:  $(test_oid t3) ! 3:  $(test_oid s3) s/11/B/
 	    @@ Metadata
 	    ZAuthor: Thomas Rast <trast@inf.ethz.ch>
 	    Z
@@ -262,9 +413,9 @@ test_expect_success 'file added and later removed' '
 	    Z 14
 	    +
 	    + ## new-file (deleted) ##
-	4:  a63e992 = 4:  fea3b5c s/12/B/
+	4:  $(test_oid t4) = 4:  $(test_oid s4) s/12/B/
 	EOF
-	test_cmp expected actual
+	test_cmp expect actual
 '
 
 test_expect_success 'no commits on one side' '
@@ -274,9 +425,9 @@ test_expect_success 'no commits on one side' '
 
 test_expect_success 'changed message' '
 	git range-diff --no-color topic...changed-message >actual &&
-	sed s/Z/\ /g >expected <<-EOF &&
-	1:  4de457d = 1:  f686024 s/5/A/
-	2:  fccce22 ! 2:  4ab067d s/4/A/
+	sed s/Z/\ /g >expect <<-EOF &&
+	1:  $(test_oid t1) = 1:  $(test_oid m1) s/5/A/
+	2:  $(test_oid t2) ! 2:  $(test_oid m2) s/4/A/
 	    @@ Metadata
 	    Z ## Commit message ##
 	    Z    s/4/A/
@@ -286,16 +437,16 @@ test_expect_success 'changed message' '
 	    Z ## file ##
 	    Z@@
 	    Z 1
-	3:  147e64e = 3:  b9cb956 s/11/B/
-	4:  a63e992 = 4:  8add5f1 s/12/B/
+	3:  $(test_oid t3) = 3:  $(test_oid m3) s/11/B/
+	4:  $(test_oid t4) = 4:  $(test_oid m4) s/12/B/
 	EOF
-	test_cmp expected actual
+	test_cmp expect actual
 '
 
 test_expect_success 'dual-coloring' '
-	sed -e "s|^:||" >expect <<-\EOF &&
-	:<YELLOW>1:  a4b3333 = 1:  f686024 s/5/A/<RESET>
-	:<RED>2:  f51d370 <RESET><YELLOW>!<RESET><GREEN> 2:  4ab067d<RESET><YELLOW> s/4/A/<RESET>
+	sed -e "s|^:||" >expect <<-EOF &&
+	:<YELLOW>1:  $(test_oid c1) = 1:  $(test_oid m1) s/5/A/<RESET>
+	:<RED>2:  $(test_oid c2) <RESET><YELLOW>!<RESET><GREEN> 2:  $(test_oid m2)<RESET><YELLOW> s/4/A/<RESET>
 	:    <REVERSE><CYAN>@@<RESET> <RESET>Metadata<RESET>
 	:      ## Commit message ##<RESET>
 	:         s/4/A/<RESET>
@@ -305,7 +456,7 @@ test_expect_success 'dual-coloring' '
 	:      ## file ##<RESET>
 	:    <CYAN> @@<RESET>
 	:      1<RESET>
-	:<RED>3:  0559556 <RESET><YELLOW>!<RESET><GREEN> 3:  b9cb956<RESET><YELLOW> s/11/B/<RESET>
+	:<RED>3:  $(test_oid c3) <RESET><YELLOW>!<RESET><GREEN> 3:  $(test_oid m3)<RESET><YELLOW> s/11/B/<RESET>
 	:    <REVERSE><CYAN>@@<RESET> <RESET>file: A<RESET>
 	:      9<RESET>
 	:      10<RESET>
@@ -315,7 +466,7 @@ test_expect_success 'dual-coloring' '
 	:      12<RESET>
 	:      13<RESET>
 	:      14<RESET>
-	:<RED>4:  d966c5c <RESET><YELLOW>!<RESET><GREEN> 4:  8add5f1<RESET><YELLOW> s/12/B/<RESET>
+	:<RED>4:  $(test_oid c4) <RESET><YELLOW>!<RESET><GREEN> 4:  $(test_oid m4)<RESET><YELLOW> s/12/B/<RESET>
 	:    <REVERSE><CYAN>@@<RESET> <RESET>file<RESET>
 	:    <CYAN> @@ file: A<RESET>
 	:      9<RESET>
@@ -354,4 +505,216 @@ test_expect_success 'format-patch --range-diff as commentary' '
 	grep "> 1: .* new message" 0001-*
 '
 
+test_expect_success 'range-diff overrides diff.noprefix internally' '
+	git -c diff.noprefix=true range-diff HEAD^...
+'
+
+test_expect_success 'basic with modified format.pretty with suffix' '
+	git -c format.pretty="format:commit %H%d%n" range-diff \
+		master..topic master..unmodified
+'
+
+test_expect_success 'basic with modified format.pretty without "commit "' '
+	git -c format.pretty="format:%H%n" range-diff \
+		master..topic master..unmodified
+'
+
+test_expect_success 'range-diff compares notes by default' '
+	git notes add -m "topic note" topic &&
+	git notes add -m "unmodified note" unmodified &&
+	test_when_finished git notes remove topic unmodified &&
+	git range-diff --no-color master..topic master..unmodified \
+		>actual &&
+	sed s/Z/\ /g >expect <<-EOF &&
+	1:  $(test_oid t1) = 1:  $(test_oid u1) s/5/A/
+	2:  $(test_oid t2) = 2:  $(test_oid u2) s/4/A/
+	3:  $(test_oid t3) = 3:  $(test_oid u3) s/11/B/
+	4:  $(test_oid t4) ! 4:  $(test_oid u4) s/12/B/
+	    @@ Commit message
+	    Z
+	    Z
+	    Z ## Notes ##
+	    -    topic note
+	    +    unmodified note
+	    Z
+	    Z ## file ##
+	    Z@@ file: A
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'range-diff with --no-notes' '
+	git notes add -m "topic note" topic &&
+	git notes add -m "unmodified note" unmodified &&
+	test_when_finished git notes remove topic unmodified &&
+	git range-diff --no-color --no-notes master..topic master..unmodified \
+		>actual &&
+	cat >expect <<-EOF &&
+	1:  $(test_oid t1) = 1:  $(test_oid u1) s/5/A/
+	2:  $(test_oid t2) = 2:  $(test_oid u2) s/4/A/
+	3:  $(test_oid t3) = 3:  $(test_oid u3) s/11/B/
+	4:  $(test_oid t4) = 4:  $(test_oid u4) s/12/B/
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'range-diff with multiple --notes' '
+	git notes --ref=note1 add -m "topic note1" topic &&
+	git notes --ref=note1 add -m "unmodified note1" unmodified &&
+	test_when_finished git notes --ref=note1 remove topic unmodified &&
+	git notes --ref=note2 add -m "topic note2" topic &&
+	git notes --ref=note2 add -m "unmodified note2" unmodified &&
+	test_when_finished git notes --ref=note2 remove topic unmodified &&
+	git range-diff --no-color --notes=note1 --notes=note2 master..topic master..unmodified \
+		>actual &&
+	sed s/Z/\ /g >expect <<-EOF &&
+	1:  $(test_oid t1) = 1:  $(test_oid u1) s/5/A/
+	2:  $(test_oid t2) = 2:  $(test_oid u2) s/4/A/
+	3:  $(test_oid t3) = 3:  $(test_oid u3) s/11/B/
+	4:  $(test_oid t4) ! 4:  $(test_oid u4) s/12/B/
+	    @@ Commit message
+	    Z
+	    Z
+	    Z ## Notes (note1) ##
+	    -    topic note1
+	    +    unmodified note1
+	    Z
+	    Z
+	    Z ## Notes (note2) ##
+	    -    topic note2
+	    +    unmodified note2
+	    Z
+	    Z ## file ##
+	    Z@@ file: A
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'format-patch --range-diff does not compare notes by default' '
+	git notes add -m "topic note" topic &&
+	git notes add -m "unmodified note" unmodified &&
+	test_when_finished git notes remove topic unmodified &&
+	git format-patch --cover-letter --range-diff=$prev \
+		master..unmodified >actual &&
+	test_when_finished "rm 000?-*" &&
+	test_line_count = 5 actual &&
+	test_i18ngrep "^Range-diff:$" 0000-* &&
+	grep "= 1: .* s/5/A" 0000-* &&
+	grep "= 2: .* s/4/A" 0000-* &&
+	grep "= 3: .* s/11/B" 0000-* &&
+	grep "= 4: .* s/12/B" 0000-* &&
+	! grep "Notes" 0000-* &&
+	! grep "note" 0000-*
+'
+
+test_expect_success 'format-patch --range-diff with --no-notes' '
+	git notes add -m "topic note" topic &&
+	git notes add -m "unmodified note" unmodified &&
+	test_when_finished git notes remove topic unmodified &&
+	git format-patch --no-notes --cover-letter --range-diff=$prev \
+		master..unmodified >actual &&
+	test_when_finished "rm 000?-*" &&
+	test_line_count = 5 actual &&
+	test_i18ngrep "^Range-diff:$" 0000-* &&
+	grep "= 1: .* s/5/A" 0000-* &&
+	grep "= 2: .* s/4/A" 0000-* &&
+	grep "= 3: .* s/11/B" 0000-* &&
+	grep "= 4: .* s/12/B" 0000-* &&
+	! grep "Notes" 0000-* &&
+	! grep "note" 0000-*
+'
+
+test_expect_success 'format-patch --range-diff with --notes' '
+	git notes add -m "topic note" topic &&
+	git notes add -m "unmodified note" unmodified &&
+	test_when_finished git notes remove topic unmodified &&
+	git format-patch --notes --cover-letter --range-diff=$prev \
+		master..unmodified >actual &&
+	test_when_finished "rm 000?-*" &&
+	test_line_count = 5 actual &&
+	test_i18ngrep "^Range-diff:$" 0000-* &&
+	grep "= 1: .* s/5/A" 0000-* &&
+	grep "= 2: .* s/4/A" 0000-* &&
+	grep "= 3: .* s/11/B" 0000-* &&
+	grep "! 4: .* s/12/B" 0000-* &&
+	sed s/Z/\ /g >expect <<-EOF &&
+	    @@ Commit message
+	    Z
+	    Z
+	    Z ## Notes ##
+	    -    topic note
+	    +    unmodified note
+	    Z
+	    Z ## file ##
+	    Z@@ file: A
+	EOF
+	sed "/@@ Commit message/,/@@ file: A/!d" 0000-* >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'format-patch --range-diff with format.notes config' '
+	git notes add -m "topic note" topic &&
+	git notes add -m "unmodified note" unmodified &&
+	test_when_finished git notes remove topic unmodified &&
+	test_config format.notes true &&
+	git format-patch --cover-letter --range-diff=$prev \
+		master..unmodified >actual &&
+	test_when_finished "rm 000?-*" &&
+	test_line_count = 5 actual &&
+	test_i18ngrep "^Range-diff:$" 0000-* &&
+	grep "= 1: .* s/5/A" 0000-* &&
+	grep "= 2: .* s/4/A" 0000-* &&
+	grep "= 3: .* s/11/B" 0000-* &&
+	grep "! 4: .* s/12/B" 0000-* &&
+	sed s/Z/\ /g >expect <<-EOF &&
+	    @@ Commit message
+	    Z
+	    Z
+	    Z ## Notes ##
+	    -    topic note
+	    +    unmodified note
+	    Z
+	    Z ## file ##
+	    Z@@ file: A
+	EOF
+	sed "/@@ Commit message/,/@@ file: A/!d" 0000-* >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'format-patch --range-diff with multiple notes' '
+	git notes --ref=note1 add -m "topic note1" topic &&
+	git notes --ref=note1 add -m "unmodified note1" unmodified &&
+	test_when_finished git notes --ref=note1 remove topic unmodified &&
+	git notes --ref=note2 add -m "topic note2" topic &&
+	git notes --ref=note2 add -m "unmodified note2" unmodified &&
+	test_when_finished git notes --ref=note2 remove topic unmodified &&
+	git format-patch --notes=note1 --notes=note2 --cover-letter --range-diff=$prev \
+		master..unmodified >actual &&
+	test_when_finished "rm 000?-*" &&
+	test_line_count = 5 actual &&
+	test_i18ngrep "^Range-diff:$" 0000-* &&
+	grep "= 1: .* s/5/A" 0000-* &&
+	grep "= 2: .* s/4/A" 0000-* &&
+	grep "= 3: .* s/11/B" 0000-* &&
+	grep "! 4: .* s/12/B" 0000-* &&
+	sed s/Z/\ /g >expect <<-EOF &&
+	    @@ Commit message
+	    Z
+	    Z
+	    Z ## Notes (note1) ##
+	    -    topic note1
+	    +    unmodified note1
+	    Z
+	    Z
+	    Z ## Notes (note2) ##
+	    -    topic note2
+	    +    unmodified note2
+	    Z
+	    Z ## file ##
+	    Z@@ file: A
+	EOF
+	sed "/@@ Commit message/,/@@ file: A/!d" 0000-* >actual &&
+	test_cmp expect actual
+'
+
 test_done
diff --git a/third_party/git/t/t3206/history.export b/third_party/git/t/t3206/history.export
index 7bb3814962..4c808e5b3b 100644
--- a/third_party/git/t/t3206/history.export
+++ b/third_party/git/t/t3206/history.export
@@ -55,7 +55,7 @@ A
 19
 20
 
-commit refs/heads/topic
+commit refs/heads/mode-only-change
 mark :4
 author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
 committer Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
@@ -678,3 +678,32 @@ s/12/B/
 from :55
 M 100644 :9 renamed-file
 
+commit refs/heads/mode-only-change
+mark :57
+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+committer Thomas Gummerer <t.gummerer@gmail.com> 1570473767 +0100
+data 24
+s/4/A/ + add other-file
+from :4
+M 100644 :5 file
+M 100644 :49 other-file
+
+commit refs/heads/mode-only-change
+mark :58
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Gummerer <t.gummerer@gmail.com> 1570473768 +0100
+data 33
+s/11/B/ + mode change other-file
+from :57
+M 100644 :7 file
+M 100755 :49 other-file
+
+commit refs/heads/mode-only-change
+mark :59
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Gummerer <t.gummerer@gmail.com> 1570473768 +0100
+data 8
+s/12/B/
+from :58
+M 100644 :9 file
+
diff --git a/third_party/git/t/t3210-pack-refs.sh b/third_party/git/t/t3210-pack-refs.sh
index 9ea5fa4fd2..f41b2afb99 100755
--- a/third_party/git/t/t3210-pack-refs.sh
+++ b/third_party/git/t/t3210-pack-refs.sh
@@ -240,7 +240,7 @@ test_expect_success 'retry acquiring packed-refs.lock' '
 
 test_expect_success SYMLINKS 'pack symlinked packed-refs' '
 	# First make sure that symlinking works when reading:
-	git update-ref refs/heads/loosy refs/heads/master &&
+	git update-ref refs/heads/lossy refs/heads/master &&
 	git for-each-ref >all-refs-before &&
 	mv .git/packed-refs .git/my-deviant-packed-refs &&
 	ln -s my-deviant-packed-refs .git/packed-refs &&
diff --git a/third_party/git/t/t3301-notes.sh b/third_party/git/t/t3301-notes.sh
index 704bbc6541..8f43303007 100755
--- a/third_party/git/t/t3301-notes.sh
+++ b/third_party/git/t/t3301-notes.sh
@@ -54,7 +54,9 @@ test_expect_success 'create notes' '
 	test_path_is_missing .git/NOTES_EDITMSG &&
 	git ls-tree -r refs/notes/commits >actual &&
 	test_line_count = 1 actual &&
-	test "b4" = "$(git notes show)" &&
+	echo b4 >expect &&
+	git notes show >actual &&
+	test_cmp expect actual &&
 	git show HEAD^ &&
 	test_must_fail git notes show HEAD^
 '
@@ -66,8 +68,9 @@ test_expect_success 'show notes entry with %N' '
 '
 
 test_expect_success 'create reflog entry' '
+	ref=$(git rev-parse --short refs/notes/commits) &&
 	cat <<-EOF >expect &&
-		a1d8fa6 refs/notes/commits@{0}: notes: Notes added by '\''git notes add'\''
+		$ref refs/notes/commits@{0}: notes: Notes added by '\''git notes add'\''
 	EOF
 	git reflog show refs/notes/commits >actual &&
 	test_cmp expect actual
@@ -78,14 +81,21 @@ test_expect_success 'edit existing notes' '
 	test_path_is_missing .git/NOTES_EDITMSG &&
 	git ls-tree -r refs/notes/commits >actual &&
 	test_line_count = 1 actual &&
-	test "b3" = "$(git notes show)" &&
+	echo b3 >expect &&
+	git notes show >actual &&
+	test_cmp expect actual &&
 	git show HEAD^ &&
 	test_must_fail git notes show HEAD^
 '
 
 test_expect_success 'show notes from treeish' '
-	test "b3" = "$(git notes --ref commits^{tree} show)" &&
-	test "b4" = "$(git notes --ref commits@{1} show)"
+	echo b3 >expect &&
+	git notes --ref commits^{tree} show >actual &&
+	test_cmp expect actual &&
+
+	echo b4 >expect &&
+	git notes --ref commits@{1} show >actual &&
+	test_cmp expect actual
 '
 
 test_expect_success 'cannot edit notes from non-ref' '
@@ -98,7 +108,9 @@ test_expect_success 'cannot "git notes add -m" where notes already exists' '
 	test_path_is_missing .git/NOTES_EDITMSG &&
 	git ls-tree -r refs/notes/commits >actual &&
 	test_line_count = 1 actual &&
-	test "b3" = "$(git notes show)" &&
+	echo b3 >expect &&
+	git notes show >actual &&
+	test_cmp expect actual &&
 	git show HEAD^ &&
 	test_must_fail git notes show HEAD^
 '
@@ -108,7 +120,9 @@ test_expect_success 'can overwrite existing note with "git notes add -f -m"' '
 	test_path_is_missing .git/NOTES_EDITMSG &&
 	git ls-tree -r refs/notes/commits >actual &&
 	test_line_count = 1 actual &&
-	test "b1" = "$(git notes show)" &&
+	echo b1 >expect &&
+	git notes show >actual &&
+	test_cmp expect actual &&
 	git show HEAD^ &&
 	test_must_fail git notes show HEAD^
 '
@@ -118,7 +132,9 @@ test_expect_success 'add w/no options on existing note morphs into edit' '
 	test_path_is_missing .git/NOTES_EDITMSG &&
 	git ls-tree -r refs/notes/commits >actual &&
 	test_line_count = 1 actual &&
-	test "b2" = "$(git notes show)" &&
+	echo b2 >expect &&
+	git notes show >actual &&
+	test_cmp expect actual &&
 	git show HEAD^ &&
 	test_must_fail git notes show HEAD^
 '
@@ -128,14 +144,17 @@ test_expect_success 'can overwrite existing note with "git notes add -f"' '
 	test_path_is_missing .git/NOTES_EDITMSG &&
 	git ls-tree -r refs/notes/commits >actual &&
 	test_line_count = 1 actual &&
-	test "b1" = "$(git notes show)" &&
+	echo b1 >expect &&
+	git notes show >actual &&
+	test_cmp expect actual &&
 	git show HEAD^ &&
 	test_must_fail git notes show HEAD^
 '
 
 test_expect_success 'show notes' '
+	commit=$(git rev-parse HEAD) &&
 	cat >expect <<-EOF &&
-		commit 7a4ca6ee52a974a66cbaa78e33214535dff1d691
+		commit $commit
 		Author: A U Thor <author@example.com>
 		Date:   Thu Apr 7 15:14:13 2005 -0700
 
@@ -144,7 +163,8 @@ test_expect_success 'show notes' '
 		Notes:
 		${indent}b1
 	EOF
-	! (git cat-file commit HEAD | grep b1) &&
+	git cat-file commit HEAD >commits &&
+	! grep b1 commits &&
 	git log -1 >actual &&
 	test_cmp expect actual
 '
@@ -152,8 +172,9 @@ test_expect_success 'show notes' '
 test_expect_success 'show multi-line notes' '
 	test_commit 3rd &&
 	MSG="b3${LF}c3c3c3c3${LF}d3d3d3" git notes add &&
+	commit=$(git rev-parse HEAD) &&
 	cat >expect-multiline <<-EOF &&
-		commit d07d62e5208f22eb5695e7eb47667dc8b9860290
+		commit $commit
 		Author: A U Thor <author@example.com>
 		Date:   Thu Apr 7 15:15:13 2005 -0700
 
@@ -174,8 +195,9 @@ test_expect_success 'show -F notes' '
 	test_commit 4th &&
 	echo "xyzzy" >note5 &&
 	git notes add -F note5 &&
+	commit=$(git rev-parse HEAD) &&
 	cat >expect-F <<-EOF &&
-		commit 0f7aa3ec6325aeb88b910453bb3eb37c49d75c11
+		commit $commit
 		Author: A U Thor <author@example.com>
 		Date:   Thu Apr 7 15:16:13 2005 -0700
 
@@ -198,10 +220,13 @@ test_expect_success 'Re-adding -F notes without -f fails' '
 '
 
 test_expect_success 'git log --pretty=raw does not show notes' '
+	commit=$(git rev-parse HEAD) &&
+	tree=$(git rev-parse HEAD^{tree}) &&
+	parent=$(git rev-parse HEAD^) &&
 	cat >expect <<-EOF &&
-		commit 0f7aa3ec6325aeb88b910453bb3eb37c49d75c11
-		tree 05ac65288c4c4b3b709a020ae94b2ece2f2201ae
-		parent d07d62e5208f22eb5695e7eb47667dc8b9860290
+		commit $commit
+		tree $tree
+		parent $parent
 		author A U Thor <author@example.com> 1112912173 -0700
 		committer C O Mitter <committer@example.com> 1112912173 -0700
 
@@ -291,8 +316,9 @@ test_expect_success 'git log --no-notes resets ref list' '
 test_expect_success 'show -m notes' '
 	test_commit 5th &&
 	git notes add -m spam -m "foo${LF}bar${LF}baz" &&
+	commit=$(git rev-parse HEAD) &&
 	cat >expect-m <<-EOF &&
-		commit 7f9ad8836c775acb134c0a055fc55fb4cd1ba361
+		commit $commit
 		Author: A U Thor <author@example.com>
 		Date:   Thu Apr 7 15:17:13 2005 -0700
 
@@ -313,8 +339,9 @@ test_expect_success 'show -m notes' '
 
 test_expect_success 'remove note with add -f -F /dev/null' '
 	git notes add -f -F /dev/null &&
+	commit=$(git rev-parse HEAD) &&
 	cat >expect-rm-F <<-EOF &&
-		commit 7f9ad8836c775acb134c0a055fc55fb4cd1ba361
+		commit $commit
 		Author: A U Thor <author@example.com>
 		Date:   Thu Apr 7 15:17:13 2005 -0700
 
@@ -356,14 +383,16 @@ test_expect_success 'create note with combination of -m and -F' '
 test_expect_success 'remove note with "git notes remove"' '
 	git notes remove HEAD^ &&
 	git notes remove &&
+	commit=$(git rev-parse HEAD) &&
+	parent=$(git rev-parse HEAD^) &&
 	cat >expect-rm-remove <<-EOF &&
-		commit 7f9ad8836c775acb134c0a055fc55fb4cd1ba361
+		commit $commit
 		Author: A U Thor <author@example.com>
 		Date:   Thu Apr 7 15:17:13 2005 -0700
 
 		${indent}5th
 
-		commit 0f7aa3ec6325aeb88b910453bb3eb37c49d75c11
+		commit $parent
 		Author: A U Thor <author@example.com>
 		Date:   Thu Apr 7 15:16:13 2005 -0700
 
@@ -459,9 +488,13 @@ test_expect_success 'removing with --stdin --ignore-missing' '
 '
 
 test_expect_success 'list notes with "git notes list"' '
-	cat >expect <<-EOF &&
-		c9c6af7f78bc47490dbf3e822cf2f3c24d4b9061 7a4ca6ee52a974a66cbaa78e33214535dff1d691
-		c18dc024e14f08d18d14eea0d747ff692d66d6a3 d07d62e5208f22eb5695e7eb47667dc8b9860290
+	commit_2=$(git rev-parse 2nd) &&
+	commit_3=$(git rev-parse 3rd) &&
+	note_2=$(git rev-parse refs/notes/commits:$commit_2) &&
+	note_3=$(git rev-parse refs/notes/commits:$commit_3) &&
+	sort -t" " -k2 >expect <<-EOF &&
+		$note_2 $commit_2
+		$note_3 $commit_3
 	EOF
 	git notes list >actual &&
 	test_cmp expect actual
@@ -473,9 +506,7 @@ test_expect_success 'list notes with "git notes"' '
 '
 
 test_expect_success 'list specific note with "git notes list <object>"' '
-	cat >expect <<-EOF &&
-		c18dc024e14f08d18d14eea0d747ff692d66d6a3
-	EOF
+	git rev-parse refs/notes/commits:$commit_3 >expect &&
 	git notes list HEAD^^ >actual &&
 	test_cmp expect actual
 '
@@ -498,10 +529,12 @@ test_expect_success 'append to existing note with "git notes append"' '
 '
 
 test_expect_success '"git notes list" does not expand to "git notes list HEAD"' '
-	cat >expect_list <<-EOF &&
-		c9c6af7f78bc47490dbf3e822cf2f3c24d4b9061 7a4ca6ee52a974a66cbaa78e33214535dff1d691
-		4b6ad22357cc8a1296720574b8d2fbc22fab0671 7f9ad8836c775acb134c0a055fc55fb4cd1ba361
-		c18dc024e14f08d18d14eea0d747ff692d66d6a3 d07d62e5208f22eb5695e7eb47667dc8b9860290
+	commit_5=$(git rev-parse 5th) &&
+	note_5=$(git rev-parse refs/notes/commits:$commit_5) &&
+	sort -t" " -k2 >expect_list <<-EOF &&
+		$note_2 $commit_2
+		$note_3 $commit_3
+		$note_5 $commit_5
 	EOF
 	git notes list >actual &&
 	test_cmp expect_list actual
@@ -531,8 +564,9 @@ test_expect_success 'appending empty string to non-existing note does not create
 test_expect_success 'create other note on a different notes ref (setup)' '
 	test_commit 6th &&
 	GIT_NOTES_REF="refs/notes/other" git notes add -m "other note" &&
+	commit=$(git rev-parse HEAD) &&
 	cat >expect-not-other <<-EOF &&
-		commit 2c125331118caba0ff8238b7f4958ac6e93fe39c
+		commit $commit
 		Author: A U Thor <author@example.com>
 		Date:   Thu Apr 7 15:18:13 2005 -0700
 
@@ -569,8 +603,10 @@ test_expect_success 'Do not show note when core.notesRef is overridden' '
 '
 
 test_expect_success 'Show all notes when notes.displayRef=refs/notes/*' '
+	commit=$(git rev-parse HEAD) &&
+	parent=$(git rev-parse HEAD^) &&
 	cat >expect-both <<-EOF &&
-		commit 2c125331118caba0ff8238b7f4958ac6e93fe39c
+		commit $commit
 		Author: A U Thor <author@example.com>
 		Date:   Thu Apr 7 15:18:13 2005 -0700
 
@@ -582,7 +618,7 @@ test_expect_success 'Show all notes when notes.displayRef=refs/notes/*' '
 		Notes (other):
 		${indent}other note
 
-		commit 7f9ad8836c775acb134c0a055fc55fb4cd1ba361
+		commit $parent
 		Author: A U Thor <author@example.com>
 		Date:   Thu Apr 7 15:17:13 2005 -0700
 
@@ -616,8 +652,9 @@ test_expect_success 'notes.displayRef can be given more than once' '
 '
 
 test_expect_success 'notes.displayRef respects order' '
+	commit=$(git rev-parse HEAD) &&
 	cat >expect-both-reversed <<-EOF &&
-		commit 2c125331118caba0ff8238b7f4958ac6e93fe39c
+		commit $commit
 		Author: A U Thor <author@example.com>
 		Date:   Thu Apr 7 15:18:13 2005 -0700
 
@@ -642,14 +679,16 @@ test_expect_success 'GIT_NOTES_DISPLAY_REF works' '
 '
 
 test_expect_success 'GIT_NOTES_DISPLAY_REF overrides config' '
+	commit=$(git rev-parse HEAD) &&
+	parent=$(git rev-parse HEAD^) &&
 	cat >expect-none <<-EOF &&
-		commit 2c125331118caba0ff8238b7f4958ac6e93fe39c
+		commit $commit
 		Author: A U Thor <author@example.com>
 		Date:   Thu Apr 7 15:18:13 2005 -0700
 
 		${indent}6th
 
-		commit 7f9ad8836c775acb134c0a055fc55fb4cd1ba361
+		commit $parent
 		Author: A U Thor <author@example.com>
 		Date:   Thu Apr 7 15:17:13 2005 -0700
 
@@ -666,8 +705,9 @@ test_expect_success '--show-notes=* adds to GIT_NOTES_DISPLAY_REF' '
 '
 
 test_expect_success '--no-standard-notes' '
+	commit=$(git rev-parse HEAD) &&
 	cat >expect-commits <<-EOF &&
-		commit 2c125331118caba0ff8238b7f4958ac6e93fe39c
+		commit $commit
 		Author: A U Thor <author@example.com>
 		Date:   Thu Apr 7 15:18:13 2005 -0700
 
@@ -700,7 +740,8 @@ test_expect_success 'Allow notes on non-commits (trees, blobs, tags)' '
 	git notes show HEAD: >actual &&
 	test_cmp expect actual &&
 	echo "Note on a blob" >expect &&
-	filename=$(git ls-tree --name-only HEAD | head -n1) &&
+	git ls-tree --name-only HEAD >files &&
+	filename=$(head -n1 files) &&
 	git notes add -m "Note on a blob" HEAD:$filename &&
 	git notes show HEAD:$filename >actual &&
 	test_cmp expect actual &&
@@ -712,8 +753,10 @@ test_expect_success 'Allow notes on non-commits (trees, blobs, tags)' '
 '
 
 test_expect_success 'create note from other note with "git notes add -C"' '
+	test_commit 7th &&
+	commit=$(git rev-parse HEAD) &&
 	cat >expect <<-EOF &&
-		commit fb01e0ca8c33b6cc0c6451dde747f97df567cb5c
+		commit $commit
 		Author: A U Thor <author@example.com>
 		Date:   Thu Apr 7 15:19:13 2005 -0700
 
@@ -722,11 +765,13 @@ test_expect_success 'create note from other note with "git notes add -C"' '
 		Notes:
 		${indent}order test
 	EOF
-	test_commit 7th &&
-	git notes add -C $(git notes list HEAD^) &&
+	note=$(git notes list HEAD^) &&
+	git notes add -C $note &&
 	git log -1 >actual &&
 	test_cmp expect actual &&
-	test "$(git notes list HEAD)" = "$(git notes list HEAD^)"
+	git notes list HEAD^ >expect &&
+	git notes list HEAD >actual &&
+	test_cmp expect actual
 '
 
 test_expect_success 'create note from non-existing note with "git notes add -C" fails' '
@@ -744,8 +789,9 @@ test_expect_success 'create note from non-blob with "git notes add -C" fails' '
 '
 
 test_expect_success 'create note from blob with "git notes add -C" reuses blob id' '
+	commit=$(git rev-parse HEAD) &&
 	cat >expect <<-EOF &&
-		commit 9a4c31c7f722b5d517e92c64e932dd751e1413bf
+		commit $commit
 		Author: A U Thor <author@example.com>
 		Date:   Thu Apr 7 15:20:13 2005 -0700
 
@@ -754,16 +800,19 @@ test_expect_success 'create note from blob with "git notes add -C" reuses blob i
 		Notes:
 		${indent}This is a blob object
 	EOF
-	blob=$(echo "This is a blob object" | git hash-object -w --stdin) &&
-	git notes add -C $blob &&
+	echo "This is a blob object" | git hash-object -w --stdin >blob &&
+	git notes add -C $(cat blob) &&
 	git log -1 >actual &&
 	test_cmp expect actual &&
-	test "$(git notes list HEAD)" = "$blob"
+	git notes list HEAD >actual &&
+	test_cmp blob actual
 '
 
 test_expect_success 'create note from other note with "git notes add -c"' '
+	test_commit 9th &&
+	commit=$(git rev-parse HEAD) &&
 	cat >expect <<-EOF &&
-		commit 2e0db4bc649e174d667a1cde19e725cf897a5bd2
+		commit $commit
 		Author: A U Thor <author@example.com>
 		Date:   Thu Apr 7 15:21:13 2005 -0700
 
@@ -772,8 +821,8 @@ test_expect_success 'create note from other note with "git notes add -c"' '
 		Notes:
 		${indent}yet another note
 	EOF
-	test_commit 9th &&
-	MSG="yet another note" git notes add -c $(git notes list HEAD^^) &&
+	note=$(git notes list HEAD^^) &&
+	MSG="yet another note" git notes add -c $note &&
 	git log -1 >actual &&
 	test_cmp expect actual
 '
@@ -785,8 +834,9 @@ test_expect_success 'create note from non-existing note with "git notes add -c"
 '
 
 test_expect_success 'append to note from other note with "git notes append -C"' '
+	commit=$(git rev-parse HEAD^) &&
 	cat >expect <<-EOF &&
-		commit 2e0db4bc649e174d667a1cde19e725cf897a5bd2
+		commit $commit
 		Author: A U Thor <author@example.com>
 		Date:   Thu Apr 7 15:21:13 2005 -0700
 
@@ -797,14 +847,16 @@ test_expect_success 'append to note from other note with "git notes append -C"'
 		${indent}
 		${indent}yet another note
 	EOF
-	git notes append -C $(git notes list HEAD^) HEAD^ &&
+	note=$(git notes list HEAD^) &&
+	git notes append -C $note HEAD^ &&
 	git log -1 HEAD^ >actual &&
 	test_cmp expect actual
 '
 
 test_expect_success 'create note from other note with "git notes append -c"' '
+	commit=$(git rev-parse HEAD) &&
 	cat >expect <<-EOF &&
-		commit 7c3b87ab368f81e11b1ea87b2ab99a71ccd25406
+		commit $commit
 		Author: A U Thor <author@example.com>
 		Date:   Thu Apr 7 15:22:13 2005 -0700
 
@@ -813,14 +865,16 @@ test_expect_success 'create note from other note with "git notes append -c"' '
 		Notes:
 		${indent}other note
 	EOF
-	MSG="other note" git notes append -c $(git notes list HEAD^) &&
+	note=$(git notes list HEAD^) &&
+	MSG="other note" git notes append -c $note &&
 	git log -1 >actual &&
 	test_cmp expect actual
 '
 
 test_expect_success 'append to note from other note with "git notes append -c"' '
+	commit=$(git rev-parse HEAD) &&
 	cat >expect <<-EOF &&
-		commit 7c3b87ab368f81e11b1ea87b2ab99a71ccd25406
+		commit $commit
 		Author: A U Thor <author@example.com>
 		Date:   Thu Apr 7 15:22:13 2005 -0700
 
@@ -831,14 +885,37 @@ test_expect_success 'append to note from other note with "git notes append -c"'
 		${indent}
 		${indent}yet another note
 	EOF
-	MSG="yet another note" git notes append -c $(git notes list HEAD) &&
+	note=$(git notes list HEAD) &&
+	MSG="yet another note" git notes append -c $note &&
 	git log -1 >actual &&
 	test_cmp expect actual
 '
 
 test_expect_success 'copy note with "git notes copy"' '
+	commit=$(git rev-parse 4th) &&
 	cat >expect <<-EOF &&
-		commit a446fff8777efdc6eb8f4b7c8a5ff699484df0d5
+		commit $commit
+		Author: A U Thor <author@example.com>
+		Date:   Thu Apr 7 15:16:13 2005 -0700
+
+		${indent}4th
+
+		Notes:
+		${indent}This is a blob object
+	EOF
+	git notes copy 8th 4th &&
+	git log 3rd..4th >actual &&
+	test_cmp expect actual &&
+	git notes list 4th >expect &&
+	git notes list 8th >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'copy note with "git notes copy" with default' '
+	test_commit 11th &&
+	commit=$(git rev-parse HEAD) &&
+	cat >expect <<-EOF &&
+		commit $commit
 		Author: A U Thor <author@example.com>
 		Date:   Thu Apr 7 15:23:13 2005 -0700
 
@@ -849,23 +926,59 @@ test_expect_success 'copy note with "git notes copy"' '
 		${indent}
 		${indent}yet another note
 	EOF
-	test_commit 11th &&
-	git notes copy HEAD^ HEAD &&
+	git notes copy HEAD^ &&
 	git log -1 >actual &&
 	test_cmp expect actual &&
-	test "$(git notes list HEAD)" = "$(git notes list HEAD^)"
+	git notes list HEAD^ >expect &&
+	git notes list HEAD >actual &&
+	test_cmp expect actual
 '
 
 test_expect_success 'prevent overwrite with "git notes copy"' '
 	test_must_fail git notes copy HEAD~2 HEAD &&
+	cat >expect <<-EOF &&
+		commit $commit
+		Author: A U Thor <author@example.com>
+		Date:   Thu Apr 7 15:23:13 2005 -0700
+
+		${indent}11th
+
+		Notes:
+		${indent}other note
+		${indent}
+		${indent}yet another note
+	EOF
 	git log -1 >actual &&
 	test_cmp expect actual &&
-	test "$(git notes list HEAD)" = "$(git notes list HEAD^)"
+	git notes list HEAD^ >expect &&
+	git notes list HEAD >actual &&
+	test_cmp expect actual
 '
 
 test_expect_success 'allow overwrite with "git notes copy -f"' '
+	commit=$(git rev-parse HEAD) &&
+	cat >expect <<-EOF &&
+		commit $commit
+		Author: A U Thor <author@example.com>
+		Date:   Thu Apr 7 15:23:13 2005 -0700
+
+		${indent}11th
+
+		Notes:
+		${indent}This is a blob object
+	EOF
+	git notes copy -f HEAD~3 HEAD &&
+	git log -1 >actual &&
+	test_cmp expect actual &&
+	git notes list HEAD~3 >expect &&
+	git notes list HEAD >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'allow overwrite with "git notes copy -f" with default' '
+	commit=$(git rev-parse HEAD) &&
 	cat >expect <<-EOF &&
-		commit a446fff8777efdc6eb8f4b7c8a5ff699484df0d5
+		commit $commit
 		Author: A U Thor <author@example.com>
 		Date:   Thu Apr 7 15:23:13 2005 -0700
 
@@ -876,10 +989,12 @@ test_expect_success 'allow overwrite with "git notes copy -f"' '
 		${indent}
 		${indent}yet another note
 	EOF
-	git notes copy -f HEAD~2 HEAD &&
+	git notes copy -f HEAD~2 &&
 	git log -1 >actual &&
 	test_cmp expect actual &&
-	test "$(git notes list HEAD)" = "$(git notes list HEAD~2)"
+	git notes list HEAD~2 >expect &&
+	git notes list HEAD >actual &&
+	test_cmp expect actual
 '
 
 test_expect_success 'cannot copy note from object without notes' '
@@ -889,8 +1004,10 @@ test_expect_success 'cannot copy note from object without notes' '
 '
 
 test_expect_success 'git notes copy --stdin' '
+	commit=$(git rev-parse HEAD) &&
+	parent=$(git rev-parse HEAD^) &&
 	cat >expect <<-EOF &&
-		commit e871aa61182b1d95d0a6fb75445d891722863b6b
+		commit $commit
 		Author: A U Thor <author@example.com>
 		Date:   Thu Apr 7 15:25:13 2005 -0700
 
@@ -901,7 +1018,7 @@ test_expect_success 'git notes copy --stdin' '
 		${indent}
 		${indent}yet another note
 
-		commit 65e263ded02ae4e8839bc151095113737579dc12
+		commit $parent
 		Author: A U Thor <author@example.com>
 		Date:   Thu Apr 7 15:24:13 2005 -0700
 
@@ -912,41 +1029,57 @@ test_expect_success 'git notes copy --stdin' '
 		${indent}
 		${indent}yet another note
 	EOF
-	(echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^) &&
-	echo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) |
-	git notes copy --stdin &&
+	from=$(git rev-parse HEAD~3) &&
+	to=$(git rev-parse HEAD^) &&
+	echo "$from" "$to" >copy &&
+	from=$(git rev-parse HEAD~2) &&
+	to=$(git rev-parse HEAD) &&
+	echo "$from" "$to" >>copy &&
+	git notes copy --stdin <copy &&
 	git log -2 >actual &&
 	test_cmp expect actual &&
-	test "$(git notes list HEAD)" = "$(git notes list HEAD~2)" &&
-	test "$(git notes list HEAD^)" = "$(git notes list HEAD~3)"
+	git notes list HEAD~2 >expect &&
+	git notes list HEAD >actual &&
+	test_cmp expect actual &&
+	git notes list HEAD~3 >expect &&
+	git notes list HEAD^ >actual &&
+	test_cmp expect actual
 '
 
 test_expect_success 'git notes copy --for-rewrite (unconfigured)' '
+	test_commit 14th &&
+	test_commit 15th &&
+	commit=$(git rev-parse HEAD) &&
+	parent=$(git rev-parse HEAD^) &&
 	cat >expect <<-EOF &&
-		commit 4acf42e847e7fffbbf89ee365c20ac7caf40de89
+		commit $commit
 		Author: A U Thor <author@example.com>
 		Date:   Thu Apr 7 15:27:13 2005 -0700
 
 		${indent}15th
 
-		commit 07c85d77059393ed0154b8c96906547a59dfcddd
+		commit $parent
 		Author: A U Thor <author@example.com>
 		Date:   Thu Apr 7 15:26:13 2005 -0700
 
 		${indent}14th
 	EOF
-	test_commit 14th &&
-	test_commit 15th &&
-	(echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^) &&
-	echo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) |
-	git notes copy --for-rewrite=foo &&
+	from=$(git rev-parse HEAD~3) &&
+	to=$(git rev-parse HEAD^) &&
+	echo "$from" "$to" >copy &&
+	from=$(git rev-parse HEAD~2) &&
+	to=$(git rev-parse HEAD) &&
+	echo "$from" "$to" >>copy &&
+	git notes copy --for-rewrite=foo <copy &&
 	git log -2 >actual &&
 	test_cmp expect actual
 '
 
 test_expect_success 'git notes copy --for-rewrite (enabled)' '
+	commit=$(git rev-parse HEAD) &&
+	parent=$(git rev-parse HEAD^) &&
 	cat >expect <<-EOF &&
-		commit 4acf42e847e7fffbbf89ee365c20ac7caf40de89
+		commit $commit
 		Author: A U Thor <author@example.com>
 		Date:   Thu Apr 7 15:27:13 2005 -0700
 
@@ -957,7 +1090,7 @@ test_expect_success 'git notes copy --for-rewrite (enabled)' '
 		${indent}
 		${indent}yet another note
 
-		commit 07c85d77059393ed0154b8c96906547a59dfcddd
+		commit $parent
 		Author: A U Thor <author@example.com>
 		Date:   Thu Apr 7 15:26:13 2005 -0700
 
@@ -970,24 +1103,31 @@ test_expect_success 'git notes copy --for-rewrite (enabled)' '
 	EOF
 	test_config notes.rewriteMode overwrite &&
 	test_config notes.rewriteRef "refs/notes/*" &&
-	(echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^) &&
-	echo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) |
-	git notes copy --for-rewrite=foo &&
+	from=$(git rev-parse HEAD~3) &&
+	to=$(git rev-parse HEAD^) &&
+	echo "$from" "$to" >copy &&
+	from=$(git rev-parse HEAD~2) &&
+	to=$(git rev-parse HEAD) &&
+	echo "$from" "$to" >>copy &&
+	git notes copy --for-rewrite=foo <copy &&
 	git log -2 >actual &&
 	test_cmp expect actual
 '
 
 test_expect_success 'git notes copy --for-rewrite (disabled)' '
 	test_config notes.rewrite.bar false &&
-	echo $(git rev-parse HEAD~3) $(git rev-parse HEAD) |
-	git notes copy --for-rewrite=bar &&
+	from=$(git rev-parse HEAD~3) &&
+	to=$(git rev-parse HEAD) &&
+	echo "$from" "$to" >copy &&
+	git notes copy --for-rewrite=bar <copy &&
 	git log -2 >actual &&
 	test_cmp expect actual
 '
 
 test_expect_success 'git notes copy --for-rewrite (overwrite)' '
+	commit=$(git rev-parse HEAD) &&
 	cat >expect <<-EOF &&
-		commit 4acf42e847e7fffbbf89ee365c20ac7caf40de89
+		commit $commit
 		Author: A U Thor <author@example.com>
 		Date:   Thu Apr 7 15:27:13 2005 -0700
 
@@ -999,8 +1139,10 @@ test_expect_success 'git notes copy --for-rewrite (overwrite)' '
 	git notes add -f -m"a fresh note" HEAD^ &&
 	test_config notes.rewriteMode overwrite &&
 	test_config notes.rewriteRef "refs/notes/*" &&
-	echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
-	git notes copy --for-rewrite=foo &&
+	from=$(git rev-parse HEAD^) &&
+	to=$(git rev-parse HEAD) &&
+	echo "$from" "$to" >copy &&
+	git notes copy --for-rewrite=foo <copy &&
 	git log -1 >actual &&
 	test_cmp expect actual
 '
@@ -1008,15 +1150,18 @@ test_expect_success 'git notes copy --for-rewrite (overwrite)' '
 test_expect_success 'git notes copy --for-rewrite (ignore)' '
 	test_config notes.rewriteMode ignore &&
 	test_config notes.rewriteRef "refs/notes/*" &&
-	echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
-	git notes copy --for-rewrite=foo &&
+	from=$(git rev-parse HEAD^) &&
+	to=$(git rev-parse HEAD) &&
+	echo "$from" "$to" >copy &&
+	git notes copy --for-rewrite=foo <copy &&
 	git log -1 >actual &&
 	test_cmp expect actual
 '
 
 test_expect_success 'git notes copy --for-rewrite (append)' '
+	commit=$(git rev-parse HEAD) &&
 	cat >expect <<-EOF &&
-		commit 4acf42e847e7fffbbf89ee365c20ac7caf40de89
+		commit $commit
 		Author: A U Thor <author@example.com>
 		Date:   Thu Apr 7 15:27:13 2005 -0700
 
@@ -1030,15 +1175,18 @@ test_expect_success 'git notes copy --for-rewrite (append)' '
 	git notes add -f -m"another fresh note" HEAD^ &&
 	test_config notes.rewriteMode concatenate &&
 	test_config notes.rewriteRef "refs/notes/*" &&
-	echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
-	git notes copy --for-rewrite=foo &&
+	from=$(git rev-parse HEAD^) &&
+	to=$(git rev-parse HEAD) &&
+	echo "$from" "$to" >copy &&
+	git notes copy --for-rewrite=foo <copy &&
 	git log -1 >actual &&
 	test_cmp expect actual
 '
 
 test_expect_success 'git notes copy --for-rewrite (append two to one)' '
+	commit=$(git rev-parse HEAD) &&
 	cat >expect <<-EOF &&
-		commit 4acf42e847e7fffbbf89ee365c20ac7caf40de89
+		commit $commit
 		Author: A U Thor <author@example.com>
 		Date:   Thu Apr 7 15:27:13 2005 -0700
 
@@ -1057,9 +1205,13 @@ test_expect_success 'git notes copy --for-rewrite (append two to one)' '
 	git notes add -f -m"append 2" HEAD^^ &&
 	test_config notes.rewriteMode concatenate &&
 	test_config notes.rewriteRef "refs/notes/*" &&
-	(echo $(git rev-parse HEAD^) $(git rev-parse HEAD) &&
-	echo $(git rev-parse HEAD^^) $(git rev-parse HEAD)) |
-	git notes copy --for-rewrite=foo &&
+	from=$(git rev-parse HEAD^) &&
+	to=$(git rev-parse HEAD) &&
+	echo "$from" "$to" >copy &&
+	from=$(git rev-parse HEAD^^) &&
+	to=$(git rev-parse HEAD) &&
+	echo "$from" "$to" >>copy &&
+	git notes copy --for-rewrite=foo <copy &&
 	git log -1 >actual &&
 	test_cmp expect actual
 '
@@ -1068,15 +1220,18 @@ test_expect_success 'git notes copy --for-rewrite (append empty)' '
 	git notes remove HEAD^ &&
 	test_config notes.rewriteMode concatenate &&
 	test_config notes.rewriteRef "refs/notes/*" &&
-	echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
-	git notes copy --for-rewrite=foo &&
+	from=$(git rev-parse HEAD^) &&
+	to=$(git rev-parse HEAD) &&
+	echo "$from" "$to" >copy &&
+	git notes copy --for-rewrite=foo <copy &&
 	git log -1 >actual &&
 	test_cmp expect actual
 '
 
 test_expect_success 'GIT_NOTES_REWRITE_MODE works' '
+	commit=$(git rev-parse HEAD) &&
 	cat >expect <<-EOF &&
-		commit 4acf42e847e7fffbbf89ee365c20ac7caf40de89
+		commit $commit
 		Author: A U Thor <author@example.com>
 		Date:   Thu Apr 7 15:27:13 2005 -0700
 
@@ -1088,15 +1243,18 @@ test_expect_success 'GIT_NOTES_REWRITE_MODE works' '
 	test_config notes.rewriteMode concatenate &&
 	test_config notes.rewriteRef "refs/notes/*" &&
 	git notes add -f -m"replacement note 1" HEAD^ &&
-	echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
-	GIT_NOTES_REWRITE_MODE=overwrite git notes copy --for-rewrite=foo &&
+	from=$(git rev-parse HEAD^) &&
+	to=$(git rev-parse HEAD) &&
+	echo "$from" "$to" >copy &&
+	GIT_NOTES_REWRITE_MODE=overwrite git notes copy --for-rewrite=foo <copy &&
 	git log -1 >actual &&
 	test_cmp expect actual
 '
 
 test_expect_success 'GIT_NOTES_REWRITE_REF works' '
+	commit=$(git rev-parse HEAD) &&
 	cat >expect <<-EOF &&
-		commit 4acf42e847e7fffbbf89ee365c20ac7caf40de89
+		commit $commit
 		Author: A U Thor <author@example.com>
 		Date:   Thu Apr 7 15:27:13 2005 -0700
 
@@ -1108,9 +1266,11 @@ test_expect_success 'GIT_NOTES_REWRITE_REF works' '
 	git notes add -f -m"replacement note 2" HEAD^ &&
 	test_config notes.rewriteMode overwrite &&
 	test_unconfig notes.rewriteRef &&
-	echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
+	from=$(git rev-parse HEAD^) &&
+	to=$(git rev-parse HEAD) &&
+	echo "$from" "$to" >copy &&
 	GIT_NOTES_REWRITE_REF=refs/notes/commits:refs/notes/other \
-		git notes copy --for-rewrite=foo &&
+		git notes copy --for-rewrite=foo <copy &&
 	git log -1 >actual &&
 	test_cmp expect actual
 '
@@ -1119,41 +1279,55 @@ test_expect_success 'GIT_NOTES_REWRITE_REF overrides config' '
 	git notes add -f -m"replacement note 3" HEAD^ &&
 	test_config notes.rewriteMode overwrite &&
 	test_config notes.rewriteRef refs/notes/other &&
-	echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
+	from=$(git rev-parse HEAD^) &&
+	to=$(git rev-parse HEAD) &&
+	echo "$from" "$to" >copy &&
 	GIT_NOTES_REWRITE_REF=refs/notes/commits \
-		git notes copy --for-rewrite=foo &&
+		git notes copy --for-rewrite=foo <copy &&
 	git log -1 >actual &&
 	grep "replacement note 3" actual
 '
 
 test_expect_success 'git notes copy diagnoses too many or too few parameters' '
-	test_must_fail git notes copy &&
-	test_must_fail git notes copy one two three
+	test_must_fail git notes copy 2>error &&
+	test_i18ngrep "too few parameters" error &&
+	test_must_fail git notes copy one two three 2>error &&
+	test_i18ngrep "too many parameters" error
 '
 
 test_expect_success 'git notes get-ref expands refs/heads/master to refs/notes/refs/heads/master' '
 	test_unconfig core.notesRef &&
 	sane_unset GIT_NOTES_REF &&
-	test "$(git notes --ref=refs/heads/master get-ref)" = "refs/notes/refs/heads/master"
+	echo refs/notes/refs/heads/master >expect &&
+	git notes --ref=refs/heads/master get-ref >actual &&
+	test_cmp expect actual
 '
 
 test_expect_success 'git notes get-ref (no overrides)' '
 	test_unconfig core.notesRef &&
 	sane_unset GIT_NOTES_REF &&
-	test "$(git notes get-ref)" = "refs/notes/commits"
+	echo refs/notes/commits >expect &&
+	git notes get-ref >actual &&
+	test_cmp expect actual
 '
 
 test_expect_success 'git notes get-ref (core.notesRef)' '
 	test_config core.notesRef refs/notes/foo &&
-	test "$(git notes get-ref)" = "refs/notes/foo"
+	echo refs/notes/foo >expect &&
+	git notes get-ref >actual &&
+	test_cmp expect actual
 '
 
 test_expect_success 'git notes get-ref (GIT_NOTES_REF)' '
-	test "$(GIT_NOTES_REF=refs/notes/bar git notes get-ref)" = "refs/notes/bar"
+	echo refs/notes/bar >expect &&
+	GIT_NOTES_REF=refs/notes/bar git notes get-ref >actual &&
+	test_cmp expect actual
 '
 
 test_expect_success 'git notes get-ref (--ref)' '
-	test "$(GIT_NOTES_REF=refs/notes/bar git notes --ref=baz get-ref)" = "refs/notes/baz"
+	echo refs/notes/baz >expect &&
+	GIT_NOTES_REF=refs/notes/bar git notes --ref=baz get-ref >actual &&
+	test_cmp expect actual
 '
 
 test_expect_success 'setup testing of empty notes' '
diff --git a/third_party/git/t/t3305-notes-fanout.sh b/third_party/git/t/t3305-notes-fanout.sh
index 54460beec4..94c1b02251 100755
--- a/third_party/git/t/t3305-notes-fanout.sh
+++ b/third_party/git/t/t3305-notes-fanout.sh
@@ -4,6 +4,38 @@ test_description='Test that adding/removing many notes triggers automatic fanout
 
 . ./test-lib.sh
 
+path_has_fanout() {
+	path=$1 &&
+	fanout=$2 &&
+	after_last_slash=$(($(test_oid hexsz) - $fanout * 2)) &&
+	echo $path | grep -q "^\([0-9a-f]\{2\}/\)\{$fanout\}[0-9a-f]\{$after_last_slash\}$"
+}
+
+touched_one_note_with_fanout() {
+	notes_commit=$1 &&
+	modification=$2 &&  # 'A' for addition, 'D' for deletion
+	fanout=$3 &&
+	diff=$(git diff-tree --no-commit-id --name-status --root -r $notes_commit) &&
+	path=$(echo $diff | sed -e "s/^$modification[\t ]//") &&
+	path_has_fanout "$path" $fanout;
+}
+
+all_notes_have_fanout() {
+	notes_commit=$1 &&
+	fanout=$2 &&
+	git ls-tree -r --name-only $notes_commit 2>/dev/null |
+	while read path
+	do
+		path_has_fanout $path $fanout || return 1
+	done
+}
+
+test_expect_success 'tweak test environment' '
+	git checkout -b nondeterminism &&
+	test_commit A &&
+	git checkout --orphan with_notes;
+'
+
 test_expect_success 'creating many notes with git-notes' '
 	num_notes=300 &&
 	i=0 &&
@@ -20,7 +52,7 @@ test_expect_success 'creating many notes with git-notes' '
 
 test_expect_success 'many notes created correctly with git-notes' '
 	git log | grep "^    " > output &&
-	i=300 &&
+	i=$num_notes &&
 	while test $i -gt 0
 	do
 		echo "    commit #$i" &&
@@ -30,39 +62,46 @@ test_expect_success 'many notes created correctly with git-notes' '
 	test_cmp expect output
 '
 
-test_expect_success 'many notes created with git-notes triggers fanout' '
-	# Expect entire notes tree to have a fanout == 1
-	git ls-tree -r --name-only refs/notes/commits |
-	while read path
+test_expect_success 'stable fanout 0 is followed by stable fanout 1' '
+	i=$num_notes &&
+	fanout=0 &&
+	while test $i -gt 0
 	do
-		case "$path" in
-		??/??????????????????????????????????????)
-			: true
-			;;
-		*)
-			echo "Invalid path \"$path\"" &&
-			return 1
-			;;
-		esac
-	done
+		i=$(($i - 1)) &&
+		if touched_one_note_with_fanout refs/notes/commits~$i A $fanout
+		then
+			continue
+		elif test $fanout -eq 0
+		then
+			fanout=1 &&
+			if all_notes_have_fanout refs/notes/commits~$i $fanout
+			then
+				echo "Fanout 0 -> 1 at refs/notes/commits~$i" &&
+				continue
+			fi
+		fi &&
+		echo "Failed fanout=$fanout check at refs/notes/commits~$i" &&
+		git ls-tree -r --name-only refs/notes/commits~$i &&
+		return 1
+	done &&
+	all_notes_have_fanout refs/notes/commits 1
 '
 
 test_expect_success 'deleting most notes with git-notes' '
-	num_notes=250 &&
+	remove_notes=285 &&
 	i=0 &&
 	git rev-list HEAD |
-	while test $i -lt $num_notes && read sha1
+	while test $i -lt $remove_notes && read sha1
 	do
 		i=$(($i + 1)) &&
 		test_tick &&
-		git notes remove "$sha1" ||
-		exit 1
+		git notes remove "$sha1" 2>/dev/null || return 1
 	done
 '
 
 test_expect_success 'most notes deleted correctly with git-notes' '
-	git log HEAD~250 | grep "^    " > output &&
-	i=50 &&
+	git log HEAD~$remove_notes | grep "^    " > output &&
+	i=$(($num_notes - $remove_notes)) &&
 	while test $i -gt 0
 	do
 		echo "    commit #$i" &&
@@ -72,21 +111,29 @@ test_expect_success 'most notes deleted correctly with git-notes' '
 	test_cmp expect output
 '
 
-test_expect_success 'deleting most notes triggers fanout consolidation' '
-	# Expect entire notes tree to have a fanout == 0
-	git ls-tree -r --name-only refs/notes/commits |
-	while read path
+test_expect_success 'stable fanout 1 is followed by stable fanout 0' '
+	i=$remove_notes &&
+	fanout=1 &&
+	while test $i -gt 0
 	do
-		case "$path" in
-		????????????????????????????????????????)
-			: true
-			;;
-		*)
-			echo "Invalid path \"$path\"" &&
-			return 1
-			;;
-		esac
-	done
+		i=$(($i - 1)) &&
+		if touched_one_note_with_fanout refs/notes/commits~$i D $fanout
+		then
+			continue
+		elif test $fanout -eq 1
+		then
+			fanout=0 &&
+			if all_notes_have_fanout refs/notes/commits~$i $fanout
+			then
+				echo "Fanout 1 -> 0 at refs/notes/commits~$i" &&
+				continue
+			fi
+		fi &&
+		echo "Failed fanout=$fanout check at refs/notes/commits~$i" &&
+		git ls-tree -r --name-only refs/notes/commits~$i &&
+		return 1
+	done &&
+	all_notes_have_fanout refs/notes/commits 0
 '
 
 test_done
diff --git a/third_party/git/t/t3306-notes-prune.sh b/third_party/git/t/t3306-notes-prune.sh
index 61748088eb..8f4102ff9e 100755
--- a/third_party/git/t/t3306-notes-prune.sh
+++ b/third_party/git/t/t3306-notes-prune.sh
@@ -11,23 +11,26 @@ test_expect_success 'setup: create a few commits with notes' '
 	test_tick &&
 	git commit -m 1st &&
 	git notes add -m "Note #1" &&
+	first=$(git rev-parse HEAD) &&
 	: > file2 &&
 	git add file2 &&
 	test_tick &&
 	git commit -m 2nd &&
 	git notes add -m "Note #2" &&
+	second=$(git rev-parse HEAD) &&
 	: > file3 &&
 	git add file3 &&
 	test_tick &&
 	git commit -m 3rd &&
-	COMMIT_FILE=.git/objects/5e/e1c35e83ea47cd3cc4f8cbee0568915fbbbd29 &&
+	third=$(git rev-parse HEAD) &&
+	COMMIT_FILE=$(echo $third | sed "s!^..!.git/objects/&/!") &&
 	test -f $COMMIT_FILE &&
 	test-tool chmtime =+0 $COMMIT_FILE &&
 	git notes add -m "Note #3"
 '
 
 cat > expect <<END_OF_LOG
-commit 5ee1c35e83ea47cd3cc4f8cbee0568915fbbbd29
+commit $third
 Author: A U Thor <author@example.com>
 Date:   Thu Apr 7 15:15:13 2005 -0700
 
@@ -36,7 +39,7 @@ Date:   Thu Apr 7 15:15:13 2005 -0700
 Notes:
     Note #3
 
-commit 08341ad9e94faa089d60fd3f523affb25c6da189
+commit $second
 Author: A U Thor <author@example.com>
 Date:   Thu Apr 7 15:14:13 2005 -0700
 
@@ -45,7 +48,7 @@ Date:   Thu Apr 7 15:14:13 2005 -0700
 Notes:
     Note #2
 
-commit ab5f302035f2e7aaf04265f08b42034c23256e1f
+commit $first
 Author: A U Thor <author@example.com>
 Date:   Thu Apr 7 15:13:13 2005 -0700
 
@@ -70,16 +73,16 @@ test_expect_success 'remove some commits' '
 
 test_expect_success 'verify that commits are gone' '
 
-	test_must_fail git cat-file -p 5ee1c35e83ea47cd3cc4f8cbee0568915fbbbd29 &&
-	git cat-file -p 08341ad9e94faa089d60fd3f523affb25c6da189 &&
-	git cat-file -p ab5f302035f2e7aaf04265f08b42034c23256e1f
+	test_must_fail git cat-file -p $third &&
+	git cat-file -p $second &&
+	git cat-file -p $first
 '
 
 test_expect_success 'verify that notes are still present' '
 
-	git notes show 5ee1c35e83ea47cd3cc4f8cbee0568915fbbbd29 &&
-	git notes show 08341ad9e94faa089d60fd3f523affb25c6da189 &&
-	git notes show ab5f302035f2e7aaf04265f08b42034c23256e1f
+	git notes show $third &&
+	git notes show $second &&
+	git notes show $first
 '
 
 test_expect_success 'prune -n does not remove notes' '
@@ -90,13 +93,10 @@ test_expect_success 'prune -n does not remove notes' '
 	test_cmp expect actual
 '
 
-cat > expect <<EOF
-5ee1c35e83ea47cd3cc4f8cbee0568915fbbbd29
-EOF
 
 test_expect_success 'prune -n lists prunable notes' '
 
-
+	echo $third >expect &&
 	git notes prune -n > actual &&
 	test_cmp expect actual
 '
@@ -109,9 +109,9 @@ test_expect_success 'prune notes' '
 
 test_expect_success 'verify that notes are gone' '
 
-	test_must_fail git notes show 5ee1c35e83ea47cd3cc4f8cbee0568915fbbbd29 &&
-	git notes show 08341ad9e94faa089d60fd3f523affb25c6da189 &&
-	git notes show ab5f302035f2e7aaf04265f08b42034c23256e1f
+	test_must_fail git notes show $third &&
+	git notes show $second &&
+	git notes show $first
 '
 
 test_expect_success 'remove some commits' '
@@ -121,21 +121,18 @@ test_expect_success 'remove some commits' '
 	git gc --prune=now
 '
 
-cat > expect <<EOF
-08341ad9e94faa089d60fd3f523affb25c6da189
-EOF
-
 test_expect_success 'prune -v notes' '
 
+	echo $second >expect &&
 	git notes prune -v > actual &&
 	test_cmp expect actual
 '
 
 test_expect_success 'verify that notes are gone' '
 
-	test_must_fail git notes show 5ee1c35e83ea47cd3cc4f8cbee0568915fbbbd29 &&
-	test_must_fail git notes show 08341ad9e94faa089d60fd3f523affb25c6da189 &&
-	git notes show ab5f302035f2e7aaf04265f08b42034c23256e1f
+	test_must_fail git notes show $third &&
+	test_must_fail git notes show $second &&
+	git notes show $first
 '
 
 test_done
diff --git a/third_party/git/t/t3308-notes-merge.sh b/third_party/git/t/t3308-notes-merge.sh
index d60588ec8f..d69c84c640 100755
--- a/third_party/git/t/t3308-notes-merge.sh
+++ b/third_party/git/t/t3308-notes-merge.sh
@@ -20,7 +20,33 @@ test_expect_success setup '
 	git notes add -m "Notes on 3rd commit" 3rd &&
 	git notes add -m "Notes on 4th commit" 4th &&
 	# Copy notes to remote-notes
-	git fetch . refs/notes/*:refs/remote-notes/origin/*
+	git fetch . refs/notes/*:refs/remote-notes/origin/* &&
+
+	test_oid_cache <<-EOF
+	hash4a sha1:5e93d24084d32e1cb61f7070505b9d2530cca987
+	hash3a sha1:8366731eeee53787d2bdf8fc1eff7d94757e8da0
+	hash2a sha1:eede89064cd42441590d6afec6c37b321ada3389
+	hash1a sha1:daa55ffad6cb99bf64226532147ffcaf5ce8bdd1
+	hash5b sha1:0f2efbd00262f2fd41dfae33df8765618eeacd99
+	hash4b sha1:dec2502dac3ea161543f71930044deff93fa945c
+	hash3b sha1:4069cdb399fd45463ec6eef8e051a16a03592d91
+	hash2c sha1:d000d30e6ddcfce3a8122c403226a2ce2fd04d9d
+	hash1c sha1:43add6bd0c8c0bc871ac7991e0f5573cfba27804
+	hash4d sha1:1f257a3a90328557c452f0817d6cc50c89d315d4
+	hash3d sha1:05a4927951bcef347f51486575b878b2b60137f2
+
+	hash4a sha256:eef876be1d32ac2e2e42240e0429325cec116e55e88cb2969899fac695aa762f
+	hash3a sha256:cf7cd1bc091d7ba4166a86df864110e42087cd893a5ae96bc50d637e0290939d
+	hash2a sha256:21ddde7ebce2c285213898cb04deca0fd3209610cf7aaf8222e4e2f45262fae2
+	hash1a sha256:f9fe0eda16c6027732ed9d4295689a03abd16f893be69b3dcbf4037ddb191921
+	hash5b sha256:20046f2244577797a9e3d3f790ea9eca4d8a6bafb2a5570bcb0e03aa02ce100b
+	hash4b sha256:f90563d134c61a95bb88afbd45d48ccc9e919c62aa6fbfcd483302b3e4d8dbcb
+	hash3b sha256:988f2aca9f2d87e93e6a73197c2bb99560cc44a2f92d18653968f956f01221e0
+	hash2c sha256:84153b777b4d42827a756c6578dcdb59d8ae5d1360b874fb37c430150c825c26
+	hash1c sha256:9beb2bc4eef72e4c4087be168a20573e34d993d9ab1883055f23e322afa06567
+	hash4d sha256:32de39dc06e679a7abb2d4a55ede7709b3124340a4a90aa305971b1c72ac319d
+	hash3d sha256:fa73b20e41cbb7541c4c81d1535016131dbfbeb05bf6a71f6115e9cad31c7af5
+	EOF
 '
 
 commit_sha1=$(git rev-parse 1st^{commit})
@@ -40,10 +66,10 @@ verify_notes () {
 }
 
 cat <<EOF | sort >expect_notes_x
-5e93d24084d32e1cb61f7070505b9d2530cca987 $commit_sha4
-8366731eeee53787d2bdf8fc1eff7d94757e8da0 $commit_sha3
-eede89064cd42441590d6afec6c37b321ada3389 $commit_sha2
-daa55ffad6cb99bf64226532147ffcaf5ce8bdd1 $commit_sha1
+$(test_oid hash4a) $commit_sha4
+$(test_oid hash3a) $commit_sha3
+$(test_oid hash2a) $commit_sha2
+$(test_oid hash1a) $commit_sha1
 EOF
 
 cat >expect_log_x <<EOF
@@ -126,10 +152,10 @@ test_expect_success 'merge previous notes commit (y^ => y) => No-op' '
 '
 
 cat <<EOF | sort >expect_notes_y
-0f2efbd00262f2fd41dfae33df8765618eeacd99 $commit_sha5
-dec2502dac3ea161543f71930044deff93fa945c $commit_sha4
-4069cdb399fd45463ec6eef8e051a16a03592d91 $commit_sha3
-daa55ffad6cb99bf64226532147ffcaf5ce8bdd1 $commit_sha1
+$(test_oid hash5b) $commit_sha5
+$(test_oid hash4b) $commit_sha4
+$(test_oid hash3b) $commit_sha3
+$(test_oid hash1a) $commit_sha1
 EOF
 
 cat >expect_log_y <<EOF
@@ -193,11 +219,11 @@ test_expect_success 'merge empty notes ref (z => y)' '
 '
 
 cat <<EOF | sort >expect_notes_y
-0f2efbd00262f2fd41dfae33df8765618eeacd99 $commit_sha5
-dec2502dac3ea161543f71930044deff93fa945c $commit_sha4
-4069cdb399fd45463ec6eef8e051a16a03592d91 $commit_sha3
-d000d30e6ddcfce3a8122c403226a2ce2fd04d9d $commit_sha2
-43add6bd0c8c0bc871ac7991e0f5573cfba27804 $commit_sha1
+$(test_oid hash5b) $commit_sha5
+$(test_oid hash4b) $commit_sha4
+$(test_oid hash3b) $commit_sha3
+$(test_oid hash2c) $commit_sha2
+$(test_oid hash1c) $commit_sha1
 EOF
 
 cat >expect_log_y <<EOF
@@ -231,9 +257,9 @@ test_expect_success 'change notes on other notes ref (y)' '
 '
 
 cat <<EOF | sort >expect_notes_x
-0f2efbd00262f2fd41dfae33df8765618eeacd99 $commit_sha5
-1f257a3a90328557c452f0817d6cc50c89d315d4 $commit_sha4
-daa55ffad6cb99bf64226532147ffcaf5ce8bdd1 $commit_sha1
+$(test_oid hash5b) $commit_sha5
+$(test_oid hash4d) $commit_sha4
+$(test_oid hash1a) $commit_sha1
 EOF
 
 cat >expect_log_x <<EOF
@@ -262,10 +288,10 @@ test_expect_success 'change notes on notes ref (x)' '
 '
 
 cat <<EOF | sort >expect_notes_x
-0f2efbd00262f2fd41dfae33df8765618eeacd99 $commit_sha5
-1f257a3a90328557c452f0817d6cc50c89d315d4 $commit_sha4
-d000d30e6ddcfce3a8122c403226a2ce2fd04d9d $commit_sha2
-43add6bd0c8c0bc871ac7991e0f5573cfba27804 $commit_sha1
+$(test_oid hash5b) $commit_sha5
+$(test_oid hash4d) $commit_sha4
+$(test_oid hash2c) $commit_sha2
+$(test_oid hash1c) $commit_sha1
 EOF
 
 cat >expect_log_x <<EOF
@@ -296,8 +322,8 @@ test_expect_success 'merge y into x => Non-conflicting 3-way merge' '
 '
 
 cat <<EOF | sort >expect_notes_w
-05a4927951bcef347f51486575b878b2b60137f2 $commit_sha3
-d000d30e6ddcfce3a8122c403226a2ce2fd04d9d $commit_sha2
+$(test_oid hash3d) $commit_sha3
+$(test_oid hash2c) $commit_sha2
 EOF
 
 cat >expect_log_w <<EOF
@@ -326,11 +352,11 @@ test_expect_success 'create notes on new, separate notes ref (w)' '
 '
 
 cat <<EOF | sort >expect_notes_x
-0f2efbd00262f2fd41dfae33df8765618eeacd99 $commit_sha5
-1f257a3a90328557c452f0817d6cc50c89d315d4 $commit_sha4
-05a4927951bcef347f51486575b878b2b60137f2 $commit_sha3
-d000d30e6ddcfce3a8122c403226a2ce2fd04d9d $commit_sha2
-43add6bd0c8c0bc871ac7991e0f5573cfba27804 $commit_sha1
+$(test_oid hash5b) $commit_sha5
+$(test_oid hash4d) $commit_sha4
+$(test_oid hash3d) $commit_sha3
+$(test_oid hash2c) $commit_sha2
+$(test_oid hash1c) $commit_sha1
 EOF
 
 cat >expect_log_x <<EOF
diff --git a/third_party/git/t/t3309-notes-merge-auto-resolve.sh b/third_party/git/t/t3309-notes-merge-auto-resolve.sh
index 14c2adf970..141d3e4ca4 100755
--- a/third_party/git/t/t3309-notes-merge-auto-resolve.sh
+++ b/third_party/git/t/t3309-notes-merge-auto-resolve.sh
@@ -23,7 +23,67 @@ test_expect_success 'setup commits' '
 	test_commit 12th &&
 	test_commit 13th &&
 	test_commit 14th &&
-	test_commit 15th
+	test_commit 15th &&
+
+	test_oid_cache <<-EOF
+	hash15a sha1:457a85d6c814ea208550f15fcc48f804ac8dc023
+	hash14a sha1:b0c95b954301d69da2bc3723f4cb1680d355937c
+	hash13a sha1:5d30216a129eeffa97d9694ffe8c74317a560315
+	hash12a sha1:dd161bc149470fd890dd4ab52a4cbd79bbd18c36
+	hash11a sha1:7abbc45126d680336fb24294f013a7cdfa3ed545
+	hash10a sha1:b8d03e173f67f6505a76f6e00cf93440200dd9be
+	hash09a sha1:20c613c835011c48a5abe29170a2402ca6354910
+	hash08a sha1:a3daf8a1e4e5dc3409a303ad8481d57bfea7f5d6
+	hash07a sha1:897003322b53bc6ca098e9324ee508362347e734
+	hash06a sha1:11d97fdebfa5ceee540a3da07bce6fa0222bc082
+	hash15b sha1:68b8630d25516028bed862719855b3d6768d7833
+	hash14b sha1:5de7ea7ad4f47e7ff91989fb82234634730f75df
+	hash13b sha1:3a631fdb6f41b05b55d8f4baf20728ba8f6fccbc
+	hash12b sha1:a66055fa82f7a03fe0c02a6aba3287a85abf7c62
+	hash05b sha1:154508c7a0bcad82b6fe4b472bc4c26b3bf0825b
+	hash04b sha1:e2bfd06a37dd2031684a59a6e2b033e212239c78
+	hash03b sha1:5772f42408c0dd6f097a7ca2d24de0e78d1c46b1
+	hash15c sha1:9b4b2c61f0615412da3c10f98ff85b57c04ec765
+	hash11c sha1:7e3c53503a3db8dd996cb62e37c66e070b44b54d
+	hash08c sha1:851e1638784a884c7dd26c5d41f3340f6387413a
+	hash05c sha1:99fc34adfc400b95c67b013115e37e31aa9a6d23
+	hash02c sha1:283b48219aee9a4105f6cab337e789065c82c2b9
+	hash15d sha1:7c4e546efd0fe939f876beb262ece02797880b54
+	hash05d sha1:6c841cc36ea496027290967ca96bd2bef54dbb47
+	hash15e sha1:d682107b8bf7a7aea1e537a8d5cb6a12b60135f1
+	hash05e sha1:357b6ca14c7afd59b7f8b8aaaa6b8b723771135b
+	hash15f sha1:6be90240b5f54594203e25d9f2f64b7567175aee
+	hash05f sha1:660311d7f78dc53db12ac373a43fca7465381a7e
+
+	hash15a sha256:45b1558e5c1b75f570010fa48aaa67bb2289fcd431b34ad81cb4c8b95f4f872a
+	hash14a sha256:6e7af179ea4dd28afdc83ae6912ba0098cdeff764b26a8b750b157dd81749092
+	hash13a sha256:7353089961baf555388e1bac68c67c8ea94b08ccbd97532201cf7f6790703052
+	hash12a sha256:5863e4521689ee1879ceab3b38d39e93ab5b51ec70aaf6a96ad388fbdedfa25e
+	hash11a sha256:82a0ec0338b4ecf8b44304badf4ad38d7469dc41827f38d7ba6c42e3bae3ee98
+	hash10a sha256:e84f2564e92de9792c93b8d197262c735d7ccb1de6025cef8759af8f6c3308eb
+	hash09a sha256:4dd07764bcec696f195c0ea71ae89e174876403af1637e4642b8f4453fd23028
+	hash08a sha256:02132c4546cd88a1d0aa5854dd55da120927f7904ba16afe36fe03e91a622067
+	hash07a sha256:369baf7d00c6720efdc10273493555f943051f84a4706fb24caeb353fa4789db
+	hash06a sha256:52d32c10353583b2d96a5849b1f1f43c8018e76f3e8ef1b0d46eb5cff7cdefaf
+	hash15b sha256:345e6660b345fa174738a31a7a59423c394bdf414804e200bc510c65d971ae96
+	hash14b sha256:7653a6596021c52e405cba979eea15a729993e7102b9a61ba4667e34f0ead4a1
+	hash13b sha256:0f202a0b6b9690de2349c173dfd766a37e82744f61c14f1c389306f1d69f470b
+	hash12b sha256:eb00f219c026136ea6535b16ff8ec3efa510e6bf50098ca041e1a2a1d4b79840
+	hash05b sha256:993b2290cd0c24c27c849d99f1904f3b590f77af0f539932734ad05679ac5a2f
+	hash04b sha256:c7fba0d6104917fbf35258f40b9fa4fc697cfa992deecd1570a3b08d0a5587a9
+	hash03b sha256:7287a2d78a3766c181b08df38951d784b08b72a44f571ed6d855bd0be22c70f6
+	hash15c sha256:62316660a22bf97857dc4a16709ec4d93a224e8c9f37d661ef91751e1f4c4166
+	hash11c sha256:51c3763de9b08309370adc5036d58debb331980e73097902957c444602551daa
+	hash08c sha256:22cf1fa29599898a7218c51135d66ed85d22aad584f77db3305dedce4c3d4798
+	hash05c sha256:2508fd86db980f0508893a1c1571bdf3b2ee113dc25ddb1a3a2fb94bd6cd0d58
+	hash02c sha256:63bb527e0b4e1c8e1dd0d54dd778ca7c3718689fd6e37c473044cfbcf1cacfdb
+	hash15d sha256:667acb4e2d5f8df15e5aea4506dfd16d25bc7feca70fdb0d965a7222f983bb88
+	hash05d sha256:09e6b5a6fe666c4a027674b6611a254b7d2528cd211c6b5288d1b4db6c741dfa
+	hash15e sha256:e8cbf52f6fcadc6de3c7761e64a89e9fe38d19a03d3e28ef6ca8596d93fc4f3a
+	hash05e sha256:cdb1e19f7ba1539f95af51a57edeb88a7ecc97d3c2f52da8c4c86af308595607
+	hash15f sha256:29c14cb92da448a923963b8a43994268b19c2e57913de73f3667421fd2c0eeec
+	hash05f sha256:14a6e641b2c0a9f398ebac6b4d34afa5efea4c52d2631382f45f8f662266903b
+	EOF
 '
 
 commit_sha1=$(git rev-parse 1st^{commit})
@@ -68,16 +128,16 @@ test_expect_success 'setup merge base (x)' '
 '
 
 cat <<EOF | sort >expect_notes_x
-457a85d6c814ea208550f15fcc48f804ac8dc023 $commit_sha15
-b0c95b954301d69da2bc3723f4cb1680d355937c $commit_sha14
-5d30216a129eeffa97d9694ffe8c74317a560315 $commit_sha13
-dd161bc149470fd890dd4ab52a4cbd79bbd18c36 $commit_sha12
-7abbc45126d680336fb24294f013a7cdfa3ed545 $commit_sha11
-b8d03e173f67f6505a76f6e00cf93440200dd9be $commit_sha10
-20c613c835011c48a5abe29170a2402ca6354910 $commit_sha9
-a3daf8a1e4e5dc3409a303ad8481d57bfea7f5d6 $commit_sha8
-897003322b53bc6ca098e9324ee508362347e734 $commit_sha7
-11d97fdebfa5ceee540a3da07bce6fa0222bc082 $commit_sha6
+$(test_oid hash15a) $commit_sha15
+$(test_oid hash14a) $commit_sha14
+$(test_oid hash13a) $commit_sha13
+$(test_oid hash12a) $commit_sha12
+$(test_oid hash11a) $commit_sha11
+$(test_oid hash10a) $commit_sha10
+$(test_oid hash09a) $commit_sha9
+$(test_oid hash08a) $commit_sha8
+$(test_oid hash07a) $commit_sha7
+$(test_oid hash06a) $commit_sha6
 EOF
 
 cat >expect_log_x <<EOF
@@ -141,16 +201,16 @@ test_expect_success 'setup local branch (y)' '
 '
 
 cat <<EOF | sort >expect_notes_y
-68b8630d25516028bed862719855b3d6768d7833 $commit_sha15
-5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14
-3a631fdb6f41b05b55d8f4baf20728ba8f6fccbc $commit_sha13
-a66055fa82f7a03fe0c02a6aba3287a85abf7c62 $commit_sha12
-7abbc45126d680336fb24294f013a7cdfa3ed545 $commit_sha11
-b8d03e173f67f6505a76f6e00cf93440200dd9be $commit_sha10
-20c613c835011c48a5abe29170a2402ca6354910 $commit_sha9
-154508c7a0bcad82b6fe4b472bc4c26b3bf0825b $commit_sha5
-e2bfd06a37dd2031684a59a6e2b033e212239c78 $commit_sha4
-5772f42408c0dd6f097a7ca2d24de0e78d1c46b1 $commit_sha3
+$(test_oid hash15b) $commit_sha15
+$(test_oid hash14b) $commit_sha14
+$(test_oid hash13b) $commit_sha13
+$(test_oid hash12b) $commit_sha12
+$(test_oid hash11a) $commit_sha11
+$(test_oid hash10a) $commit_sha10
+$(test_oid hash09a) $commit_sha9
+$(test_oid hash05b) $commit_sha5
+$(test_oid hash04b) $commit_sha4
+$(test_oid hash03b) $commit_sha3
 EOF
 
 cat >expect_log_y <<EOF
@@ -214,16 +274,16 @@ test_expect_success 'setup remote branch (z)' '
 '
 
 cat <<EOF | sort >expect_notes_z
-9b4b2c61f0615412da3c10f98ff85b57c04ec765 $commit_sha15
-5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14
-5d30216a129eeffa97d9694ffe8c74317a560315 $commit_sha13
-7e3c53503a3db8dd996cb62e37c66e070b44b54d $commit_sha11
-b8d03e173f67f6505a76f6e00cf93440200dd9be $commit_sha10
-851e1638784a884c7dd26c5d41f3340f6387413a $commit_sha8
-897003322b53bc6ca098e9324ee508362347e734 $commit_sha7
-99fc34adfc400b95c67b013115e37e31aa9a6d23 $commit_sha5
-e2bfd06a37dd2031684a59a6e2b033e212239c78 $commit_sha4
-283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2
+$(test_oid hash15c) $commit_sha15
+$(test_oid hash14b) $commit_sha14
+$(test_oid hash13a) $commit_sha13
+$(test_oid hash11c) $commit_sha11
+$(test_oid hash10a) $commit_sha10
+$(test_oid hash08c) $commit_sha8
+$(test_oid hash07a) $commit_sha7
+$(test_oid hash05c) $commit_sha5
+$(test_oid hash04b) $commit_sha4
+$(test_oid hash02c) $commit_sha2
 EOF
 
 cat >expect_log_z <<EOF
@@ -306,16 +366,16 @@ test_expect_success 'merge z into y with invalid configuration option => Fail/No
 '
 
 cat <<EOF | sort >expect_notes_ours
-68b8630d25516028bed862719855b3d6768d7833 $commit_sha15
-5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14
-3a631fdb6f41b05b55d8f4baf20728ba8f6fccbc $commit_sha13
-a66055fa82f7a03fe0c02a6aba3287a85abf7c62 $commit_sha12
-7e3c53503a3db8dd996cb62e37c66e070b44b54d $commit_sha11
-b8d03e173f67f6505a76f6e00cf93440200dd9be $commit_sha10
-154508c7a0bcad82b6fe4b472bc4c26b3bf0825b $commit_sha5
-e2bfd06a37dd2031684a59a6e2b033e212239c78 $commit_sha4
-5772f42408c0dd6f097a7ca2d24de0e78d1c46b1 $commit_sha3
-283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2
+$(test_oid hash15b) $commit_sha15
+$(test_oid hash14b) $commit_sha14
+$(test_oid hash13b) $commit_sha13
+$(test_oid hash12b) $commit_sha12
+$(test_oid hash11c) $commit_sha11
+$(test_oid hash10a) $commit_sha10
+$(test_oid hash05b) $commit_sha5
+$(test_oid hash04b) $commit_sha4
+$(test_oid hash03b) $commit_sha3
+$(test_oid hash02c) $commit_sha2
 EOF
 
 cat >expect_log_ours <<EOF
@@ -395,16 +455,16 @@ test_expect_success 'reset to pre-merge state (y)' '
 '
 
 cat <<EOF | sort >expect_notes_theirs
-9b4b2c61f0615412da3c10f98ff85b57c04ec765 $commit_sha15
-5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14
-3a631fdb6f41b05b55d8f4baf20728ba8f6fccbc $commit_sha13
-7e3c53503a3db8dd996cb62e37c66e070b44b54d $commit_sha11
-b8d03e173f67f6505a76f6e00cf93440200dd9be $commit_sha10
-851e1638784a884c7dd26c5d41f3340f6387413a $commit_sha8
-99fc34adfc400b95c67b013115e37e31aa9a6d23 $commit_sha5
-e2bfd06a37dd2031684a59a6e2b033e212239c78 $commit_sha4
-5772f42408c0dd6f097a7ca2d24de0e78d1c46b1 $commit_sha3
-283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2
+$(test_oid hash15c) $commit_sha15
+$(test_oid hash14b) $commit_sha14
+$(test_oid hash13b) $commit_sha13
+$(test_oid hash11c) $commit_sha11
+$(test_oid hash10a) $commit_sha10
+$(test_oid hash08c) $commit_sha8
+$(test_oid hash05c) $commit_sha5
+$(test_oid hash04b) $commit_sha4
+$(test_oid hash03b) $commit_sha3
+$(test_oid hash02c) $commit_sha2
 EOF
 
 cat >expect_log_theirs <<EOF
@@ -473,17 +533,17 @@ test_expect_success 'reset to pre-merge state (y)' '
 '
 
 cat <<EOF | sort >expect_notes_union
-7c4e546efd0fe939f876beb262ece02797880b54 $commit_sha15
-5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14
-3a631fdb6f41b05b55d8f4baf20728ba8f6fccbc $commit_sha13
-a66055fa82f7a03fe0c02a6aba3287a85abf7c62 $commit_sha12
-7e3c53503a3db8dd996cb62e37c66e070b44b54d $commit_sha11
-b8d03e173f67f6505a76f6e00cf93440200dd9be $commit_sha10
-851e1638784a884c7dd26c5d41f3340f6387413a $commit_sha8
-6c841cc36ea496027290967ca96bd2bef54dbb47 $commit_sha5
-e2bfd06a37dd2031684a59a6e2b033e212239c78 $commit_sha4
-5772f42408c0dd6f097a7ca2d24de0e78d1c46b1 $commit_sha3
-283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2
+$(test_oid hash15d) $commit_sha15
+$(test_oid hash14b) $commit_sha14
+$(test_oid hash13b) $commit_sha13
+$(test_oid hash12b) $commit_sha12
+$(test_oid hash11c) $commit_sha11
+$(test_oid hash10a) $commit_sha10
+$(test_oid hash08c) $commit_sha8
+$(test_oid hash05d) $commit_sha5
+$(test_oid hash04b) $commit_sha4
+$(test_oid hash03b) $commit_sha3
+$(test_oid hash02c) $commit_sha2
 EOF
 
 cat >expect_log_union <<EOF
@@ -574,17 +634,17 @@ test_expect_success 'merge z into y with "manual" per-ref only checks specific r
 '
 
 cat <<EOF | sort >expect_notes_union2
-d682107b8bf7a7aea1e537a8d5cb6a12b60135f1 $commit_sha15
-5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14
-3a631fdb6f41b05b55d8f4baf20728ba8f6fccbc $commit_sha13
-a66055fa82f7a03fe0c02a6aba3287a85abf7c62 $commit_sha12
-7e3c53503a3db8dd996cb62e37c66e070b44b54d $commit_sha11
-b8d03e173f67f6505a76f6e00cf93440200dd9be $commit_sha10
-851e1638784a884c7dd26c5d41f3340f6387413a $commit_sha8
-357b6ca14c7afd59b7f8b8aaaa6b8b723771135b $commit_sha5
-e2bfd06a37dd2031684a59a6e2b033e212239c78 $commit_sha4
-5772f42408c0dd6f097a7ca2d24de0e78d1c46b1 $commit_sha3
-283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2
+$(test_oid hash15e) $commit_sha15
+$(test_oid hash14b) $commit_sha14
+$(test_oid hash13b) $commit_sha13
+$(test_oid hash12b) $commit_sha12
+$(test_oid hash11c) $commit_sha11
+$(test_oid hash10a) $commit_sha10
+$(test_oid hash08c) $commit_sha8
+$(test_oid hash05e) $commit_sha5
+$(test_oid hash04b) $commit_sha4
+$(test_oid hash03b) $commit_sha3
+$(test_oid hash02c) $commit_sha2
 EOF
 
 cat >expect_log_union2 <<EOF
@@ -648,17 +708,17 @@ test_expect_success 'reset to pre-merge state (z)' '
 '
 
 cat <<EOF | sort >expect_notes_cat_sort_uniq
-6be90240b5f54594203e25d9f2f64b7567175aee $commit_sha15
-5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14
-3a631fdb6f41b05b55d8f4baf20728ba8f6fccbc $commit_sha13
-a66055fa82f7a03fe0c02a6aba3287a85abf7c62 $commit_sha12
-7e3c53503a3db8dd996cb62e37c66e070b44b54d $commit_sha11
-b8d03e173f67f6505a76f6e00cf93440200dd9be $commit_sha10
-851e1638784a884c7dd26c5d41f3340f6387413a $commit_sha8
-660311d7f78dc53db12ac373a43fca7465381a7e $commit_sha5
-e2bfd06a37dd2031684a59a6e2b033e212239c78 $commit_sha4
-5772f42408c0dd6f097a7ca2d24de0e78d1c46b1 $commit_sha3
-283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2
+$(test_oid hash15f) $commit_sha15
+$(test_oid hash14b) $commit_sha14
+$(test_oid hash13b) $commit_sha13
+$(test_oid hash12b) $commit_sha12
+$(test_oid hash11c) $commit_sha11
+$(test_oid hash10a) $commit_sha10
+$(test_oid hash08c) $commit_sha8
+$(test_oid hash05f) $commit_sha5
+$(test_oid hash04b) $commit_sha4
+$(test_oid hash03b) $commit_sha3
+$(test_oid hash02c) $commit_sha2
 EOF
 
 cat >expect_log_cat_sort_uniq <<EOF
diff --git a/third_party/git/t/t3310-notes-merge-manual-resolve.sh b/third_party/git/t/t3310-notes-merge-manual-resolve.sh
index 2dea846e25..d3d72e25fe 100755
--- a/third_party/git/t/t3310-notes-merge-manual-resolve.sh
+++ b/third_party/git/t/t3310-notes-merge-manual-resolve.sh
@@ -13,7 +13,39 @@ test_expect_success 'setup commits' '
 	test_commit 2nd &&
 	test_commit 3rd &&
 	test_commit 4th &&
-	test_commit 5th
+	test_commit 5th &&
+
+	test_oid_cache <<-EOF
+	hash04a sha1:6e8e3febca3c2bb896704335cc4d0c34cb2f8715
+	hash03a sha1:e5388c10860456ee60673025345fe2e153eb8cf8
+	hash02a sha1:ceefa674873670e7ecd131814d909723cce2b669
+	hash04b sha1:e2bfd06a37dd2031684a59a6e2b033e212239c78
+	hash03b sha1:5772f42408c0dd6f097a7ca2d24de0e78d1c46b1
+	hash01b sha1:b0a6021ec006d07e80e9b20ec9b444cbd9d560d3
+	hash04c sha1:cff59c793c20bb49a4e01bc06fb06bad642e0d54
+	hash02c sha1:283b48219aee9a4105f6cab337e789065c82c2b9
+	hash01c sha1:0a81da8956346e19bcb27a906f04af327e03e31b
+	hash04d sha1:00494adecf2d9635a02fa431308d67993f853968
+	hash01e sha1:f75d1df88cbfe4258d49852f26cfc83f2ad4494b
+	hash04f sha1:021faa20e931fb48986ffc6282b4bb05553ac946
+	hash01f sha1:0a59e787e6d688aa6309e56e8c1b89431a0fc1c1
+	hash05g sha1:304dfb4325cf243025b9957486eb605a9b51c199
+
+	hash04a	sha256:f18a935e65866345098b3b754071dbf9f3aa3520eb27a7b036b278c5e2f1ed7e
+	hash03a	sha256:713035dc94067a64e5fa6e4e1821b7c3bde49a77c7cb3f80eaadefa1ca41b3d2
+	hash02a	sha256:f160a67e048b6fa75bec3952184154045076692cf5dccd3da21e3fd34b7a3f0f
+	hash04b sha256:c7fba0d6104917fbf35258f40b9fa4fc697cfa992deecd1570a3b08d0a5587a9
+	hash03b sha256:7287a2d78a3766c181b08df38951d784b08b72a44f571ed6d855bd0be22c70f6
+	hash01b sha256:da96cf778c15d0a2bb76f98b2a62f6c9c01730fa7030e8f08ef0191048e7d620
+	hash04c sha256:cb615d2def4b834d5f55b2351df97dc92bee4f5009d285201427f349081c8aca
+	hash02c sha256:63bb527e0b4e1c8e1dd0d54dd778ca7c3718689fd6e37c473044cfbcf1cacfdb
+	hash01c sha256:5b87237ac1fbae0246256fed9f9a1f077c4140fb7e6444925f8dbfa5ae406cd8
+	hash04d sha256:eeddc9f9f6cb3d6b39b861659853f10891dc373e0b6eecb09e03e39b6ce64714
+	hash01e sha256:108f521b1a74c2e6d0b52a4eda87e09162bf847f7d190cfce496ee1af0b29a5a
+	hash04f sha256:901acda0454502b3bbd281f130c419e6c8de78afcf72a8def8d45ad31462bce4
+	hash01f sha256:a2d99d1b8bf23c8af7d9d91368454adc110dfd5cc068a4cebb486ee8f5a1e16c
+	hash05g sha256:4fef015b01da8efe929a68e3bb9b8fbad81f53995f097befe8ebc93f12ab98ec
+	EOF
 '
 
 commit_sha1=$(git rev-parse 1st^{commit})
@@ -32,10 +64,16 @@ verify_notes () {
 	test_cmp "expect_log_$notes_ref" "output_log_$notes_ref"
 }
 
+notes_merge_files_gone () {
+	# No .git/NOTES_MERGE_* files left
+	{ ls .git/NOTES_MERGE_* >output || :; } &&
+	test_must_be_empty output
+}
+
 cat <<EOF | sort >expect_notes_x
-6e8e3febca3c2bb896704335cc4d0c34cb2f8715 $commit_sha4
-e5388c10860456ee60673025345fe2e153eb8cf8 $commit_sha3
-ceefa674873670e7ecd131814d909723cce2b669 $commit_sha2
+$(test_oid hash04a) $commit_sha4
+$(test_oid hash03a) $commit_sha3
+$(test_oid hash02a) $commit_sha2
 EOF
 
 cat >expect_log_x <<EOF
@@ -63,9 +101,9 @@ test_expect_success 'setup merge base (x)' '
 '
 
 cat <<EOF | sort >expect_notes_y
-e2bfd06a37dd2031684a59a6e2b033e212239c78 $commit_sha4
-5772f42408c0dd6f097a7ca2d24de0e78d1c46b1 $commit_sha3
-b0a6021ec006d07e80e9b20ec9b444cbd9d560d3 $commit_sha1
+$(test_oid hash04b) $commit_sha4
+$(test_oid hash03b) $commit_sha3
+$(test_oid hash01b) $commit_sha1
 EOF
 
 cat >expect_log_y <<EOF
@@ -95,9 +133,9 @@ test_expect_success 'setup local branch (y)' '
 '
 
 cat <<EOF | sort >expect_notes_z
-cff59c793c20bb49a4e01bc06fb06bad642e0d54 $commit_sha4
-283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2
-0a81da8956346e19bcb27a906f04af327e03e31b $commit_sha1
+$(test_oid hash04c) $commit_sha4
+$(test_oid hash02c) $commit_sha2
+$(test_oid hash01c) $commit_sha1
 EOF
 
 cat >expect_log_z <<EOF
@@ -193,9 +231,9 @@ test_expect_success 'merge z into m (== y) with default ("manual") resolver => C
 '
 
 cat <<EOF | sort >expect_notes_z
-00494adecf2d9635a02fa431308d67993f853968 $commit_sha4
-283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2
-0a81da8956346e19bcb27a906f04af327e03e31b $commit_sha1
+$(test_oid hash04d) $commit_sha4
+$(test_oid hash02c) $commit_sha2
+$(test_oid hash01c) $commit_sha1
 EOF
 
 cat >expect_log_z <<EOF
@@ -231,8 +269,8 @@ test_expect_success 'cannot do merge w/conflicts when previous merge is unfinish
 # Setup non-conflicting merge between x and new notes ref w
 
 cat <<EOF | sort >expect_notes_w
-ceefa674873670e7ecd131814d909723cce2b669 $commit_sha2
-f75d1df88cbfe4258d49852f26cfc83f2ad4494b $commit_sha1
+$(test_oid hash02a) $commit_sha2
+$(test_oid hash01e) $commit_sha1
 EOF
 
 cat >expect_log_w <<EOF
@@ -258,10 +296,10 @@ test_expect_success 'setup unrelated notes ref (w)' '
 '
 
 cat <<EOF | sort >expect_notes_w
-6e8e3febca3c2bb896704335cc4d0c34cb2f8715 $commit_sha4
-e5388c10860456ee60673025345fe2e153eb8cf8 $commit_sha3
-ceefa674873670e7ecd131814d909723cce2b669 $commit_sha2
-f75d1df88cbfe4258d49852f26cfc83f2ad4494b $commit_sha1
+$(test_oid hash04a) $commit_sha4
+$(test_oid hash03a) $commit_sha3
+$(test_oid hash02a) $commit_sha2
+$(test_oid hash01e) $commit_sha1
 EOF
 
 cat >expect_log_w <<EOF
@@ -291,10 +329,10 @@ test_expect_success 'can do merge without conflicts even if previous merge is un
 '
 
 cat <<EOF | sort >expect_notes_m
-021faa20e931fb48986ffc6282b4bb05553ac946 $commit_sha4
-5772f42408c0dd6f097a7ca2d24de0e78d1c46b1 $commit_sha3
-283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2
-0a59e787e6d688aa6309e56e8c1b89431a0fc1c1 $commit_sha1
+$(test_oid hash04f) $commit_sha4
+$(test_oid hash03b) $commit_sha3
+$(test_oid hash02c) $commit_sha2
+$(test_oid hash01f) $commit_sha1
 EOF
 
 cat >expect_log_m <<EOF
@@ -335,9 +373,7 @@ EOF
 y and z notes on 4th commit
 EOF
 	git notes merge --commit &&
-	# No .git/NOTES_MERGE_* files left
-	test_might_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&
-	test_must_be_empty output &&
+	notes_merge_files_gone &&
 	# Merge commit has pre-merge y and pre-merge z as parents
 	test "$(git rev-parse refs/notes/m^1)" = "$(cat pre_merge_y)" &&
 	test "$(git rev-parse refs/notes/m^2)" = "$(cat pre_merge_z)" &&
@@ -397,9 +433,7 @@ test_expect_success 'redo merge of z into m (== y) with default ("manual") resol
 
 test_expect_success 'abort notes merge' '
 	git notes merge --abort &&
-	# No .git/NOTES_MERGE_* files left
-	test_might_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&
-	test_must_be_empty output &&
+	notes_merge_files_gone &&
 	# m has not moved (still == y)
 	test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)" &&
 	# Verify that other notes refs has not changed (w, x, y and z)
@@ -430,9 +464,9 @@ test_expect_success 'redo merge of z into m (== y) with default ("manual") resol
 '
 
 cat <<EOF | sort >expect_notes_m
-304dfb4325cf243025b9957486eb605a9b51c199 $commit_sha5
-283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2
-0a59e787e6d688aa6309e56e8c1b89431a0fc1c1 $commit_sha1
+$(test_oid hash05g) $commit_sha5
+$(test_oid hash02c) $commit_sha2
+$(test_oid hash01f) $commit_sha1
 EOF
 
 cat >expect_log_m <<EOF
@@ -464,9 +498,7 @@ EOF
 	echo "new note on 5th commit" > .git/NOTES_MERGE_WORKTREE/$commit_sha5 &&
 	# Finalize merge
 	git notes merge --commit &&
-	# No .git/NOTES_MERGE_* files left
-	test_might_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&
-	test_must_be_empty output &&
+	notes_merge_files_gone &&
 	# Merge commit has pre-merge y and pre-merge z as parents
 	test "$(git rev-parse refs/notes/m^1)" = "$(cat pre_merge_y)" &&
 	test "$(git rev-parse refs/notes/m^2)" = "$(cat pre_merge_z)" &&
@@ -553,9 +585,7 @@ EOF
 
 test_expect_success 'resolve situation by aborting the notes merge' '
 	git notes merge --abort &&
-	# No .git/NOTES_MERGE_* files left
-	test_might_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&
-	test_must_be_empty output &&
+	notes_merge_files_gone &&
 	# m has not moved (still == w)
 	test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)" &&
 	# Verify that other notes refs has not changed (w, x, y and z)
diff --git a/third_party/git/t/t3311-notes-merge-fanout.sh b/third_party/git/t/t3311-notes-merge-fanout.sh
index 37151a3adc..5b675417e9 100755
--- a/third_party/git/t/t3311-notes-merge-fanout.sh
+++ b/third_party/git/t/t3311-notes-merge-fanout.sh
@@ -29,15 +29,10 @@ verify_fanout () {
 	git ls-tree -r --name-only "refs/notes/$notes_ref" |
 	while read path
 	do
-		case "$path" in
-		??/??????????????????????????????????????)
-			: true
-			;;
-		*)
+		echo "$path" | grep "^../[0-9a-f]*$" || {
 			echo "Invalid path \"$path\"" &&
-			return 1
-			;;
-		esac
+			return 1;
+		}
 	done
 }
 
@@ -48,15 +43,10 @@ verify_no_fanout () {
 	git ls-tree -r --name-only "refs/notes/$notes_ref" |
 	while read path
 	do
-		case "$path" in
-		????????????????????????????????????????)
-			: true
-			;;
-		*)
+		echo "$path" | grep -v "^../.*" || {
 			echo "Invalid path \"$path\"" &&
-			return 1
-			;;
-		esac
+			return 1;
+		}
 	done
 }
 
@@ -67,7 +57,27 @@ test_expect_success 'setup a few initial commits with notes (notes ref: x)' '
 	do
 		test_commit "commit$i" >/dev/null &&
 		git notes add -m "notes for commit$i" || return 1
-	done
+	done &&
+
+	git log --format=oneline &&
+
+	test_oid_cache <<-EOF
+	hash05a sha1:aed91155c7a72c2188e781fdf40e0f3761b299db
+	hash04a sha1:99fab268f9d7ee7b011e091a436c78def8eeee69
+	hash03a sha1:953c20ae26c7aa0b428c20693fe38bc687f9d1a9
+	hash02a sha1:6358796131b8916eaa2dde6902642942a1cb37e1
+	hash01a sha1:b02d459c32f0e68f2fe0981033bb34f38776ba47
+	hash03b sha1:9f506ee70e20379d7f78204c77b334f43d77410d
+	hash02b sha1:23a47d6ea7d589895faf800752054818e1e7627b
+
+	hash05a sha256:3aae5d26619d96dba93795f66325716e4cbc486884f95a6adee8fb0615a76d12
+	hash04a sha256:07e43dd3d89fe634d3252e253b426aacc7285a995dcdbcf94ac284060a1122cf
+	hash03a sha256:26fb52eaa7f4866bf735254587be7b31209ec10e525912ffd8e8ba549ba892ff
+	hash02a sha256:b57ebdf23634e750dcbc4b9a37991d70f90830d568a0e4529ce9de0a3f8d605c
+	hash01a sha256:377903b1572bd5117087a5518fcb1011b5053cccbc59e3c7c823a8615204173b
+	hash03b sha256:04e7b392fda7c185bfa17c9179b56db732edc2dc2b3bf887308dcaabb717270d
+	hash02b sha256:66099aaaec49a485ed990acadd9a9b81232ea592079964113d8f581ff69ef50b
+	EOF
 '
 
 commit_sha1=$(git rev-parse commit1^{commit})
@@ -77,11 +87,11 @@ commit_sha4=$(git rev-parse commit4^{commit})
 commit_sha5=$(git rev-parse commit5^{commit})
 
 cat <<EOF | sort >expect_notes_x
-aed91155c7a72c2188e781fdf40e0f3761b299db $commit_sha5
-99fab268f9d7ee7b011e091a436c78def8eeee69 $commit_sha4
-953c20ae26c7aa0b428c20693fe38bc687f9d1a9 $commit_sha3
-6358796131b8916eaa2dde6902642942a1cb37e1 $commit_sha2
-b02d459c32f0e68f2fe0981033bb34f38776ba47 $commit_sha1
+$(test_oid hash05a) $commit_sha5
+$(test_oid hash04a) $commit_sha4
+$(test_oid hash03a) $commit_sha3
+$(test_oid hash02a) $commit_sha2
+$(test_oid hash01a) $commit_sha1
 EOF
 
 cat >expect_log_x <<EOF
@@ -145,9 +155,9 @@ test_expect_success 'Fast-forward merge (y => x)' '
 '
 
 cat <<EOF | sort >expect_notes_z
-9f506ee70e20379d7f78204c77b334f43d77410d $commit_sha3
-23a47d6ea7d589895faf800752054818e1e7627b $commit_sha2
-b02d459c32f0e68f2fe0981033bb34f38776ba47 $commit_sha1
+$(test_oid hash03b) $commit_sha3
+$(test_oid hash02b) $commit_sha2
+$(test_oid hash01a) $commit_sha1
 EOF
 
 cat >expect_log_z <<EOF
diff --git a/third_party/git/t/t3400-rebase.sh b/third_party/git/t/t3400-rebase.sh
index 80b23fd326..40d2975995 100755
--- a/third_party/git/t/t3400-rebase.sh
+++ b/third_party/git/t/t3400-rebase.sh
@@ -64,7 +64,7 @@ test_expect_success 'rebase sets ORIG_HEAD to pre-rebase state' '
 	pre="$(git rev-parse --verify HEAD)" &&
 	git rebase master &&
 	test_cmp_rev "$pre" ORIG_HEAD &&
-	! test_cmp_rev "$pre" HEAD
+	test_cmp_rev ! "$pre" HEAD
 '
 
 test_expect_success 'rebase, with <onto> and <upstream> specified as :/quuxery' '
@@ -143,11 +143,11 @@ test_expect_success 'setup: recover' '
 
 test_expect_success 'Show verbose error when HEAD could not be detached' '
 	>B &&
+	test_when_finished "rm -f B" &&
 	test_must_fail git rebase topic 2>output.err >output.out &&
 	test_i18ngrep "The following untracked working tree files would be overwritten by checkout:" output.err &&
 	test_i18ngrep B output.err
 '
-rm -f B
 
 test_expect_success 'fail when upstream arg is missing and not on branch' '
 	git checkout topic &&
@@ -159,19 +159,43 @@ test_expect_success 'fail when upstream arg is missing and not configured' '
 	test_must_fail git rebase
 '
 
-test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg' '
+test_expect_success 'rebase works with format.useAutoBase' '
+	test_config format.useAutoBase true &&
+	git checkout topic &&
+	git rebase master
+'
+
+test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg (--merge)' '
 	git checkout -b default-base master &&
 	git checkout -b default topic &&
 	git config branch.default.remote . &&
 	git config branch.default.merge refs/heads/default-base &&
-	git rebase &&
+	git rebase --merge &&
 	git rev-parse --verify default-base >expect &&
 	git rev-parse default~1 >actual &&
 	test_cmp expect actual &&
 	git checkout default-base &&
 	git reset --hard HEAD^ &&
 	git checkout default &&
-	git rebase &&
+	git rebase --merge &&
+	git rev-parse --verify default-base >expect &&
+	git rev-parse default~1 >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg (--apply)' '
+	git checkout -B default-base master &&
+	git checkout -B default topic &&
+	git config branch.default.remote . &&
+	git config branch.default.merge refs/heads/default-base &&
+	git rebase --apply &&
+	git rev-parse --verify default-base >expect &&
+	git rev-parse default~1 >actual &&
+	test_cmp expect actual &&
+	git checkout default-base &&
+	git reset --hard HEAD^ &&
+	git checkout default &&
+	git rebase --apply &&
 	git rev-parse --verify default-base >expect &&
 	git rev-parse default~1 >actual &&
 	test_cmp expect actual
@@ -200,9 +224,15 @@ test_expect_success 'cherry-picked commits and fork-point work together' '
 	test_cmp expect D
 '
 
-test_expect_success 'rebase -q is quiet' '
+test_expect_success 'rebase --apply -q is quiet' '
 	git checkout -b quiet topic &&
-	git rebase -q master >output.out 2>&1 &&
+	git rebase --apply -q master >output.out 2>&1 &&
+	test_must_be_empty output.out
+'
+
+test_expect_success 'rebase --merge -q is quiet' '
+	git checkout -B quiet topic &&
+	git rebase --merge -q master >output.out 2>&1 &&
 	test_must_be_empty output.out
 '
 
@@ -285,7 +315,7 @@ EOF
 	test_cmp From_.msg out
 '
 
-test_expect_success 'rebase --am and --show-current-patch' '
+test_expect_success 'rebase --apply and --show-current-patch' '
 	test_create_repo conflict-apply &&
 	(
 		cd conflict-apply &&
@@ -295,12 +325,48 @@ test_expect_success 'rebase --am and --show-current-patch' '
 		echo two >>init.t &&
 		git commit -a -m two &&
 		git tag two &&
-		test_must_fail git rebase --onto init HEAD^ &&
+		test_must_fail git rebase --apply -f --onto init HEAD^ &&
 		GIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr &&
 		grep "show.*$(git rev-parse two)" stderr
 	)
 '
 
+test_expect_success 'rebase --apply and .gitattributes' '
+	test_create_repo attributes &&
+	(
+		cd attributes &&
+		test_commit init &&
+		git config filter.test.clean "sed -e '\''s/smudged/clean/g'\''" &&
+		git config filter.test.smudge "sed -e '\''s/clean/smudged/g'\''" &&
+
+		test_commit second &&
+		git checkout -b test HEAD^ &&
+
+		echo "*.txt filter=test" >.gitattributes &&
+		git add .gitattributes &&
+		test_commit third &&
+
+		echo "This text is smudged." >a.txt &&
+		git add a.txt &&
+		test_commit fourth &&
+
+		git checkout -b removal HEAD^ &&
+		git rm .gitattributes &&
+		git add -u &&
+		test_commit fifth &&
+		git cherry-pick test &&
+
+		git checkout test &&
+		git rebase master &&
+		grep "smudged" a.txt &&
+
+		git checkout removal &&
+		git reset --hard &&
+		git rebase master &&
+		grep "clean" a.txt
+	)
+'
+
 test_expect_success 'rebase--merge.sh and --show-current-patch' '
 	test_create_repo conflict-merge &&
 	(
@@ -335,4 +401,22 @@ test_expect_success 'rebase -c rebase.useBuiltin=false warning' '
 	test_must_be_empty err
 '
 
+test_expect_success 'switch to branch checked out here' '
+	git checkout master &&
+	git rebase master master
+'
+
+test_expect_success 'switch to branch not checked out' '
+	git checkout master &&
+	git branch other &&
+	git rebase master other
+'
+
+test_expect_success 'refuse to switch to branch checked out elsewhere' '
+	git checkout master &&
+	git worktree add wt &&
+	test_must_fail git -C wt rebase master master 2>err &&
+	test_i18ngrep "already checked out" err
+'
+
 test_done
diff --git a/third_party/git/t/t3401-rebase-and-am-rename.sh b/third_party/git/t/t3401-rebase-and-am-rename.sh
index a0b9438b22..f18bae9450 100755
--- a/third_party/git/t/t3401-rebase-and-am-rename.sh
+++ b/third_party/git/t/t3401-rebase-and-am-rename.sh
@@ -52,13 +52,13 @@ test_expect_success 'rebase --interactive: directory rename detected' '
 	)
 '
 
-test_expect_failure 'rebase (am): directory rename detected' '
+test_expect_failure 'rebase --apply: directory rename detected' '
 	(
 		cd dir-rename &&
 
 		git checkout B^0 &&
 
-		git -c merge.directoryRenames=true rebase A &&
+		git -c merge.directoryRenames=true rebase --apply A &&
 
 		git ls-files -s >out &&
 		test_line_count = 5 out &&
diff --git a/third_party/git/t/t3402-rebase-merge.sh b/third_party/git/t/t3402-rebase-merge.sh
index a1ec501a87..6e032716a6 100755
--- a/third_party/git/t/t3402-rebase-merge.sh
+++ b/third_party/git/t/t3402-rebase-merge.sh
@@ -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
diff --git a/third_party/git/t/t3403-rebase-skip.sh b/third_party/git/t/t3403-rebase-skip.sh
index 1f5122b632..a927774910 100755
--- a/third_party/git/t/t3403-rebase-skip.sh
+++ b/third_party/git/t/t3403-rebase-skip.sh
@@ -7,6 +7,8 @@ test_description='git rebase --merge --skip tests'
 
 . ./test-lib.sh
 
+. "$TEST_DIRECTORY"/lib-rebase.sh
+
 # we assume the default git am -3 --skip strategy is tested independently
 # and always works :)
 
@@ -20,6 +22,20 @@ test_expect_success setup '
 	git commit -a -m "hello world" &&
 	echo goodbye >> hello &&
 	git commit -a -m "goodbye" &&
+	git tag goodbye &&
+
+	git checkout --detach &&
+	git checkout HEAD^ . &&
+	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 &&
@@ -76,4 +92,99 @@ 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" &&
+	(
+		set_fake_editor &&
+		test_must_fail env FAKE_LINES="1 fixup 2" git rebase -i \
+			goodbye^ reverted-goodbye
+	)
+'
+
+test_expect_success 'squash that empties commit fails' '
+	test_when_finished "git rebase --abort" &&
+	(
+		set_fake_editor &&
+		test_must_fail env FAKE_LINES="1 squash 2" git rebase -i \
+			goodbye^ reverted-goodbye
+	)
+'
+
+# Must be the last test in this file
+test_expect_success '$EDITOR and friends are unchanged' '
+	test_editor_unchanged
+'
+
 test_done
diff --git a/third_party/git/t/t3404-rebase-interactive.sh b/third_party/git/t/t3404-rebase-interactive.sh
index 461dd539ff..07a1617351 100755
--- a/third_party/git/t/t3404-rebase-interactive.sh
+++ b/third_party/git/t/t3404-rebase-interactive.sh
@@ -29,9 +29,6 @@ Initial setup:
 
 . "$TEST_DIRECTORY"/lib-rebase.sh
 
-# WARNING: Modifications to the initial repository can change the SHA ID used
-# in the expect2 file for the 'stop on conflicting pick' test.
-
 test_expect_success 'setup' '
 	test_commit A file1 &&
 	test_commit B file1 &&
@@ -75,12 +72,16 @@ test_expect_success 'rebase --keep-empty' '
 	test_line_count = 6 actual
 '
 
-test_expect_success 'rebase -i with empty HEAD' '
+test_expect_success 'rebase -i with empty todo list' '
 	cat >expect <<-\EOF &&
 	error: nothing to do
 	EOF
-	set_fake_editor &&
-	test_must_fail env FAKE_LINES="1 exec_true" git rebase -i HEAD^ >actual 2>&1 &&
+	(
+		set_fake_editor &&
+		test_must_fail env FAKE_LINES="#" \
+			git rebase -i HEAD^ >output 2>&1
+	) &&
+	tail -n 1 output >actual &&  # Ignore output about changing todo list
 	test_i18ncmp expect actual
 '
 
@@ -139,8 +140,11 @@ test_expect_success 'rebase -i sets work tree properly' '
 
 test_expect_success 'rebase -i with the exec command checks tree cleanness' '
 	git checkout master &&
-	set_fake_editor &&
-	test_must_fail env FAKE_LINES="exec_echo_foo_>file1 1" git rebase -i HEAD^ &&
+	(
+		set_fake_editor &&
+		test_must_fail env FAKE_LINES="exec_echo_foo_>file1 1" \
+			git rebase -i HEAD^
+	) &&
 	test_cmp_rev master^ HEAD &&
 	git reset --hard &&
 	git rebase --continue
@@ -155,8 +159,6 @@ test_expect_success 'rebase -x with empty command fails' '
 	test_i18ncmp expected actual
 '
 
-LF='
-'
 test_expect_success 'rebase -x with newline in command fails' '
 	test_when_finished "git rebase --abort ||:" &&
 	test_must_fail env git rebase -x "a${LF}b" @ 2>actual &&
@@ -168,9 +170,11 @@ test_expect_success 'rebase -x with newline in command fails' '
 test_expect_success 'rebase -i with exec of inexistent command' '
 	git checkout master &&
 	test_when_finished "git rebase --abort" &&
-	set_fake_editor &&
-	test_must_fail env FAKE_LINES="exec_this-command-does-not-exist 1" \
-	git rebase -i HEAD^ >actual 2>&1 &&
+	(
+		set_fake_editor &&
+		test_must_fail env FAKE_LINES="exec_this-command-does-not-exist 1" \
+			git rebase -i HEAD^ >actual 2>&1
+	) &&
 	! grep "Maybe git-rebase is broken" actual
 '
 
@@ -181,80 +185,80 @@ test_expect_success 'implicit interactive rebase does not invoke sequence editor
 
 test_expect_success 'no changes are a nop' '
 	git checkout branch2 &&
-	set_fake_editor &&
 	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' '
 	git checkout -b dead-end &&
 	git rm file6 &&
 	git commit -m "stop here" &&
-	set_fake_editor &&
 	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 &&
-	set_fake_editor &&
 	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' '
 	git checkout branch1 &&
 	git tag original-branch1 &&
-	set_fake_editor &&
 	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' '
-	printf "rebase -i (finish): refs/heads/branch1 onto %s\n" \
+	printf "rebase (finish): refs/heads/branch1 onto %s\n" \
 		"$(git rev-parse branch2)" >expected &&
 	git log -g --pretty=%gs -1 refs/heads/branch1 >actual &&
 	test_cmp expected actual
 '
 
 test_expect_success 'exchange two commits' '
-	set_fake_editor &&
-	FAKE_LINES="2 1" git rebase -i HEAD~2 &&
+	(
+		set_fake_editor &&
+		FAKE_LINES="2 1" git rebase -i HEAD~2
+	) &&
 	test H = $(git cat-file commit HEAD^ | sed -ne \$p) &&
-	test G = $(git cat-file commit HEAD | sed -ne \$p)
+	test G = $(git cat-file commit HEAD | sed -ne \$p) &&
+	blob1=$(git rev-parse --short HEAD^:file1) &&
+	blob2=$(git rev-parse --short HEAD:file1) &&
+	commit=$(git rev-parse --short HEAD)
 '
 
 test_expect_success 'stop on conflicting pick' '
-	cat >expect <<-\EOF &&
+	cat >expect <<-EOF &&
 	diff --git a/file1 b/file1
-	index f70f10e..fd79235 100644
+	index $blob1..$blob2 100644
 	--- a/file1
 	+++ b/file1
 	@@ -1 +1 @@
 	-A
 	+G
 	EOF
-	cat >expect2 <<-\EOF &&
+	cat >expect2 <<-EOF &&
 	<<<<<<< HEAD
 	D
 	=======
 	G
-	>>>>>>> 5d18e54... G
+	>>>>>>> $commit (G)
 	EOF
 	git tag new-branch1 &&
-	set_fake_editor &&
 	test_must_fail git rebase -i master &&
 	test "$(git rev-parse HEAD~3)" = "$(git rev-parse master)" &&
 	test_cmp expect .git/rebase-merge/patch &&
@@ -275,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
 '
@@ -283,7 +287,6 @@ test_expect_success 'abort' '
 test_expect_success 'abort with error when new base cannot be checked out' '
 	git rm --cached file1 &&
 	git commit -m "remove file in base" &&
-	set_fake_editor &&
 	test_must_fail git rebase -i master > output 2>&1 &&
 	test_i18ngrep "The following untracked working tree files would be overwritten by checkout:" \
 		output &&
@@ -298,7 +301,6 @@ test_expect_success 'retain authorship' '
 	test_tick &&
 	GIT_AUTHOR_NAME="Twerp Snog" git commit -m "different author" &&
 	git tag twerp &&
-	set_fake_editor &&
 	git rebase -i --onto master HEAD^ &&
 	git show HEAD | grep "^Author: Twerp Snog"
 '
@@ -316,12 +318,11 @@ test_expect_success 'retain authorship w/ conflicts' '
 	test_commit b conflict b conflict-b &&
 	GIT_AUTHOR_NAME=$oGIT_AUTHOR_NAME &&
 
-	set_fake_editor &&
 	test_must_fail git rebase -i conflict-a &&
 	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
 '
@@ -332,11 +333,13 @@ test_expect_success 'squash' '
 	test_tick &&
 	GIT_AUTHOR_NAME="Nitfol" git commit -m "nitfol" file7 &&
 	echo "******************************" &&
-	set_fake_editor &&
-	FAKE_LINES="1 squash 2" EXPECT_HEADER_COUNT=2 \
-		git rebase -i --onto master HEAD~2 &&
+	(
+		set_fake_editor &&
+		FAKE_LINES="1 squash 2" EXPECT_HEADER_COUNT=2 \
+			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' '
@@ -345,7 +348,6 @@ test_expect_success 'retain authorship when squashing' '
 
 test_expect_success REBASE_P '-p handles "no changes" gracefully' '
 	HEAD=$(git rev-parse HEAD) &&
-	set_fake_editor &&
 	git rebase -i -p HEAD^ &&
 	git update-index --refresh &&
 	git diff-files --quiet &&
@@ -355,8 +357,10 @@ test_expect_success REBASE_P '-p handles "no changes" gracefully' '
 
 test_expect_failure REBASE_P 'exchange two commits with -p' '
 	git checkout H &&
-	set_fake_editor &&
-	FAKE_LINES="2 1" git rebase -i -p HEAD~2 &&
+	(
+		set_fake_editor &&
+		FAKE_LINES="2 1" git rebase -i -p HEAD~2
+	) &&
 	test H = $(git cat-file commit HEAD^ | sed -ne \$p) &&
 	test G = $(git cat-file commit HEAD | sed -ne \$p)
 '
@@ -390,14 +394,13 @@ test_expect_success REBASE_P 'preserve merges with -p' '
 	git commit -m M file1 &&
 	git checkout -b to-be-rebased &&
 	test_tick &&
-	set_fake_editor &&
 	git rebase -i -p --onto branch1 master &&
 	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 &&
@@ -405,8 +408,10 @@ test_expect_success REBASE_P 'preserve merges with -p' '
 '
 
 test_expect_success REBASE_P 'edit ancestor with -p' '
-	set_fake_editor &&
-	FAKE_LINES="1 2 edit 3 4" git rebase -i -p HEAD~3 &&
+	(
+		set_fake_editor &&
+		FAKE_LINES="1 2 edit 3 4" git rebase -i -p HEAD~3
+	) &&
 	echo 2 > unrelated-file &&
 	test_tick &&
 	git commit -m L2-modified --amend unrelated-file &&
@@ -420,19 +425,20 @@ test_expect_success REBASE_P 'edit ancestor with -p' '
 test_expect_success '--continue tries to commit' '
 	git reset --hard D &&
 	test_tick &&
-	set_fake_editor &&
-	test_must_fail git rebase -i --onto new-branch1 HEAD^ &&
-	echo resolved > file1 &&
-	git add file1 &&
-	FAKE_COMMIT_MESSAGE="chouette!" git rebase --continue &&
-	test $(git rev-parse HEAD^) = $(git rev-parse new-branch1) &&
+	(
+		set_fake_editor &&
+		test_must_fail git rebase -i --onto new-branch1 HEAD^ &&
+		echo resolved > file1 &&
+		git add file1 &&
+		FAKE_COMMIT_MESSAGE="chouette!" git rebase --continue
+	) &&
+	test_cmp_rev HEAD^ new-branch1 &&
 	git show HEAD | grep chouette
 '
 
 test_expect_success 'verbose flag is heeded, even after --continue' '
 	git reset --hard master@{1} &&
 	test_tick &&
-	set_fake_editor &&
 	test_must_fail git rebase -v -i --onto new-branch1 HEAD^ &&
 	echo resolved > file1 &&
 	git add file1 &&
@@ -442,10 +448,13 @@ test_expect_success 'verbose flag is heeded, even after --continue' '
 
 test_expect_success C_LOCALE_OUTPUT 'multi-squash only fires up editor once' '
 	base=$(git rev-parse HEAD~4) &&
-	set_fake_editor &&
-	FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="1 squash 2 squash 3 squash 4" \
-		EXPECT_HEADER_COUNT=4 \
-		git rebase -i $base &&
+	(
+		set_fake_editor &&
+		FAKE_COMMIT_AMEND="ONCE" \
+			FAKE_LINES="1 squash 2 squash 3 squash 4" \
+			EXPECT_HEADER_COUNT=4 \
+			git rebase -i $base
+	) &&
 	test $base = $(git rev-parse HEAD^) &&
 	test 1 = $(git show | grep ONCE | wc -l)
 '
@@ -453,9 +462,12 @@ test_expect_success C_LOCALE_OUTPUT 'multi-squash only fires up editor once' '
 test_expect_success C_LOCALE_OUTPUT 'multi-fixup does not fire up editor' '
 	git checkout -b multi-fixup E &&
 	base=$(git rev-parse HEAD~4) &&
-	set_fake_editor &&
-	FAKE_COMMIT_AMEND="NEVER" FAKE_LINES="1 fixup 2 fixup 3 fixup 4" \
-		git rebase -i $base &&
+	(
+		set_fake_editor &&
+		FAKE_COMMIT_AMEND="NEVER" \
+			FAKE_LINES="1 fixup 2 fixup 3 fixup 4" \
+			git rebase -i $base
+	) &&
 	test $base = $(git rev-parse HEAD^) &&
 	test 0 = $(git show | grep NEVER | wc -l) &&
 	git checkout @{-1} &&
@@ -465,12 +477,15 @@ test_expect_success C_LOCALE_OUTPUT 'multi-fixup does not fire up editor' '
 test_expect_success 'commit message used after conflict' '
 	git checkout -b conflict-fixup conflict-branch &&
 	base=$(git rev-parse HEAD~4) &&
-	set_fake_editor &&
-	test_must_fail env FAKE_LINES="1 fixup 3 fixup 4" git rebase -i $base &&
-	echo three > conflict &&
-	git add conflict &&
-	FAKE_COMMIT_AMEND="ONCE" EXPECT_HEADER_COUNT=2 \
-		git rebase --continue &&
+	(
+		set_fake_editor &&
+		test_must_fail env FAKE_LINES="1 fixup 3 fixup 4" \
+			git rebase -i $base &&
+		echo three > conflict &&
+		git add conflict &&
+		FAKE_COMMIT_AMEND="ONCE" EXPECT_HEADER_COUNT=2 \
+			git rebase --continue
+	) &&
 	test $base = $(git rev-parse HEAD^) &&
 	test 1 = $(git show | grep ONCE | wc -l) &&
 	git checkout @{-1} &&
@@ -480,12 +495,15 @@ test_expect_success 'commit message used after conflict' '
 test_expect_success 'commit message retained after conflict' '
 	git checkout -b conflict-squash conflict-branch &&
 	base=$(git rev-parse HEAD~4) &&
-	set_fake_editor &&
-	test_must_fail env FAKE_LINES="1 fixup 3 squash 4" git rebase -i $base &&
-	echo three > conflict &&
-	git add conflict &&
-	FAKE_COMMIT_AMEND="TWICE" EXPECT_HEADER_COUNT=2 \
-		git rebase --continue &&
+	(
+		set_fake_editor &&
+		test_must_fail env FAKE_LINES="1 fixup 3 squash 4" \
+			git rebase -i $base &&
+		echo three > conflict &&
+		git add conflict &&
+		FAKE_COMMIT_AMEND="TWICE" EXPECT_HEADER_COUNT=2 \
+			git rebase --continue
+	) &&
 	test $base = $(git rev-parse HEAD^) &&
 	test 2 = $(git show | grep TWICE | wc -l) &&
 	git checkout @{-1} &&
@@ -502,10 +520,13 @@ test_expect_success C_LOCALE_OUTPUT 'squash and fixup generate correct log messa
 	EOF
 	git checkout -b squash-fixup E &&
 	base=$(git rev-parse HEAD~4) &&
-	set_fake_editor &&
-	FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="1 fixup 2 squash 3 fixup 4" \
-		EXPECT_HEADER_COUNT=4 \
-		git rebase -i $base &&
+	(
+		set_fake_editor &&
+		FAKE_COMMIT_AMEND="ONCE" \
+			FAKE_LINES="1 fixup 2 squash 3 fixup 4" \
+			EXPECT_HEADER_COUNT=4 \
+			git rebase -i $base
+	) &&
 	git cat-file commit HEAD | sed -e 1,/^\$/d > actual-squash-fixup &&
 	test_cmp expect-squash-fixup actual-squash-fixup &&
 	git cat-file commit HEAD@{2} |
@@ -519,10 +540,13 @@ test_expect_success C_LOCALE_OUTPUT 'squash and fixup generate correct log messa
 test_expect_success C_LOCALE_OUTPUT 'squash ignores comments' '
 	git checkout -b skip-comments E &&
 	base=$(git rev-parse HEAD~4) &&
-	set_fake_editor &&
-	FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="# 1 # squash 2 # squash 3 # squash 4 #" \
-		EXPECT_HEADER_COUNT=4 \
-		git rebase -i $base &&
+	(
+		set_fake_editor &&
+		FAKE_COMMIT_AMEND="ONCE" \
+			FAKE_LINES="# 1 # squash 2 # squash 3 # squash 4 #" \
+			EXPECT_HEADER_COUNT=4 \
+			git rebase -i $base
+	) &&
 	test $base = $(git rev-parse HEAD^) &&
 	test 1 = $(git show | grep ONCE | wc -l) &&
 	git checkout @{-1} &&
@@ -532,10 +556,13 @@ test_expect_success C_LOCALE_OUTPUT 'squash ignores comments' '
 test_expect_success C_LOCALE_OUTPUT 'squash ignores blank lines' '
 	git checkout -b skip-blank-lines E &&
 	base=$(git rev-parse HEAD~4) &&
-	set_fake_editor &&
-	FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="> 1 > squash 2 > squash 3 > squash 4 >" \
-		EXPECT_HEADER_COUNT=4 \
-		git rebase -i $base &&
+	(
+		set_fake_editor &&
+		FAKE_COMMIT_AMEND="ONCE" \
+			FAKE_LINES="> 1 > squash 2 > squash 3 > squash 4 >" \
+			EXPECT_HEADER_COUNT=4 \
+			git rebase -i $base
+	) &&
 	test $base = $(git rev-parse HEAD^) &&
 	test 1 = $(git show | grep ONCE | wc -l) &&
 	git checkout @{-1} &&
@@ -545,17 +572,21 @@ test_expect_success C_LOCALE_OUTPUT 'squash ignores blank lines' '
 test_expect_success 'squash works as expected' '
 	git checkout -b squash-works no-conflict-branch &&
 	one=$(git rev-parse HEAD~3) &&
-	set_fake_editor &&
-	FAKE_LINES="1 s 3 2" EXPECT_HEADER_COUNT=2 \
-		git rebase -i HEAD~3 &&
+	(
+		set_fake_editor &&
+		FAKE_LINES="1 s 3 2" EXPECT_HEADER_COUNT=2 git rebase -i HEAD~3
+	) &&
 	test $one = $(git rev-parse HEAD~2)
 '
 
 test_expect_success 'interrupted squash works as expected' '
 	git checkout -b interrupted-squash conflict-branch &&
 	one=$(git rev-parse HEAD~3) &&
-	set_fake_editor &&
-	test_must_fail env FAKE_LINES="1 squash 3 2" git rebase -i HEAD~3 &&
+	(
+		set_fake_editor &&
+		test_must_fail env FAKE_LINES="1 squash 3 2" \
+			git rebase -i HEAD~3
+	) &&
 	test_write_lines one two four > conflict &&
 	git add conflict &&
 	test_must_fail git rebase --continue &&
@@ -568,8 +599,11 @@ test_expect_success 'interrupted squash works as expected' '
 test_expect_success 'interrupted squash works as expected (case 2)' '
 	git checkout -b interrupted-squash2 conflict-branch &&
 	one=$(git rev-parse HEAD~3) &&
-	set_fake_editor &&
-	test_must_fail env FAKE_LINES="3 squash 1 2" git rebase -i HEAD~3 &&
+	(
+		set_fake_editor &&
+		test_must_fail env FAKE_LINES="3 squash 1 2" \
+			git rebase -i HEAD~3
+	) &&
 	test_write_lines one four > conflict &&
 	git add conflict &&
 	test_must_fail git rebase --continue &&
@@ -589,11 +623,13 @@ test_expect_success '--continue tries to commit, even for "edit"' '
 	git commit -m "unrelated change" &&
 	parent=$(git rev-parse HEAD^) &&
 	test_tick &&
-	set_fake_editor &&
-	FAKE_LINES="edit 1" git rebase -i HEAD^ &&
-	echo edited > file7 &&
-	git add file7 &&
-	FAKE_COMMIT_MESSAGE="chouette!" git rebase --continue &&
+	(
+		set_fake_editor &&
+		FAKE_LINES="edit 1" git rebase -i HEAD^ &&
+		echo edited > file7 &&
+		git add file7 &&
+		FAKE_COMMIT_MESSAGE="chouette!" git rebase --continue
+	) &&
 	test edited = $(git show HEAD:file7) &&
 	git show HEAD | grep chouette &&
 	test $parent = $(git rev-parse HEAD^)
@@ -602,34 +638,41 @@ test_expect_success '--continue tries to commit, even for "edit"' '
 test_expect_success 'aborted --continue does not squash commits after "edit"' '
 	old=$(git rev-parse HEAD) &&
 	test_tick &&
-	set_fake_editor &&
-	FAKE_LINES="edit 1" git rebase -i HEAD^ &&
-	echo "edited again" > file7 &&
-	git add file7 &&
-	test_must_fail env FAKE_COMMIT_MESSAGE=" " git rebase --continue &&
+	(
+		set_fake_editor &&
+		FAKE_LINES="edit 1" git rebase -i HEAD^ &&
+		echo "edited again" > file7 &&
+		git add file7 &&
+		test_must_fail env FAKE_COMMIT_MESSAGE=" " git rebase --continue
+	) &&
 	test $old = $(git rev-parse HEAD) &&
 	git rebase --abort
 '
 
 test_expect_success 'auto-amend only edited commits after "edit"' '
 	test_tick &&
-	set_fake_editor &&
-	FAKE_LINES="edit 1" git rebase -i HEAD^ &&
-	echo "edited again" > file7 &&
-	git add file7 &&
-	FAKE_COMMIT_MESSAGE="edited file7 again" git commit &&
-	echo "and again" > file7 &&
-	git add file7 &&
-	test_tick &&
-	test_must_fail env FAKE_COMMIT_MESSAGE="and again" git rebase --continue &&
+	(
+		set_fake_editor &&
+		FAKE_LINES="edit 1" git rebase -i HEAD^ &&
+		echo "edited again" > file7 &&
+		git add file7 &&
+		FAKE_COMMIT_MESSAGE="edited file7 again" git commit &&
+		echo "and again" > file7 &&
+		git add file7 &&
+		test_tick &&
+		test_must_fail env FAKE_COMMIT_MESSAGE="and again" \
+			git rebase --continue
+	) &&
 	git rebase --abort
 '
 
 test_expect_success 'clean error after failed "exec"' '
 	test_tick &&
 	test_when_finished "git rebase --abort || :" &&
-	set_fake_editor &&
-	test_must_fail env FAKE_LINES="1 exec_false" git rebase -i HEAD^ &&
+	(
+		set_fake_editor &&
+		test_must_fail env FAKE_LINES="1 exec_false" git rebase -i HEAD^
+	) &&
 	echo "edited again" > file7 &&
 	git add file7 &&
 	test_must_fail git rebase --continue 2>error &&
@@ -640,8 +683,10 @@ test_expect_success 'rebase a detached HEAD' '
 	grandparent=$(git rev-parse HEAD~2) &&
 	git checkout $(git rev-parse HEAD) &&
 	test_tick &&
-	set_fake_editor &&
-	FAKE_LINES="2 1" git rebase -i HEAD~2 &&
+	(
+		set_fake_editor &&
+		FAKE_LINES="2 1" git rebase -i HEAD~2
+	) &&
 	test $grandparent = $(git rev-parse HEAD~2)
 '
 
@@ -656,9 +701,10 @@ test_expect_success 'rebase a commit violating pre-commit' '
 	test_must_fail git commit -m doesnt-verify file1 &&
 	git commit -m doesnt-verify --no-verify file1 &&
 	test_tick &&
-	set_fake_editor &&
-	FAKE_LINES=2 git rebase -i HEAD~2
-
+	(
+		set_fake_editor &&
+		FAKE_LINES=2 git rebase -i HEAD~2
+	)
 '
 
 test_expect_success 'rebase with a file named HEAD in worktree' '
@@ -678,8 +724,10 @@ test_expect_success 'rebase with a file named HEAD in worktree' '
 		git commit -m "Add body"
 	) &&
 
-	set_fake_editor &&
-	FAKE_LINES="1 squash 2" git rebase -i @{-1} &&
+	(
+		set_fake_editor &&
+		FAKE_LINES="1 squash 2" git rebase -i @{-1}
+	) &&
 	test "$(git show -s --pretty=format:%an)" = "Squashed Away"
 
 '
@@ -690,9 +738,8 @@ test_expect_success 'do "noop" when there is nothing to cherry-pick' '
 	GIT_EDITOR=: git commit --amend \
 		--author="Somebody else <somebody@else.com>" &&
 	test $(git rev-parse branch3) != $(git rev-parse branch4) &&
-	set_fake_editor &&
 	git rebase -i branch3 &&
-	test $(git rev-parse branch3) = $(git rev-parse branch4)
+	test_cmp_rev branch3 branch4
 
 '
 
@@ -715,13 +762,14 @@ test_expect_success 'submodule rebase setup' '
 		git commit -a -m "submodule second"
 	) &&
 	test_tick &&
-	set_fake_editor &&
 	git commit -a -m "Three changes submodule"
 '
 
 test_expect_success 'submodule rebase -i' '
-	set_fake_editor &&
-	FAKE_LINES="1 squash 2 3" git rebase -i A
+	(
+		set_fake_editor &&
+		FAKE_LINES="1 squash 2 3" git rebase -i A
+	)
 '
 
 test_expect_success 'submodule conflict setup' '
@@ -738,7 +786,6 @@ test_expect_success 'submodule conflict setup' '
 '
 
 test_expect_success 'rebase -i continue with only submodule staged' '
-	set_fake_editor &&
 	test_must_fail git rebase -i submodule-base &&
 	git add sub &&
 	git rebase --continue &&
@@ -748,11 +795,10 @@ test_expect_success 'rebase -i continue with only submodule staged' '
 test_expect_success 'rebase -i continue with unstaged submodule' '
 	git checkout submodule-topic &&
 	git reset --hard &&
-	set_fake_editor &&
 	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' '
@@ -761,7 +807,6 @@ test_expect_success 'avoid unnecessary reset' '
 	test-tool chmtime =123456789 file3 &&
 	git update-index --refresh &&
 	HEAD=$(git rev-parse HEAD) &&
-	set_fake_editor &&
 	git rebase -i HEAD~4 &&
 	test $HEAD = $(git rev-parse HEAD) &&
 	MTIME=$(test-tool chmtime --get file3) &&
@@ -770,16 +815,22 @@ test_expect_success 'avoid unnecessary reset' '
 
 test_expect_success 'reword' '
 	git checkout -b reword-branch master &&
-	set_fake_editor &&
-	FAKE_LINES="1 2 3 reword 4" FAKE_COMMIT_MESSAGE="E changed" 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^) &&
-	FAKE_LINES="1 2 reword 3 4" FAKE_COMMIT_MESSAGE="D changed" git rebase -i A &&
-	git show HEAD^ | grep "D changed" &&
-	FAKE_LINES="reword 1 2 3 4" FAKE_COMMIT_MESSAGE="B changed" git rebase -i A &&
-	git show HEAD~3 | grep "B changed" &&
-	FAKE_LINES="1 r 2 pick 3 p 4" FAKE_COMMIT_MESSAGE="C changed" git rebase -i A &&
+	(
+		set_fake_editor &&
+		FAKE_LINES="1 2 3 reword 4" FAKE_COMMIT_MESSAGE="E changed" \
+			git rebase -i A &&
+		git show HEAD | grep "E changed" &&
+		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" &&
+		FAKE_LINES="reword 1 2 3 4" FAKE_COMMIT_MESSAGE="B changed" \
+			git rebase -i A &&
+		git show HEAD~3 | grep "B changed" &&
+		FAKE_LINES="1 r 2 pick 3 p 4" FAKE_COMMIT_MESSAGE="C changed" \
+			git rebase -i A
+	) &&
 	git show HEAD~2 | grep "C changed"
 '
 
@@ -790,7 +841,6 @@ test_expect_success 'rebase -i can copy notes' '
 	test_commit n2 &&
 	test_commit n3 &&
 	git notes add -m"a note" n3 &&
-	set_fake_editor &&
 	git rebase -i --onto n1 n2 &&
 	test "a note" = "$(git notes show HEAD)"
 '
@@ -803,8 +853,11 @@ test_expect_success 'rebase -i can copy notes over a fixup' '
 	EOF
 	git reset --hard n3 &&
 	git notes add -m"an earlier note" n2 &&
-	set_fake_editor &&
-	GIT_NOTES_REWRITE_MODE=concatenate FAKE_LINES="1 f 2" git rebase -i n1 &&
+	(
+		set_fake_editor &&
+		GIT_NOTES_REWRITE_MODE=concatenate FAKE_LINES="1 f 2" \
+			git rebase -i n1
+	) &&
 	git notes show > output &&
 	test_cmp expect output
 '
@@ -813,8 +866,10 @@ test_expect_success 'rebase while detaching HEAD' '
 	git symbolic-ref HEAD &&
 	grandparent=$(git rev-parse HEAD~2) &&
 	test_tick &&
-	set_fake_editor &&
-	FAKE_LINES="2 1" git rebase -i HEAD~2 HEAD^0 &&
+	(
+		set_fake_editor &&
+		FAKE_LINES="2 1" git rebase -i HEAD~2 HEAD^0
+	) &&
 	test $grandparent = $(git rev-parse HEAD~2) &&
 	test_must_fail git symbolic-ref HEAD
 '
@@ -823,7 +878,6 @@ test_tick # Ensure that the rebased commits get a different timestamp.
 test_expect_success 'always cherry-pick with --no-ff' '
 	git checkout no-ff-branch &&
 	git tag original-no-ff-branch &&
-	set_fake_editor &&
 	git rebase -i --no-ff A &&
 	for p in 0 1 2
 	do
@@ -831,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
 '
@@ -855,8 +909,10 @@ test_expect_success 'set up commits with funny messages' '
 test_expect_success 'rebase-i history with funny messages' '
 	git rev-list A..funny >expect &&
 	test_tick &&
-	set_fake_editor &&
-	FAKE_LINES="1 2 3 4" git rebase -i A &&
+	(
+		set_fake_editor &&
+		FAKE_LINES="1 2 3 4" git rebase -i A
+	) &&
 	git rev-list A.. >actual &&
 	test_cmp expect actual
 '
@@ -870,9 +926,9 @@ test_expect_success 'prepare for rebase -i --exec' '
 '
 
 test_expect_success 'running "git rebase -i --exec git show HEAD"' '
-	set_fake_editor &&
-	git rebase -i --exec "git show HEAD" HEAD~2 >actual &&
 	(
+		set_fake_editor &&
+		git rebase -i --exec "git show HEAD" HEAD~2 >actual &&
 		FAKE_LINES="1 exec_git_show_HEAD 2 exec_git_show_HEAD" &&
 		export FAKE_LINES &&
 		git rebase -i HEAD~2 >expect
@@ -883,9 +939,9 @@ test_expect_success 'running "git rebase -i --exec git show HEAD"' '
 
 test_expect_success 'running "git rebase --exec git show HEAD -i"' '
 	git reset --hard execute &&
-	set_fake_editor &&
-	git rebase --exec "git show HEAD" -i HEAD~2 >actual &&
 	(
+		set_fake_editor &&
+		git rebase --exec "git show HEAD" -i HEAD~2 >actual &&
 		FAKE_LINES="1 exec_git_show_HEAD 2 exec_git_show_HEAD" &&
 		export FAKE_LINES &&
 		git rebase -i HEAD~2 >expect
@@ -896,9 +952,9 @@ test_expect_success 'running "git rebase --exec git show HEAD -i"' '
 
 test_expect_success 'running "git rebase -ix git show HEAD"' '
 	git reset --hard execute &&
-	set_fake_editor &&
-	git rebase -ix "git show HEAD" HEAD~2 >actual &&
 	(
+		set_fake_editor &&
+		git rebase -ix "git show HEAD" HEAD~2 >actual &&
 		FAKE_LINES="1 exec_git_show_HEAD 2 exec_git_show_HEAD" &&
 		export FAKE_LINES &&
 		git rebase -i HEAD~2 >expect
@@ -910,9 +966,9 @@ test_expect_success 'running "git rebase -ix git show HEAD"' '
 
 test_expect_success 'rebase -ix with several <CMD>' '
 	git reset --hard execute &&
-	set_fake_editor &&
-	git rebase -ix "git show HEAD; pwd" HEAD~2 >actual &&
 	(
+		set_fake_editor &&
+		git rebase -ix "git show HEAD; pwd" HEAD~2 >actual &&
 		FAKE_LINES="1 exec_git_show_HEAD;_pwd 2 exec_git_show_HEAD;_pwd" &&
 		export FAKE_LINES &&
 		git rebase -i HEAD~2 >expect
@@ -923,9 +979,9 @@ test_expect_success 'rebase -ix with several <CMD>' '
 
 test_expect_success 'rebase -ix with several instances of --exec' '
 	git reset --hard execute &&
-	set_fake_editor &&
-	git rebase -i --exec "git show HEAD" --exec "pwd" HEAD~2 >actual &&
 	(
+		set_fake_editor &&
+		git rebase -i --exec "git show HEAD" --exec "pwd" HEAD~2 >actual &&
 		FAKE_LINES="1 exec_git_show_HEAD exec_pwd 2
 				exec_git_show_HEAD exec_pwd" &&
 		export FAKE_LINES &&
@@ -944,13 +1000,11 @@ test_expect_success C_LOCALE_OUTPUT 'rebase -ix with --autosquash' '
 	echo bis >bis.txt &&
 	git add bis.txt &&
 	git commit -m "fixup! two_exec" &&
-	set_fake_editor &&
-	(
-		git checkout -b autosquash_actual &&
-		git rebase -i --exec "git show HEAD" --autosquash HEAD~4 >actual
-	) &&
+	git checkout -b autosquash_actual &&
+	git rebase -i --exec "git show HEAD" --autosquash HEAD~4 >actual &&
 	git checkout autosquash &&
 	(
+		set_fake_editor &&
 		git checkout -b autosquash_expected &&
 		FAKE_LINES="1 fixup 3 fixup 4 exec_git_show_HEAD 2 exec_git_show_HEAD" &&
 		export FAKE_LINES &&
@@ -971,7 +1025,6 @@ test_expect_success 'rebase --exec works without -i ' '
 
 test_expect_success 'rebase -i --exec without <CMD>' '
 	git reset --hard execute &&
-	set_fake_editor &&
 	test_must_fail git rebase -i --exec 2>actual &&
 	test_i18ngrep "requires a value" actual &&
 	git checkout master
@@ -979,8 +1032,10 @@ test_expect_success 'rebase -i --exec without <CMD>' '
 
 test_expect_success 'rebase -i --root re-order and drop commits' '
 	git checkout E &&
-	set_fake_editor &&
-	FAKE_LINES="3 1 2 5" git rebase -i --root &&
+	(
+		set_fake_editor &&
+		FAKE_LINES="3 1 2 5" git rebase -i --root
+	) &&
 	test E = $(git cat-file commit HEAD | sed -ne \$p) &&
 	test B = $(git cat-file commit HEAD^ | sed -ne \$p) &&
 	test A = $(git cat-file commit HEAD^^ | sed -ne \$p) &&
@@ -993,39 +1048,59 @@ test_expect_success 'rebase -i --root retain root commit author and message' '
 	echo B >file7 &&
 	git add file7 &&
 	GIT_AUTHOR_NAME="Twerp Snog" git commit -m "different author" &&
-	set_fake_editor &&
-	FAKE_LINES="2" git rebase -i --root &&
+	(
+		set_fake_editor &&
+		FAKE_LINES="2" git rebase -i --root
+	) &&
 	git cat-file commit HEAD | grep -q "^author Twerp Snog" &&
 	git cat-file commit HEAD | grep -q "^different author$"
 '
 
 test_expect_success 'rebase -i --root temporary sentinel commit' '
 	git checkout B &&
-	set_fake_editor &&
-	test_must_fail env FAKE_LINES="2" git rebase -i --root &&
-	git cat-file commit HEAD | grep "^tree 4b825dc642cb" &&
+	(
+		set_fake_editor &&
+		test_must_fail env FAKE_LINES="2" git rebase -i --root
+	) &&
+	git cat-file commit HEAD | grep "^tree $EMPTY_TREE" &&
 	git rebase --abort
 '
 
 test_expect_success 'rebase -i --root fixup root commit' '
 	git checkout B &&
-	set_fake_editor &&
-	FAKE_LINES="1 fixup 2" git rebase -i --root &&
+	(
+		set_fake_editor &&
+		FAKE_LINES="1 fixup 2" git rebase -i --root
+	) &&
 	test A = $(git cat-file commit HEAD | sed -ne \$p) &&
 	test B = $(git show HEAD:file1) &&
 	test 0 = $(git cat-file commit HEAD | grep -c ^parent\ )
 '
 
-test_expect_success 'rebase -i --root reword root commit' '
+test_expect_success 'rebase -i --root reword original root commit' '
 	test_when_finished "test_might_fail git rebase --abort" &&
-	git checkout -b reword-root-branch master &&
-	set_fake_editor &&
-	FAKE_LINES="reword 1 2" FAKE_COMMIT_MESSAGE="A changed" \
-	git rebase -i --root &&
+	git checkout -b reword-original-root-branch master &&
+	(
+		set_fake_editor &&
+		FAKE_LINES="reword 1 2" FAKE_COMMIT_MESSAGE="A changed" \
+			git rebase -i --root
+	) &&
 	git show HEAD^ | grep "A changed" &&
 	test -z "$(git show -s --format=%p HEAD^)"
 '
 
+test_expect_success 'rebase -i --root reword new root commit' '
+	test_when_finished "test_might_fail git rebase --abort" &&
+	git checkout -b reword-now-root-branch master &&
+	(
+		set_fake_editor &&
+		FAKE_LINES="reword 3 1" FAKE_COMMIT_MESSAGE="C changed" \
+		git rebase -i --root
+	) &&
+	git show HEAD^ | grep "C changed" &&
+	test -z "$(git show -s --format=%p HEAD^)"
+'
+
 test_expect_success 'rebase -i --root when root has untracked file conflict' '
 	test_when_finished "reset_rebase" &&
 	git checkout -b failing-root-pick A &&
@@ -1033,8 +1108,10 @@ test_expect_success 'rebase -i --root when root has untracked file conflict' '
 	git rm file1 &&
 	git commit -m "remove file 1 add file 2" &&
 	echo z >file1 &&
-	set_fake_editor &&
-	test_must_fail env FAKE_LINES="1 2" git rebase -i --root &&
+	(
+		set_fake_editor &&
+		test_must_fail env FAKE_LINES="1 2" git rebase -i --root
+	) &&
 	rm file1 &&
 	git rebase --continue &&
 	test "$(git log -1 --format=%B)" = "remove file 1 add file 2" &&
@@ -1044,32 +1121,38 @@ test_expect_success 'rebase -i --root when root has untracked file conflict' '
 test_expect_success 'rebase -i --root reword root when root has untracked file conflict' '
 	test_when_finished "reset_rebase" &&
 	echo z>file1 &&
-	set_fake_editor &&
-	test_must_fail env FAKE_LINES="reword 1 2" \
-		FAKE_COMMIT_MESSAGE="Modified A" git rebase -i --root &&
-	rm file1 &&
-	FAKE_COMMIT_MESSAGE="Reworded A" git rebase --continue &&
+	(
+		set_fake_editor &&
+		test_must_fail env FAKE_LINES="reword 1 2" \
+			FAKE_COMMIT_MESSAGE="Modified A" git rebase -i --root &&
+		rm file1 &&
+		FAKE_COMMIT_MESSAGE="Reworded A" git rebase --continue
+	) &&
 	test "$(git log -1 --format=%B HEAD^)" = "Reworded A" &&
 	test "$(git rev-list --count HEAD)" = 2
 '
 
 test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-interactive rebase' '
-	git checkout reword-root-branch &&
+	git checkout reword-original-root-branch &&
 	git reset --hard &&
 	git checkout conflict-branch &&
-	set_fake_editor &&
-	test_must_fail git rebase --onto HEAD~2 HEAD~ &&
-	test_must_fail git rebase --edit-todo &&
+	(
+		set_fake_editor &&
+		test_must_fail git rebase -f --apply --onto HEAD~2 HEAD~ &&
+		test_must_fail git rebase --edit-todo
+	) &&
 	git rebase --abort
 '
 
 test_expect_success 'rebase --edit-todo can be used to modify todo' '
 	git reset --hard &&
 	git checkout no-conflict-branch^0 &&
-	set_fake_editor &&
-	FAKE_LINES="edit 1 2 3" git rebase -i HEAD~3 &&
-	FAKE_LINES="2 1" git rebase --edit-todo &&
-	git rebase --continue &&
+	(
+		set_fake_editor &&
+		FAKE_LINES="edit 1 2 3" git rebase -i HEAD~3 &&
+		FAKE_LINES="2 1" git rebase --edit-todo &&
+		git rebase --continue
+	) &&
 	test M = $(git cat-file commit HEAD^ | sed -ne \$p) &&
 	test L = $(git cat-file commit HEAD | sed -ne \$p)
 '
@@ -1077,13 +1160,12 @@ test_expect_success 'rebase --edit-todo can be used to modify todo' '
 test_expect_success 'rebase -i produces readable reflog' '
 	git reset --hard &&
 	git branch -f branch-reflog-test H &&
-	set_fake_editor &&
 	git rebase -i --onto I F branch-reflog-test &&
 	cat >expect <<-\EOF &&
-	rebase -i (finish): returning to refs/heads/branch-reflog-test
-	rebase -i (pick): H
-	rebase -i (pick): G
-	rebase -i (start): checkout I
+	rebase (finish): returning to refs/heads/branch-reflog-test
+	rebase (pick): H
+	rebase (pick): G
+	rebase (start): checkout I
 	EOF
 	git reflog -n4 HEAD |
 	sed "s/[^:]*: //" >actual &&
@@ -1098,8 +1180,10 @@ test_expect_success 'rebase -i respects core.commentchar' '
 	sed -e "2,\$s/^/\\\\/" "$1" >"$1.tmp" &&
 	mv "$1.tmp" "$1"
 	EOF
-	test_set_editor "$(pwd)/remove-all-but-first.sh" &&
-	git rebase -i B &&
+	(
+		test_set_editor "$(pwd)/remove-all-but-first.sh" &&
+		git rebase -i B
+	) &&
 	test B = $(git cat-file commit HEAD^ | sed -ne \$p)
 '
 
@@ -1108,9 +1192,11 @@ test_expect_success 'rebase -i respects core.commentchar=auto' '
 	write_script copy-edit-script.sh <<-\EOF &&
 	cp "$1" edit-script
 	EOF
-	test_set_editor "$(pwd)/copy-edit-script.sh" &&
 	test_when_finished "git rebase --abort || :" &&
-	git rebase -i HEAD^ &&
+	(
+		test_set_editor "$(pwd)/copy-edit-script.sh" &&
+		git rebase -i HEAD^
+	) &&
 	test -z "$(grep -ve "^#" -e "^\$" -e "^pick" edit-script)"
 '
 
@@ -1145,8 +1231,11 @@ test_expect_success 'interrupted rebase -i with --strategy and -X' '
 	echo five >conflict &&
 	echo Z >file1 &&
 	git commit -a -m "one file conflict" &&
-	set_fake_editor &&
-	FAKE_LINES="edit 1 2" git rebase -i --strategy=recursive -Xours conflict-branch &&
+	(
+		set_fake_editor &&
+		FAKE_LINES="edit 1 2" git rebase -i --strategy=recursive \
+			-Xours conflict-branch
+	) &&
 	git rebase --continue &&
 	test $(git show conflict-branch:conflict) = $(cat conflict) &&
 	test $(cat file1) = Z
@@ -1161,7 +1250,7 @@ test_expect_success 'rebase -i error on commits with \ in message' '
 	test_expect_code 1 grep  "	emp" error
 '
 
-test_expect_success 'short SHA-1 setup' '
+test_expect_success 'short commit ID setup' '
 	test_when_finished "git checkout master" &&
 	git checkout --orphan collide &&
 	git rm -rf . &&
@@ -1173,22 +1262,68 @@ test_expect_success 'short SHA-1 setup' '
 	)
 '
 
-test_expect_success 'short SHA-1 collide' '
+if test -n "$GIT_TEST_FIND_COLLIDER"
+then
+	author="$(unset test_tick; test_tick; git var GIT_AUTHOR_IDENT)"
+	committer="$(unset test_tick; test_tick; git var GIT_COMMITTER_IDENT)"
+	blob="$(git rev-parse collide2:collide)"
+	from="$(git rev-parse collide1^0)"
+	repl="commit refs/heads/collider-&\\n"
+	repl="${repl}author $author\\ncommitter $committer\\n"
+	repl="${repl}data <<EOF\\ncollide2 &\\nEOF\\n"
+	repl="${repl}from $from\\nM 100644 $blob collide\\n"
+	test_seq 1 32768 | sed "s|.*|$repl|" >script &&
+	git fast-import <script &&
+	git pack-refs &&
+	git for-each-ref >refs &&
+	grep "^$(test_oid t3404_collision)" <refs >matches &&
+	cat matches &&
+	test_line_count -gt 2 matches || {
+		echo "Could not find a collider" >&2
+		exit 1
+	}
+fi
+
+test_expect_success 'short commit ID collide' '
+	test_oid_cache <<-EOF &&
+	# collision-related constants
+	t3404_collision	sha1:6bcd
+	t3404_collision	sha256:0161
+	t3404_collider	sha1:ac4f2ee
+	t3404_collider	sha256:16697
+	EOF
 	test_when_finished "reset_rebase && git checkout master" &&
 	git checkout collide &&
+	colliding_id=$(test_oid t3404_collision) &&
+	hexsz=$(test_oid hexsz) &&
+	test $colliding_id = "$(git rev-parse HEAD | cut -c 1-4)" &&
+	test_config core.abbrev 4 &&
 	(
-	unset test_tick &&
-	test_tick &&
-	set_fake_editor &&
-	FAKE_COMMIT_MESSAGE="collide2 ac4f2ee" \
-	FAKE_LINES="reword 1 2" git rebase -i HEAD~2
-	)
+		unset test_tick &&
+		test_tick &&
+		set_fake_editor &&
+		FAKE_COMMIT_MESSAGE="collide2 $(test_oid t3404_collider)" \
+		FAKE_LINES="reword 1 break 2" git rebase -i HEAD~2 &&
+		test $colliding_id = "$(git rev-parse HEAD | cut -c 1-4)" &&
+		grep "^pick $colliding_id " \
+			.git/rebase-merge/git-rebase-todo.tmp &&
+		grep "^pick [0-9a-f]\{$hexsz\}" \
+			.git/rebase-merge/git-rebase-todo &&
+		grep "^pick [0-9a-f]\{$hexsz\}" \
+			.git/rebase-merge/git-rebase-todo.backup &&
+		git rebase --continue
+	) &&
+	collide2="$(git rev-parse HEAD~1 | cut -c 1-4)" &&
+	collide3="$(git rev-parse collide3 | cut -c 1-4)" &&
+	test "$collide2" = "$collide3"
 '
 
 test_expect_success 'respect core.abbrev' '
 	git config core.abbrev 12 &&
-	set_cat_todo_editor &&
-	test_must_fail git rebase -i HEAD~4 >todo-list &&
+	(
+		set_cat_todo_editor &&
+		test_must_fail git rebase -i HEAD~4 >todo-list
+	) &&
 	test 4 = $(grep -c "pick [0-9a-f]\{12,\}" todo-list)
 '
 
@@ -1196,16 +1331,20 @@ test_expect_success 'todo count' '
 	write_script dump-raw.sh <<-\EOF &&
 		cat "$1"
 	EOF
-	test_set_editor "$(pwd)/dump-raw.sh" &&
-	git rebase -i HEAD~4 >actual &&
+	(
+		test_set_editor "$(pwd)/dump-raw.sh" &&
+		git rebase -i HEAD~4 >actual
+	) &&
 	test_i18ngrep "^# Rebase ..* onto ..* ([0-9]" actual
 '
 
 test_expect_success 'rebase -i commits that overwrite untracked files (pick)' '
 	git checkout --force branch2 &&
 	git clean -f &&
-	set_fake_editor &&
-	FAKE_LINES="edit 1 2" git rebase -i A &&
+	(
+		set_fake_editor &&
+		FAKE_LINES="edit 1 2" git rebase -i A
+	) &&
 	test_cmp_rev HEAD F &&
 	test_path_is_missing file6 &&
 	>file6 &&
@@ -1220,8 +1359,10 @@ test_expect_success 'rebase -i commits that overwrite untracked files (squash)'
 	git checkout --force branch2 &&
 	git clean -f &&
 	git tag original-branch2 &&
-	set_fake_editor &&
-	FAKE_LINES="edit 1 squash 2" git rebase -i A &&
+	(
+		set_fake_editor &&
+		FAKE_LINES="edit 1 squash 2" git rebase -i A
+	) &&
 	test_cmp_rev HEAD F &&
 	test_path_is_missing file6 &&
 	>file6 &&
@@ -1236,8 +1377,10 @@ test_expect_success 'rebase -i commits that overwrite untracked files (squash)'
 test_expect_success 'rebase -i commits that overwrite untracked files (no ff)' '
 	git checkout --force branch2 &&
 	git clean -f &&
-	set_fake_editor &&
-	FAKE_LINES="edit 1 2" git rebase -i --no-ff A &&
+	(
+		set_fake_editor &&
+		FAKE_LINES="edit 1 2" git rebase -i --no-ff A
+	) &&
 	test $(git cat-file commit HEAD | sed -ne \$p) = F &&
 	test_path_is_missing file6 &&
 	>file6 &&
@@ -1260,8 +1403,10 @@ test_expect_success 'rebase --continue removes CHERRY_PICK_HEAD' '
 	git tag seq-onto &&
 	git reset --hard HEAD~2 &&
 	git cherry-pick seq-onto &&
-	set_fake_editor &&
-	test_must_fail env FAKE_LINES= git rebase -i seq-onto &&
+	(
+		set_fake_editor &&
+		test_must_fail env FAKE_LINES= git rebase -i seq-onto
+	) &&
 	test -d .git/rebase-merge &&
 	git rebase --continue &&
 	git diff --exit-code seq-onto &&
@@ -1280,8 +1425,10 @@ rebase_setup_and_clean () {
 
 test_expect_success 'drop' '
 	rebase_setup_and_clean drop-test &&
-	set_fake_editor &&
-	FAKE_LINES="1 drop 2 3 d 4 5" git rebase -i --root &&
+	(
+		set_fake_editor &&
+		FAKE_LINES="1 drop 2 3 d 4 5" git rebase -i --root
+	) &&
 	test E = $(git cat-file commit HEAD | sed -ne \$p) &&
 	test C = $(git cat-file commit HEAD^ | sed -ne \$p) &&
 	test A = $(git cat-file commit HEAD^^ | sed -ne \$p)
@@ -1290,9 +1437,10 @@ test_expect_success 'drop' '
 test_expect_success 'rebase -i respects rebase.missingCommitsCheck = ignore' '
 	test_config rebase.missingCommitsCheck ignore &&
 	rebase_setup_and_clean missing-commit &&
-	set_fake_editor &&
-	FAKE_LINES="1 2 3 4" \
-		git rebase -i --root 2>actual &&
+	(
+		set_fake_editor &&
+		FAKE_LINES="1 2 3 4" git rebase -i --root 2>actual
+	) &&
 	test D = $(git cat-file commit HEAD | sed -ne \$p) &&
 	test_i18ngrep \
 		"Successfully rebased and updated refs/heads/missing-commit" \
@@ -1308,9 +1456,10 @@ test_expect_success 'rebase -i respects rebase.missingCommitsCheck = warn' '
 	EOF
 	test_config rebase.missingCommitsCheck warn &&
 	rebase_setup_and_clean missing-commit &&
-	set_fake_editor &&
-	FAKE_LINES="1 2 3 4" \
-		git rebase -i --root 2>actual.2 &&
+	(
+		set_fake_editor &&
+		FAKE_LINES="1 2 3 4" git rebase -i --root 2>actual.2
+	) &&
 	head -n4 actual.2 >actual &&
 	test_i18ncmp expect actual &&
 	test D = $(git cat-file commit HEAD | sed -ne \$p)
@@ -1332,19 +1481,141 @@ test_expect_success 'rebase -i respects rebase.missingCommitsCheck = error' '
 	EOF
 	test_config rebase.missingCommitsCheck error &&
 	rebase_setup_and_clean missing-commit &&
-	set_fake_editor &&
-	test_must_fail env FAKE_LINES="1 2 4" \
-		git rebase -i --root 2>actual &&
-	test_i18ncmp expect actual &&
-	cp .git/rebase-merge/git-rebase-todo.backup \
-		.git/rebase-merge/git-rebase-todo &&
-	FAKE_LINES="1 2 drop 3 4 drop 5" \
-		git rebase --edit-todo &&
+	(
+		set_fake_editor &&
+		test_must_fail env FAKE_LINES="1 2 4" \
+			git rebase -i --root 2>actual &&
+		test_i18ncmp expect actual &&
+		cp .git/rebase-merge/git-rebase-todo.backup \
+			.git/rebase-merge/git-rebase-todo &&
+		FAKE_LINES="1 2 drop 3 4 drop 5" git rebase --edit-todo
+	) &&
 	git rebase --continue &&
 	test D = $(git cat-file commit HEAD | sed -ne \$p) &&
 	test B = $(git cat-file commit HEAD^ | sed -ne \$p)
 '
 
+test_expect_success 'rebase --edit-todo respects rebase.missingCommitsCheck = ignore' '
+	test_config rebase.missingCommitsCheck ignore &&
+	rebase_setup_and_clean missing-commit &&
+	(
+		set_fake_editor &&
+		FAKE_LINES="break 1 2 3 4 5" git rebase -i --root &&
+		FAKE_LINES="1 2 3 4" git rebase --edit-todo &&
+		git rebase --continue 2>actual
+	) &&
+	test D = $(git cat-file commit HEAD | sed -ne \$p) &&
+	test_i18ngrep \
+		"Successfully rebased and updated refs/heads/missing-commit" \
+		actual
+'
+
+test_expect_success 'rebase --edit-todo respects rebase.missingCommitsCheck = warn' '
+	cat >expect <<-EOF &&
+	error: invalid line 1: badcmd $(git rev-list --pretty=oneline --abbrev-commit -1 master~4)
+	Warning: some commits may have been dropped accidentally.
+	Dropped commits (newer to older):
+	 - $(git rev-list --pretty=oneline --abbrev-commit -1 master)
+	 - $(git rev-list --pretty=oneline --abbrev-commit -1 master~4)
+	To avoid this message, use "drop" to explicitly remove a commit.
+	EOF
+	head -n4 expect >expect.2 &&
+	tail -n1 expect >>expect.2 &&
+	tail -n4 expect.2 >expect.3 &&
+	test_config rebase.missingCommitsCheck warn &&
+	rebase_setup_and_clean missing-commit &&
+	(
+		set_fake_editor &&
+		test_must_fail env FAKE_LINES="bad 1 2 3 4 5" \
+			git rebase -i --root &&
+		cp .git/rebase-merge/git-rebase-todo.backup orig &&
+		FAKE_LINES="2 3 4" git rebase --edit-todo 2>actual.2 &&
+		head -n6 actual.2 >actual &&
+		test_i18ncmp expect actual &&
+		cp orig .git/rebase-merge/git-rebase-todo &&
+		FAKE_LINES="1 2 3 4" git rebase --edit-todo 2>actual.2 &&
+		head -n4 actual.2 >actual &&
+		test_i18ncmp expect.3 actual &&
+		git rebase --continue 2>actual
+	) &&
+	test D = $(git cat-file commit HEAD | sed -ne \$p) &&
+	test_i18ngrep \
+		"Successfully rebased and updated refs/heads/missing-commit" \
+		actual
+'
+
+test_expect_success 'rebase --edit-todo respects rebase.missingCommitsCheck = error' '
+	cat >expect <<-EOF &&
+	error: invalid line 1: badcmd $(git rev-list --pretty=oneline --abbrev-commit -1 master~4)
+	Warning: some commits may have been dropped accidentally.
+	Dropped commits (newer to older):
+	 - $(git rev-list --pretty=oneline --abbrev-commit -1 master)
+	 - $(git rev-list --pretty=oneline --abbrev-commit -1 master~4)
+	To avoid this message, use "drop" to explicitly remove a commit.
+
+	Use '\''git config rebase.missingCommitsCheck'\'' to change the level of warnings.
+	The possible behaviours are: ignore, warn, error.
+
+	You can fix this with '\''git rebase --edit-todo'\'' and then run '\''git rebase --continue'\''.
+	Or you can abort the rebase with '\''git rebase --abort'\''.
+	EOF
+	tail -n11 expect >expect.2 &&
+	head -n3 expect.2 >expect.3 &&
+	tail -n7 expect.2 >>expect.3 &&
+	test_config rebase.missingCommitsCheck error &&
+	rebase_setup_and_clean missing-commit &&
+	(
+		set_fake_editor &&
+		test_must_fail env FAKE_LINES="bad 1 2 3 4 5" \
+			git rebase -i --root &&
+		cp .git/rebase-merge/git-rebase-todo.backup orig &&
+		test_must_fail env FAKE_LINES="2 3 4" \
+			git rebase --edit-todo 2>actual &&
+		test_i18ncmp expect actual &&
+		test_must_fail git rebase --continue 2>actual &&
+		test_i18ncmp expect.2 actual &&
+		test_must_fail git rebase --edit-todo &&
+		cp orig .git/rebase-merge/git-rebase-todo &&
+		test_must_fail env FAKE_LINES="1 2 3 4" \
+			git rebase --edit-todo 2>actual &&
+		test_i18ncmp expect.3 actual &&
+		test_must_fail git rebase --continue 2>actual &&
+		test_i18ncmp expect.3 actual &&
+		cp orig .git/rebase-merge/git-rebase-todo &&
+		FAKE_LINES="1 2 3 4 drop 5" git rebase --edit-todo &&
+		git rebase --continue 2>actual
+	) &&
+	test D = $(git cat-file commit HEAD | sed -ne \$p) &&
+	test_i18ngrep \
+		"Successfully rebased and updated refs/heads/missing-commit" \
+		actual
+'
+
+test_expect_success 'rebase.missingCommitsCheck = error after resolving conflicts' '
+	test_config rebase.missingCommitsCheck error &&
+	(
+		set_fake_editor &&
+		FAKE_LINES="drop 1 break 2 3 4" git rebase -i A E
+	) &&
+	git rebase --edit-todo &&
+	test_must_fail git rebase --continue &&
+	echo x >file1 &&
+	git add file1 &&
+	git rebase --continue
+'
+
+test_expect_success 'rebase.missingCommitsCheck = error when editing for a second time' '
+	test_config rebase.missingCommitsCheck error &&
+	(
+		set_fake_editor &&
+		FAKE_LINES="1 break 2 3" git rebase -i A D &&
+		cp .git/rebase-merge/git-rebase-todo todo &&
+		test_must_fail env FAKE_LINES=2 git rebase --edit-todo &&
+		GIT_SEQUENCE_EDITOR="cp todo" git rebase --edit-todo &&
+		git rebase --continue
+	)
+'
+
 test_expect_success 'respects rebase.abbreviateCommands with fixup, squash and exec' '
 	rebase_setup_and_clean abbrevcmd &&
 	test_commit "first" file1.txt "first line" first &&
@@ -1360,21 +1631,27 @@ test_expect_success 'respects rebase.abbreviateCommands with fixup, squash and e
 	x git show HEAD
 	EOF
 	git checkout abbrevcmd &&
-	set_cat_todo_editor &&
 	test_config rebase.abbreviateCommands true &&
-	test_must_fail git rebase -i --exec "git show HEAD" \
-		--autosquash master >actual &&
+	(
+		set_cat_todo_editor &&
+		test_must_fail git rebase -i --exec "git show HEAD" \
+			--autosquash master >actual
+	) &&
 	test_cmp expected actual
 '
 
 test_expect_success 'static check of bad command' '
 	rebase_setup_and_clean bad-cmd &&
-	set_fake_editor &&
-	test_must_fail env FAKE_LINES="1 2 3 bad 4 5" \
+	(
+		set_fake_editor &&
+		test_must_fail env FAKE_LINES="1 2 3 bad 4 5" \
 		git rebase -i --root 2>actual &&
-	test_i18ngrep "badcmd $(git rev-list --oneline -1 master~1)" actual &&
-	test_i18ngrep "You can fix this with .git rebase --edit-todo.." actual &&
-	FAKE_LINES="1 2 3 drop 4 5" git rebase --edit-todo &&
+		test_i18ngrep "badcmd $(git rev-list --oneline -1 master~1)" \
+				actual &&
+		test_i18ngrep "You can fix this with .git rebase --edit-todo.." \
+				actual &&
+		FAKE_LINES="1 2 3 drop 4 5" git rebase --edit-todo
+	) &&
 	git rebase --continue &&
 	test E = $(git cat-file commit HEAD | sed -ne \$p) &&
 	test C = $(git cat-file commit HEAD^ | sed -ne \$p)
@@ -1390,19 +1667,24 @@ test_expect_success 'tabs and spaces are accepted in the todolist' '
 	) >"$1.new"
 	mv "$1.new" "$1"
 	EOF
-	test_set_editor "$(pwd)/add-indent.sh" &&
-	git rebase -i HEAD^^^ &&
+	(
+		test_set_editor "$(pwd)/add-indent.sh" &&
+		git rebase -i HEAD^^^
+	) &&
 	test E = $(git cat-file commit HEAD | sed -ne \$p)
 '
 
 test_expect_success 'static check of bad SHA-1' '
 	rebase_setup_and_clean bad-sha &&
-	set_fake_editor &&
-	test_must_fail env FAKE_LINES="1 2 edit fakesha 3 4 5 #" \
-		git rebase -i --root 2>actual &&
-	test_i18ngrep "edit XXXXXXX False commit" actual &&
-	test_i18ngrep "You can fix this with .git rebase --edit-todo.." actual &&
-	FAKE_LINES="1 2 4 5 6" git rebase --edit-todo &&
+	(
+		set_fake_editor &&
+		test_must_fail env FAKE_LINES="1 2 edit fakesha 3 4 5 #" \
+			git rebase -i --root 2>actual &&
+			test_i18ngrep "edit XXXXXXX False commit" actual &&
+			test_i18ngrep "You can fix this with .git rebase --edit-todo.." \
+					actual &&
+		FAKE_LINES="1 2 4 5 6" git rebase --edit-todo
+	) &&
 	git rebase --continue &&
 	test E = $(git cat-file commit HEAD | sed -ne \$p)
 '
@@ -1419,42 +1701,105 @@ test_expect_success 'editor saves as CR/LF' '
 	)
 '
 
-SQ="'"
 test_expect_success 'rebase -i --gpg-sign=<key-id>' '
 	test_when_finished "test_might_fail git rebase --abort" &&
-	set_fake_editor &&
-	FAKE_LINES="edit 1" git rebase -i --gpg-sign="\"S I Gner\"" HEAD^ \
-		>out 2>err &&
+	(
+		set_fake_editor &&
+		FAKE_LINES="edit 1" git rebase -i --gpg-sign="\"S I Gner\"" \
+			HEAD^ >out 2>err
+	) &&
 	test_i18ngrep "$SQ-S\"S I Gner\"$SQ" err
 '
 
 test_expect_success 'rebase -i --gpg-sign=<key-id> overrides commit.gpgSign' '
 	test_when_finished "test_might_fail git rebase --abort" &&
 	test_config commit.gpgsign true &&
-	set_fake_editor &&
-	FAKE_LINES="edit 1" git rebase -i --gpg-sign="\"S I Gner\"" HEAD^ \
-		>out 2>err &&
+	(
+		set_fake_editor &&
+		FAKE_LINES="edit 1" git rebase -i --gpg-sign="\"S I Gner\"" \
+			HEAD^ >out 2>err
+	) &&
 	test_i18ngrep "$SQ-S\"S I Gner\"$SQ" err
 '
 
 test_expect_success 'valid author header after --root swap' '
 	rebase_setup_and_clean author-header no-conflict-branch &&
-	set_fake_editor &&
 	git commit --amend --author="Au ${SQ}thor <author@example.com>" --no-edit &&
 	git cat-file commit HEAD | grep ^author >expected &&
-	FAKE_LINES="5 1" git rebase -i --root &&
+	(
+		set_fake_editor &&
+		FAKE_LINES="5 1" git rebase -i --root
+	) &&
 	git cat-file commit HEAD^ | grep ^author >actual &&
 	test_cmp expected actual
 '
 
 test_expect_success 'valid author header when author contains single quote' '
 	rebase_setup_and_clean author-header no-conflict-branch &&
-	set_fake_editor &&
 	git commit --amend --author="Au ${SQ}thor <author@example.com>" --no-edit &&
 	git cat-file commit HEAD | grep ^author >expected &&
-	FAKE_LINES="2" git rebase -i HEAD~2 &&
+	(
+		set_fake_editor &&
+		FAKE_LINES="2" git rebase -i HEAD~2
+	) &&
 	git cat-file commit HEAD | grep ^author >actual &&
 	test_cmp expected actual
 '
 
+test_expect_success 'post-commit hook is called' '
+	test_when_finished "rm -f .git/hooks/post-commit" &&
+	>actual &&
+	mkdir -p .git/hooks &&
+	write_script .git/hooks/post-commit <<-\EOS &&
+	git rev-parse HEAD >>actual
+	EOS
+	(
+		set_fake_editor &&
+		FAKE_LINES="edit 4 1 reword 2 fixup 3" git rebase -i A E &&
+		echo x>file3 &&
+		git add file3 &&
+		FAKE_COMMIT_MESSAGE=edited git rebase --continue
+	) &&
+	git rev-parse HEAD@{5} HEAD@{4} HEAD@{3} HEAD@{2} HEAD@{1} HEAD \
+		>expect &&
+	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
+'
+
+test_expect_success 'todo has correct onto hash' '
+	GIT_SEQUENCE_EDITOR=cat git rebase -i no-conflict-branch~4 no-conflict-branch >actual &&
+	onto=$(git rev-parse --short HEAD~4) &&
+	test_i18ngrep "^# Rebase ..* onto $onto" actual
+'
+
+# This must be the last test in this file
+test_expect_success '$EDITOR and friends are unchanged' '
+	test_editor_unchanged
+'
+
 test_done
diff --git a/third_party/git/t/t3406-rebase-message.sh b/third_party/git/t/t3406-rebase-message.sh
index b393e1e9fe..927a4f4a4e 100755
--- a/third_party/git/t/t3406-rebase-message.sh
+++ b/third_party/git/t/t3406-rebase-message.sh
@@ -18,32 +18,29 @@ test_expect_success 'setup' '
 '
 
 test_expect_success 'rebase -m' '
-	git rebase -m master >report &&
-	>expect &&
-	sed -n -e "/^Already applied: /p" \
-		-e "/^Committed: /p" report >actual &&
-	test_cmp expect actual
+	git rebase -m master >actual &&
+	test_must_be_empty actual
 '
 
 test_expect_success 'rebase against master twice' '
-	git rebase master >out &&
+	git rebase --apply master >out &&
 	test_i18ngrep "Current branch topic is up to date" out
 '
 
 test_expect_success 'rebase against master twice with --force' '
-	git rebase --force-rebase master >out &&
+	git rebase --force-rebase --apply master >out &&
 	test_i18ngrep "Current branch topic is up to date, rebase forced" out
 '
 
 test_expect_success 'rebase against master twice from another branch' '
 	git checkout topic^ &&
-	git rebase master topic >out &&
+	git rebase --apply master topic >out &&
 	test_i18ngrep "Current branch topic is up to date" out
 '
 
 test_expect_success 'rebase fast-forward to master' '
 	git checkout topic^ &&
-	git rebase topic >out &&
+	git rebase --apply topic >out &&
 	test_i18ngrep "Fast-forwarded HEAD to topic" out
 '
 
@@ -95,9 +92,9 @@ test_expect_success 'GIT_REFLOG_ACTION' '
 	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 &&
 
@@ -105,9 +102,9 @@ test_expect_success 'GIT_REFLOG_ACTION' '
 	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
 '
diff --git a/third_party/git/t/t3407-rebase-abort.sh b/third_party/git/t/t3407-rebase-abort.sh
index 910f218284..97efea0f56 100755
--- a/third_party/git/t/t3407-rebase-abort.sh
+++ b/third_party/git/t/t3407-rebase-abort.sh
@@ -96,14 +96,14 @@ testrebase() {
 	'
 }
 
-testrebase "" .git/rebase-apply
+testrebase " --apply" .git/rebase-apply
 testrebase " --merge" .git/rebase-merge
 
-test_expect_success 'rebase --quit' '
+test_expect_success 'rebase --apply --quit' '
 	cd "$work_dir" &&
 	# Clean up the state from the previous one
 	git reset --hard pre-rebase &&
-	test_must_fail git rebase master &&
+	test_must_fail git rebase --apply master &&
 	test_path_is_dir .git/rebase-apply &&
 	head_before=$(git rev-parse HEAD) &&
 	git rebase --quit &&
diff --git a/third_party/git/t/t3415-rebase-autosquash.sh b/third_party/git/t/t3415-rebase-autosquash.sh
index 22d218698e..7bab6000dc 100755
--- a/third_party/git/t/t3415-rebase-autosquash.sh
+++ b/third_party/git/t/t3415-rebase-autosquash.sh
@@ -25,6 +25,13 @@ test_expect_success setup '
 '
 
 test_auto_fixup () {
+	no_squash= &&
+	if test "x$1" = 'x!'
+	then
+		no_squash=true
+		shift
+	fi &&
+
 	git reset --hard base &&
 	echo 1 >file1 &&
 	git add -u &&
@@ -35,10 +42,19 @@ test_auto_fixup () {
 	test_tick &&
 	git rebase $2 -i HEAD^^^ &&
 	git log --oneline >actual &&
-	test_line_count = 3 actual &&
-	git diff --exit-code $1 &&
-	test 1 = "$(git cat-file blob HEAD^:file1)" &&
-	test 1 = $(git cat-file commit HEAD^ | grep first | wc -l)
+	if test -n "$no_squash"
+	then
+		test_line_count = 4 actual
+	else
+		test_line_count = 3 actual &&
+		git diff --exit-code $1 &&
+		echo 1 >expect &&
+		git cat-file blob HEAD^:file1 >actual &&
+		test_cmp expect actual &&
+		git cat-file commit HEAD^ >commit &&
+		grep first commit >actual &&
+		test_line_count = 1 actual
+	fi
 }
 
 test_expect_success 'auto fixup (option)' '
@@ -48,12 +64,19 @@ test_expect_success 'auto fixup (option)' '
 test_expect_success 'auto fixup (config)' '
 	git config rebase.autosquash true &&
 	test_auto_fixup final-fixup-config-true &&
-	test_must_fail test_auto_fixup fixup-config-true-no --no-autosquash &&
+	test_auto_fixup ! fixup-config-true-no --no-autosquash &&
 	git config rebase.autosquash false &&
-	test_must_fail test_auto_fixup final-fixup-config-false
+	test_auto_fixup ! final-fixup-config-false
 '
 
 test_auto_squash () {
+	no_squash= &&
+	if test "x$1" = 'x!'
+	then
+		no_squash=true
+		shift
+	fi &&
+
 	git reset --hard base &&
 	echo 1 >file1 &&
 	git add -u &&
@@ -64,10 +87,19 @@ test_auto_squash () {
 	test_tick &&
 	git rebase $2 -i HEAD^^^ &&
 	git log --oneline >actual &&
-	test_line_count = 3 actual &&
-	git diff --exit-code $1 &&
-	test 1 = "$(git cat-file blob HEAD^:file1)" &&
-	test 2 = $(git cat-file commit HEAD^ | grep first | wc -l)
+	if test -n "$no_squash"
+	then
+		test_line_count = 4 actual
+	else
+		test_line_count = 3 actual &&
+		git diff --exit-code $1 &&
+		echo 1 >expect &&
+		git cat-file blob HEAD^:file1 >actual &&
+		test_cmp expect actual &&
+		git cat-file commit HEAD^ >commit &&
+		grep first commit >actual &&
+		test_line_count = 2 actual
+	fi
 }
 
 test_expect_success 'auto squash (option)' '
@@ -77,9 +109,9 @@ test_expect_success 'auto squash (option)' '
 test_expect_success 'auto squash (config)' '
 	git config rebase.autosquash true &&
 	test_auto_squash final-squash-config-true &&
-	test_must_fail test_auto_squash squash-config-true-no --no-autosquash &&
+	test_auto_squash ! squash-config-true-no --no-autosquash &&
 	git config rebase.autosquash false &&
-	test_must_fail test_auto_squash final-squash-config-false
+	test_auto_squash ! final-squash-config-false
 '
 
 test_expect_success 'misspelled auto squash' '
@@ -94,7 +126,8 @@ test_expect_success 'misspelled auto squash' '
 	git log --oneline >actual &&
 	test_line_count = 4 actual &&
 	git diff --exit-code final-missquash &&
-	test 0 = $(git rev-list final-missquash...HEAD | wc -l)
+	git rev-list final-missquash...HEAD >list &&
+	test_must_be_empty list
 '
 
 test_expect_success 'auto squash that matches 2 commits' '
@@ -113,9 +146,15 @@ test_expect_success 'auto squash that matches 2 commits' '
 	git log --oneline >actual &&
 	test_line_count = 4 actual &&
 	git diff --exit-code final-multisquash &&
-	test 1 = "$(git cat-file blob HEAD^^:file1)" &&
-	test 2 = $(git cat-file commit HEAD^^ | grep first | wc -l) &&
-	test 1 = $(git cat-file commit HEAD | grep first | wc -l)
+	echo 1 >expect &&
+	git cat-file blob HEAD^^:file1 >actual &&
+	test_cmp expect actual &&
+	git cat-file commit HEAD^^ >commit &&
+	grep first commit >actual &&
+	test_line_count = 2 actual &&
+	git cat-file commit HEAD >commit &&
+	grep first commit >actual &&
+	test_line_count = 1 actual
 '
 
 test_expect_success 'auto squash that matches a commit after the squash' '
@@ -134,25 +173,38 @@ test_expect_success 'auto squash that matches a commit after the squash' '
 	git log --oneline >actual &&
 	test_line_count = 5 actual &&
 	git diff --exit-code final-presquash &&
-	test 0 = "$(git cat-file blob HEAD^^:file1)" &&
-	test 1 = "$(git cat-file blob HEAD^:file1)" &&
-	test 1 = $(git cat-file commit HEAD | grep third | wc -l) &&
-	test 1 = $(git cat-file commit HEAD^ | grep third | wc -l)
+	echo 0 >expect &&
+	git cat-file blob HEAD^^:file1 >actual &&
+	test_cmp expect actual &&
+	echo 1 >expect &&
+	git cat-file blob HEAD^:file1 >actual &&
+	test_cmp expect actual &&
+	git cat-file commit HEAD >commit &&
+	grep third commit >actual &&
+	test_line_count = 1 actual &&
+	git cat-file commit HEAD^ >commit &&
+	grep third commit >actual &&
+	test_line_count = 1 actual
 '
 test_expect_success 'auto squash that matches a sha1' '
 	git reset --hard base &&
 	echo 1 >file1 &&
 	git add -u &&
 	test_tick &&
-	git commit -m "squash! $(git rev-parse --short HEAD^)" &&
+	oid=$(git rev-parse --short HEAD^) &&
+	git commit -m "squash! $oid" &&
 	git tag final-shasquash &&
 	test_tick &&
 	git rebase --autosquash -i HEAD^^^ &&
 	git log --oneline >actual &&
 	test_line_count = 3 actual &&
 	git diff --exit-code final-shasquash &&
-	test 1 = "$(git cat-file blob HEAD^:file1)" &&
-	test 1 = $(git cat-file commit HEAD^ | grep squash | wc -l)
+	echo 1 >expect &&
+	git cat-file blob HEAD^:file1 >actual &&
+	test_cmp expect actual &&
+	git cat-file commit HEAD^ >commit &&
+	grep squash commit >actual &&
+	test_line_count = 1 actual
 '
 
 test_expect_success 'auto squash that matches longer sha1' '
@@ -160,15 +212,20 @@ test_expect_success 'auto squash that matches longer sha1' '
 	echo 1 >file1 &&
 	git add -u &&
 	test_tick &&
-	git commit -m "squash! $(git rev-parse --short=11 HEAD^)" &&
+	oid=$(git rev-parse --short=11 HEAD^) &&
+	git commit -m "squash! $oid" &&
 	git tag final-longshasquash &&
 	test_tick &&
 	git rebase --autosquash -i HEAD^^^ &&
 	git log --oneline >actual &&
 	test_line_count = 3 actual &&
 	git diff --exit-code final-longshasquash &&
-	test 1 = "$(git cat-file blob HEAD^:file1)" &&
-	test 1 = $(git cat-file commit HEAD^ | grep squash | wc -l)
+	echo 1 >expect &&
+	git cat-file blob HEAD^:file1 >actual &&
+	test_cmp expect actual &&
+	git cat-file commit HEAD^ >commit &&
+	grep squash commit >actual &&
+	test_line_count = 1 actual
 '
 
 test_auto_commit_flags () {
@@ -183,8 +240,12 @@ test_auto_commit_flags () {
 	git log --oneline >actual &&
 	test_line_count = 3 actual &&
 	git diff --exit-code final-commit-$1 &&
-	test 1 = "$(git cat-file blob HEAD^:file1)" &&
-	test $2 = $(git cat-file commit HEAD^ | grep first | wc -l)
+	echo 1 >expect &&
+	git cat-file blob HEAD^:file1 >actual &&
+	test_cmp expect actual &&
+	git cat-file commit HEAD^ >commit &&
+	grep first commit >actual &&
+	test_line_count = $2 actual
 }
 
 test_expect_success 'use commit --fixup' '
@@ -210,11 +271,15 @@ test_auto_fixup_fixup () {
 	(
 		set_cat_todo_editor &&
 		test_must_fail git rebase --autosquash -i HEAD^^^^ >actual &&
+		head=$(git rev-parse --short HEAD) &&
+		parent1=$(git rev-parse --short HEAD^) &&
+		parent2=$(git rev-parse --short HEAD^^) &&
+		parent3=$(git rev-parse --short HEAD^^^) &&
 		cat >expected <<-EOF &&
-		pick $(git rev-parse --short HEAD^^^) first commit
-		$1 $(git rev-parse --short HEAD^) $1! first
-		$1 $(git rev-parse --short HEAD) $1! $2! first
-		pick $(git rev-parse --short HEAD^^) second commit
+		pick $parent3 first commit
+		$1 $parent1 $1! first
+		$1 $head $1! $2! first
+		pick $parent2 second commit
 		EOF
 		test_cmp expected actual
 	) &&
@@ -222,13 +287,17 @@ test_auto_fixup_fixup () {
 	git log --oneline >actual &&
 	test_line_count = 3 actual
 	git diff --exit-code "final-$1-$2" &&
-	test 2 = "$(git cat-file blob HEAD^:file1)" &&
+	echo 2 >expect &&
+	git cat-file blob HEAD^:file1 >actual &&
+	test_cmp expect actual &&
+	git cat-file commit HEAD^ >commit &&
+	grep first commit >actual &&
 	if test "$1" = "fixup"
 	then
-		test 1 = $(git cat-file commit HEAD^ | grep first | wc -l)
+		test_line_count = 1 actual
 	elif test "$1" = "squash"
 	then
-		test 3 = $(git cat-file commit HEAD^ | grep first | wc -l)
+		test_line_count = 3 actual
 	else
 		false
 	fi
@@ -256,19 +325,25 @@ test_expect_success C_LOCALE_OUTPUT 'autosquash with custom inst format' '
 	echo 2 >file1 &&
 	git add -u &&
 	test_tick &&
-	git commit -m "squash! $(git rev-parse --short HEAD^)" &&
+	oid=$(git rev-parse --short HEAD^) &&
+	git commit -m "squash! $oid" &&
 	echo 1 >file1 &&
 	git add -u &&
 	test_tick &&
-	git commit -m "squash! $(git log -n 1 --format=%s HEAD~2)" &&
+	subject=$(git log -n 1 --format=%s HEAD~2) &&
+	git commit -m "squash! $subject" &&
 	git tag final-squash-instFmt &&
 	test_tick &&
 	git rebase --autosquash -i HEAD~4 &&
 	git log --oneline >actual &&
 	test_line_count = 3 actual &&
 	git diff --exit-code final-squash-instFmt &&
-	test 1 = "$(git cat-file blob HEAD^:file1)" &&
-	test 2 = $(git cat-file commit HEAD^ | grep squash | wc -l)
+	echo 1 >expect &&
+	git cat-file blob HEAD^:file1 >actual &&
+	test_cmp expect actual &&
+	git cat-file commit HEAD^ >commit &&
+	grep squash commit >actual &&
+	test_line_count = 2 actual
 '
 
 test_expect_success 'autosquash with empty custom instructionFormat' '
@@ -349,4 +424,20 @@ test_expect_success 'abort last squash' '
 	! grep first actual
 '
 
+test_expect_success 'fixup a fixup' '
+	echo 0to-fixup >file0 &&
+	test_tick &&
+	git commit -m "to-fixup" file0 &&
+	test_tick &&
+	git commit --squash HEAD -m X --allow-empty &&
+	test_tick &&
+	git commit --squash HEAD^ -m Y --allow-empty &&
+	test_tick &&
+	git commit -m "squash! $(git rev-parse HEAD^)" -m Z --allow-empty &&
+	test_tick &&
+	git commit -m "squash! $(git rev-parse HEAD^^)" -m W --allow-empty &&
+	git rebase -ki --autosquash HEAD~5 &&
+	test XZWY = $(git show | tr -cd W-Z)
+'
+
 test_done
diff --git a/third_party/git/t/t3416-rebase-onto-threedots.sh b/third_party/git/t/t3416-rebase-onto-threedots.sh
index ddf2f64853..9c2548423b 100755
--- a/third_party/git/t/t3416-rebase-onto-threedots.sh
+++ b/third_party/git/t/t3416-rebase-onto-threedots.sh
@@ -99,7 +99,64 @@ test_expect_success 'rebase -i --onto master...side' '
 	git checkout side &&
 	git reset --hard K &&
 
+	set_fake_editor &&
 	test_must_fail git rebase -i --onto master...side J
 '
 
+test_expect_success 'rebase --keep-base --onto incompatible' '
+	test_must_fail git rebase --keep-base --onto master...
+'
+
+test_expect_success 'rebase --keep-base --root incompatible' '
+	test_must_fail git rebase --keep-base --root
+'
+
+test_expect_success 'rebase --keep-base master from topic' '
+	git reset --hard &&
+	git checkout topic &&
+	git reset --hard G &&
+
+	git rebase --keep-base master &&
+	git rev-parse C >base.expect &&
+	git merge-base master HEAD >base.actual &&
+	test_cmp base.expect base.actual &&
+
+	git rev-parse HEAD~2 >actual &&
+	git rev-parse C^0 >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success 'rebase --keep-base master from side' '
+	git reset --hard &&
+	git checkout side &&
+	git reset --hard K &&
+
+	test_must_fail git rebase --keep-base master
+'
+
+test_expect_success 'rebase -i --keep-base master from topic' '
+	git reset --hard &&
+	git checkout topic &&
+	git reset --hard G &&
+
+	set_fake_editor &&
+	EXPECT_COUNT=2 git rebase -i --keep-base master &&
+	git rev-parse C >base.expect &&
+	git merge-base master HEAD >base.actual &&
+	test_cmp base.expect base.actual &&
+
+	git rev-parse HEAD~2 >actual &&
+	git rev-parse C^0 >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success 'rebase -i --keep-base master from side' '
+	git reset --hard &&
+	git checkout side &&
+	git reset --hard K &&
+
+	set_fake_editor &&
+	test_must_fail git rebase -i --keep-base master
+'
+
 test_done
diff --git a/third_party/git/t/t3417-rebase-whitespace-fix.sh b/third_party/git/t/t3417-rebase-whitespace-fix.sh
index e85cdc7037..946e92f8da 100755
--- a/third_party/git/t/t3417-rebase-whitespace-fix.sh
+++ b/third_party/git/t/t3417-rebase-whitespace-fix.sh
@@ -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
 '
diff --git a/third_party/git/t/t3418-rebase-continue.sh b/third_party/git/t/t3418-rebase-continue.sh
index 4eff14dae5..7a2da972fd 100755
--- a/third_party/git/t/t3418-rebase-continue.sh
+++ b/third_party/git/t/t3418-rebase-continue.sh
@@ -120,6 +120,20 @@ test_expect_success REBASE_P 'rebase passes merge strategy options correctly' '
 	git rebase --continue
 '
 
+test_expect_success 'rebase -r passes merge strategy options correctly' '
+	rm -fr .git/rebase-* &&
+	git reset --hard commit-new-file-F3-on-topic-branch &&
+	test_commit merge-theirs &&
+	git reset --hard HEAD^ &&
+	test_commit some-other-commit &&
+	test_tick &&
+	git merge --no-ff merge-theirs &&
+	FAKE_LINES="1 3 edit 4 5 7 8 9" git rebase -i -f -r -m \
+		-s recursive --strategy-option=theirs HEAD~2 &&
+	test_commit force-change-ours &&
+	git rebase --continue
+'
+
 test_expect_success '--skip after failed fixup cleans commit message' '
 	test_when_finished "test_might_fail git rebase --abort" &&
 	git checkout -b with-conflicting-fixup &&
diff --git a/third_party/git/t/t3419-rebase-patch-id.sh b/third_party/git/t/t3419-rebase-patch-id.sh
index 49f548cdb9..1f32faa4a4 100755
--- a/third_party/git/t/t3419-rebase-patch-id.sh
+++ b/third_party/git/t/t3419-rebase-patch-id.sh
@@ -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,75 +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 &&
-		test_must_fail test -n "$(git rev-list master...HEAD~)"
-	'
+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 &&
-		rm -rf .git/rebase-apply
-	'
-}
+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
diff --git a/third_party/git/t/t3420-rebase-autostash.sh b/third_party/git/t/t3420-rebase-autostash.sh
index b8f4d03467..ca331733fb 100755
--- a/third_party/git/t/t3420-rebase-autostash.sh
+++ b/third_party/git/t/t3420-rebase-autostash.sh
@@ -34,10 +34,9 @@ test_expect_success setup '
 	remove_progress_re="$(printf "s/.*\\r//")"
 '
 
-create_expected_success_am () {
+create_expected_success_apply () {
 	cat >expected <<-EOF
 	$(grep "^Created autostash: [0-9a-f][0-9a-f]*\$" actual)
-	HEAD is now at $(git rev-parse --short feature-branch) third commit
 	First, rewinding head to replay your work on top of it...
 	Applying: second commit
 	Applying: third commit
@@ -45,19 +44,17 @@ create_expected_success_am () {
 	EOF
 }
 
-create_expected_success_interactive () {
+create_expected_success_merge () {
 	q_to_cr >expected <<-EOF
 	$(grep "^Created autostash: [0-9a-f][0-9a-f]*\$" actual)
-	HEAD is now at $(git rev-parse --short feature-branch) third commit
 	Applied autostash.
 	Successfully rebased and updated refs/heads/rebased-feature-branch.
 	EOF
 }
 
-create_expected_failure_am () {
+create_expected_failure_apply () {
 	cat >expected <<-EOF
 	$(grep "^Created autostash: [0-9a-f][0-9a-f]*\$" actual)
-	HEAD is now at $(git rev-parse --short feature-branch) third commit
 	First, rewinding head to replay your work on top of it...
 	Applying: second commit
 	Applying: third commit
@@ -67,10 +64,9 @@ create_expected_failure_am () {
 	EOF
 }
 
-create_expected_failure_interactive () {
+create_expected_failure_merge () {
 	cat >expected <<-EOF
 	$(grep "^Created autostash: [0-9a-f][0-9a-f]*\$" actual)
-	HEAD is now at $(git rev-parse --short feature-branch) third commit
 	Applying autostash resulted in conflicts.
 	Your changes are safe in the stash.
 	You can run "git stash pop" or "git stash drop" at any time.
@@ -105,9 +101,9 @@ testrebase () {
 
 	test_expect_success "rebase$type --autostash: check output" '
 		test_when_finished git branch -D rebased-feature-branch &&
-		suffix=${type#\ --} && suffix=${suffix:-am} &&
-		if test ${suffix} = "merge"; then
-			suffix=interactive
+		suffix=${type#\ --} && suffix=${suffix:-apply} &&
+		if test ${suffix} = "interactive"; then
+			suffix=merge
 		fi &&
 		create_expected_success_$suffix &&
 		sed "$remove_progress_re" <actual >actual2 &&
@@ -188,6 +184,26 @@ testrebase () {
 		git checkout feature-branch
 	'
 
+	test_expect_success "rebase$type: --quit" '
+		test_config rebase.autostash true &&
+		git reset --hard &&
+		git checkout -b rebased-feature-branch feature-branch &&
+		test_when_finished git branch -D rebased-feature-branch &&
+		echo dirty >>file3 &&
+		git diff >expect &&
+		test_must_fail git rebase$type related-onto-branch &&
+		test_path_is_file $dotest/autostash &&
+		test_path_is_missing file3 &&
+		git rebase --quit &&
+		test_when_finished git stash drop &&
+		test_path_is_missing $dotest/autostash &&
+		! grep dirty file3 &&
+		git stash show -p >actual &&
+		test_cmp expect actual &&
+		git reset --hard &&
+		git checkout feature-branch
+	'
+
 	test_expect_success "rebase$type: non-conflicting rebase, conflicting stash" '
 		test_config rebase.autostash true &&
 		git reset --hard &&
@@ -206,9 +222,9 @@ testrebase () {
 
 	test_expect_success "rebase$type: check output with conflicting stash" '
 		test_when_finished git branch -D rebased-feature-branch &&
-		suffix=${type#\ --} && suffix=${suffix:-am} &&
-		if test ${suffix} = "merge"; then
-			suffix=interactive
+		suffix=${type#\ --} && suffix=${suffix:-apply} &&
+		if test ${suffix} = "interactive"; then
+			suffix=merge
 		fi &&
 		create_expected_failure_$suffix &&
 		sed "$remove_progress_re" <actual >actual2 &&
@@ -238,7 +254,7 @@ test_expect_success "rebase: noop rebase" '
 	git checkout feature-branch
 '
 
-testrebase "" .git/rebase-apply
+testrebase " --apply" .git/rebase-apply
 testrebase " --merge" .git/rebase-merge
 testrebase " --interactive" .git/rebase-merge
 
@@ -306,4 +322,12 @@ test_expect_success 'branch is left alone when possible' '
 	test unchanged-branch = "$(git rev-parse --abbrev-ref HEAD)"
 '
 
+test_expect_success 'never change active branch' '
+	git checkout -b not-the-feature-branch unrelated-onto-branch &&
+	test_when_finished "git reset --hard && git checkout master" &&
+	echo changed >file0 &&
+	git rebase --autostash not-the-feature-branch feature-branch &&
+	test_cmp_rev not-the-feature-branch unrelated-onto-branch
+'
+
 test_done
diff --git a/third_party/git/t/t3421-rebase-topology-linear.sh b/third_party/git/t/t3421-rebase-topology-linear.sh
index 7274dca40b..4a9204b4b6 100755
--- a/third_party/git/t/t3421-rebase-topology-linear.sh
+++ b/third_party/git/t/t3421-rebase-topology-linear.sh
@@ -26,11 +26,21 @@ test_run_rebase () {
 		test_linear_range 'd e' c..
 	"
 }
-test_run_rebase success ''
+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
 
+test_expect_success 'setup branches and remote tracking' '
+	git tag -l >tags &&
+	for tag in $(cat tags)
+	do
+		git branch branch-$tag $tag || return 1
+	done &&
+	git remote add origin "file://$PWD" &&
+	git fetch origin
+'
+
 test_run_rebase () {
 	result=$1
 	shift
@@ -40,7 +50,7 @@ test_run_rebase () {
 		test_cmp_rev e HEAD
 	"
 }
-test_run_rebase success ''
+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
@@ -51,12 +61,13 @@ test_run_rebase () {
 	test_expect_$result "rebase $* -f rewrites even if upstream is an ancestor" "
 		reset_rebase &&
 		git rebase $* -f b e &&
-		! test_cmp_rev e HEAD &&
+		test_cmp_rev ! e HEAD &&
 		test_cmp_rev b HEAD~2 &&
 		test_linear_range 'd e' b..
 	"
 }
-test_run_rebase success ''
+test_run_rebase success --apply
+test_run_rebase success --fork-point
 test_run_rebase success -m
 test_run_rebase success -i
 test_have_prereq !REBASE_P || test_run_rebase failure -p
@@ -64,13 +75,31 @@ test_have_prereq !REBASE_P || test_run_rebase failure -p
 test_run_rebase () {
 	result=$1
 	shift
+	test_expect_$result "rebase $* -f rewrites even if remote upstream is an ancestor" "
+		reset_rebase &&
+		git rebase $* -f branch-b branch-e &&
+		test_cmp_rev ! branch-e origin/branch-e &&
+		test_cmp_rev branch-b HEAD~2 &&
+		test_linear_range 'd e' branch-b..
+	"
+}
+test_run_rebase success --apply
+test_run_rebase success --fork-point
+test_run_rebase success -m
+test_run_rebase success -i
+test_have_prereq !REBASE_P || test_run_rebase success -p
+
+test_run_rebase () {
+	result=$1
+	shift
 	test_expect_$result "rebase $* fast-forwards from ancestor of upstream" "
 		reset_rebase &&
 		git rebase $* e b &&
 		test_cmp_rev e HEAD
 	"
 }
-test_run_rebase success ''
+test_run_rebase success --apply
+test_run_rebase success --fork-point
 test_run_rebase success -m
 test_run_rebase success -i
 test_have_prereq !REBASE_P || test_run_rebase success -p
@@ -110,7 +139,7 @@ test_run_rebase () {
 		test_linear_range 'd i' h..
 	"
 }
-test_run_rebase success ''
+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
@@ -125,7 +154,7 @@ test_run_rebase () {
 		test_linear_range 'd' h..
 	"
 }
-test_run_rebase success ''
+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
@@ -140,7 +169,7 @@ test_run_rebase () {
 		test_linear_range 'd i' f..
 	"
 }
-test_run_rebase success ''
+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
@@ -155,7 +184,7 @@ test_run_rebase () {
 		test_linear_range 'd gp i' h..
 	"
 }
-test_run_rebase success ''
+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
@@ -176,32 +205,31 @@ test_expect_success 'setup of linear history for empty commit tests' '
 test_run_rebase () {
 	result=$1
 	shift
-	test_expect_$result "rebase $* drops empty commit" "
+	test_expect_$result "rebase $* keeps begin-empty commits" "
 		reset_rebase &&
-		git rebase $* c l &&
-		test_cmp_rev c HEAD~2 &&
-		test_linear_range 'd l' c..
+		git rebase $* j l &&
+		test_cmp_rev c HEAD~4 &&
+		test_linear_range 'j d k l' c..
 	"
 }
-test_run_rebase success ''
+test_run_rebase failure --apply
 test_run_rebase success -m
 test_run_rebase success -i
-test_have_prereq !REBASE_P || test_run_rebase success -p
+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 k 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 ''
 test_run_rebase success -m
 test_run_rebase success -i
-test_have_prereq !REBASE_P || test_run_rebase failure -p
+test_have_prereq !REBASE_P || test_run_rebase success -p
 
 test_run_rebase () {
 	result=$1
@@ -213,10 +241,9 @@ test_run_rebase () {
 		test_linear_range 'd k l' j..
 	"
 }
-test_run_rebase success ''
 test_run_rebase success -m
 test_run_rebase success -i
-test_have_prereq !REBASE_P || test_run_rebase failure -p
+test_have_prereq !REBASE_P || test_run_rebase success -p
 test_run_rebase success --rebase-merges
 
 #       m
@@ -253,7 +280,7 @@ test_run_rebase () {
 		test_linear_range 'x y' c..
 	"
 }
-test_run_rebase success ''
+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
@@ -268,7 +295,7 @@ test_run_rebase () {
 		test_linear_range 'x y' c..
 	"
 }
-test_run_rebase success ''
+test_run_rebase success --apply
 test_run_rebase success -m
 test_run_rebase success -i
 test_have_prereq !REBASE_P || test_run_rebase failure -p
@@ -283,7 +310,7 @@ test_run_rebase () {
 		test_linear_range 'x y' m..
 	"
 }
-test_run_rebase success ''
+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
@@ -299,7 +326,7 @@ test_run_rebase () {
 	"
 }
 
-test_run_rebase success ''
+test_run_rebase success --apply
 test_run_rebase success -m
 test_run_rebase success -i
 test_have_prereq !REBASE_P || test_run_rebase failure -p
@@ -314,7 +341,7 @@ test_run_rebase () {
 		test_linear_range 'x y' m..
 	"
 }
-test_run_rebase success ''
+test_run_rebase success --apply
 test_run_rebase success -m
 test_run_rebase success -i
 test_have_prereq !REBASE_P || test_run_rebase failure -p
@@ -339,7 +366,7 @@ test_run_rebase () {
 	test_expect_$result "rebase $* -f --root on linear history causes re-write" "
 		reset_rebase &&
 		git rebase $* -f --root c &&
-		! test_cmp_rev a HEAD~2 &&
+		test_cmp_rev ! a HEAD~2 &&
 		test_linear_range 'a b c' HEAD
 	"
 }
diff --git a/third_party/git/t/t3422-rebase-incompatible-options.sh b/third_party/git/t/t3422-rebase-incompatible-options.sh
index a5868ea152..c8234062c6 100755
--- a/third_party/git/t/t3422-rebase-incompatible-options.sh
+++ b/third_party/git/t/t3422-rebase-incompatible-options.sh
@@ -61,8 +61,6 @@ test_rebase_am_only () {
 }
 
 test_rebase_am_only --whitespace=fix
-test_rebase_am_only --ignore-whitespace
-test_rebase_am_only --committer-date-is-author-date
 test_rebase_am_only -C4
 
 test_expect_success REBASE_P '--preserve-merges incompatible with --signoff' '
@@ -76,14 +74,4 @@ test_expect_success REBASE_P \
 	test_must_fail git rebase --preserve-merges --rebase-merges A
 '
 
-test_expect_success '--rebase-merges incompatible with --strategy' '
-	git checkout B^0 &&
-	test_must_fail git rebase --rebase-merges -s resolve A
-'
-
-test_expect_success '--rebase-merges incompatible with --strategy-option' '
-	git checkout B^0 &&
-	test_must_fail git rebase --rebase-merges -Xignore-space-change A
-'
-
 test_done
diff --git a/third_party/git/t/t3424-rebase-empty.sh b/third_party/git/t/t3424-rebase-empty.sh
new file mode 100755
index 0000000000..5e1045a0af
--- /dev/null
+++ b/third_party/git/t/t3424-rebase-empty.sh
@@ -0,0 +1,170 @@
+#!/bin/sh
+
+test_description='git rebase of commits that start or become empty'
+
+. ./test-lib.sh
+
+test_expect_success 'setup test repository' '
+	test_write_lines 1 2 3 4 5 6 7 8 9 10 >numbers &&
+	test_write_lines A B C D E F G H I J >letters &&
+	git add numbers letters &&
+	git commit -m A &&
+
+	git branch upstream &&
+	git branch localmods &&
+
+	git checkout upstream &&
+	test_write_lines A B C D E >letters &&
+	git add letters &&
+	git commit -m B &&
+
+	test_write_lines 1 2 3 4 five 6 7 8 9 ten >numbers &&
+	git add numbers &&
+	git commit -m C &&
+
+	git checkout localmods &&
+	test_write_lines 1 2 3 4 five 6 7 8 9 10 >numbers &&
+	git add numbers &&
+	git commit -m C2 &&
+
+	git commit --allow-empty -m D &&
+
+	test_write_lines A B C D E >letters &&
+	git add letters &&
+	git commit -m "Five letters ought to be enough for anybody"
+'
+
+test_expect_failure 'rebase (apply-backend)' '
+	test_when_finished "git rebase --abort" &&
+	git checkout -B testing localmods &&
+	# rebase (--apply) should not drop commits that start empty
+	git rebase --apply 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' '
+	git checkout -B testing localmods &&
+	git rebase --merge --empty=drop upstream &&
+
+	test_write_lines D C B A >expect &&
+	git log --format=%s >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'rebase --merge uses default of --empty=drop' '
+	git checkout -B testing localmods &&
+	git rebase --merge upstream &&
+
+	test_write_lines D C B A >expect &&
+	git log --format=%s >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'rebase --merge --empty=keep' '
+	git checkout -B testing localmods &&
+	git rebase --merge --empty=keep 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=ask' '
+	git checkout -B testing localmods &&
+	test_must_fail git rebase --merge --empty=ask upstream &&
+
+	git rebase --skip &&
+
+	test_write_lines D C B A >expect &&
+	git log --format=%s >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'rebase --interactive --empty=drop' '
+	git checkout -B testing localmods &&
+	git rebase --interactive --empty=drop upstream &&
+
+	test_write_lines D C B A >expect &&
+	git log --format=%s >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'rebase --interactive --empty=keep' '
+	git checkout -B testing localmods &&
+	git rebase --interactive --empty=keep upstream &&
+
+	test_write_lines D C2 C B A >expect &&
+	git log --format=%s >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'rebase --interactive --empty=ask' '
+	git checkout -B testing localmods &&
+	test_must_fail git rebase --interactive --empty=ask upstream &&
+
+	git rebase --skip &&
+
+	test_write_lines D C B A >expect &&
+	git log --format=%s >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'rebase --interactive uses default of --empty=ask' '
+	git checkout -B testing localmods &&
+	test_must_fail git rebase --interactive upstream &&
+
+	git rebase --skip &&
+
+	test_write_lines D C B A >expect &&
+	git log --format=%s >actual &&
+	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 &&
+
+	test_path_is_missing .git/CHERRY_PICK_HEAD &&
+	test_path_is_missing .git/MERGE_MSG
+'
+
+test_done
diff --git a/third_party/git/t/t3425-rebase-topology-merges.sh b/third_party/git/t/t3425-rebase-topology-merges.sh
index fd8efe84fe..e42faa44e7 100755
--- a/third_party/git/t/t3425-rebase-topology-merges.sh
+++ b/third_party/git/t/t3425-rebase-topology-merges.sh
@@ -54,7 +54,7 @@ test_run_rebase () {
 		test_linear_range 'n o' e..
 	"
 }
-test_run_rebase success ''
+test_run_rebase success --apply
 test_run_rebase success -m
 test_run_rebase success -i
 
@@ -70,7 +70,7 @@ test_run_rebase () {
 		test_linear_range "\'"$expected"\'" d..
 	"
 }
-test_run_rebase success 'n o e' ''
+test_run_rebase success 'n o e' --apply
 test_run_rebase success 'n o e' -m
 test_run_rebase success 'n o e' -i
 
@@ -86,7 +86,7 @@ test_run_rebase () {
 		test_linear_range "\'"$expected"\'" c..
 	"
 }
-test_run_rebase success 'd n o e' ''
+test_run_rebase success 'd n o e' --apply
 test_run_rebase success 'd n o e' -m
 test_run_rebase success 'd n o e' -i
 
@@ -102,7 +102,7 @@ test_run_rebase () {
 		test_linear_range "\'"$expected"\'" c..
 	"
 }
-test_run_rebase success 'd n o e' ''
+test_run_rebase success 'd n o e' --apply
 test_run_rebase success 'd n o e' -m
 test_run_rebase success 'd n o e' -i
 
diff --git a/third_party/git/t/t3426-rebase-submodule.sh b/third_party/git/t/t3426-rebase-submodule.sh
index a2bba04ba9..0ad3a07bf4 100755
--- a/third_party/git/t/t3426-rebase-submodule.sh
+++ b/third_party/git/t/t3426-rebase-submodule.sh
@@ -17,10 +17,11 @@ git_rebase () {
 	git status -su >actual &&
 	ls -1pR * >>actual &&
 	test_cmp expect actual &&
-	git rebase "$1"
+	may_only_be_test_must_fail "$2" &&
+	$2 git rebase "$1"
 }
 
-test_submodule_switch "git_rebase"
+test_submodule_switch_func "git_rebase"
 
 git_rebase_interactive () {
 	git status -su >expect &&
@@ -35,10 +36,11 @@ git_rebase_interactive () {
 	test_cmp expect actual &&
 	set_fake_editor &&
 	echo "fake-editor.sh" >.git/info/exclude &&
-	git rebase -i "$1"
+	may_only_be_test_must_fail "$2" &&
+	$2 git rebase -i "$1"
 }
 
-test_submodule_switch "git_rebase_interactive"
+test_submodule_switch_func "git_rebase_interactive"
 
 test_expect_success 'rebase interactive ignores modified submodules' '
 	test_when_finished "rm -rf super sub" &&
diff --git a/third_party/git/t/t3427-rebase-subtree.sh b/third_party/git/t/t3427-rebase-subtree.sh
index d8640522a0..2a7d958ea7 100755
--- a/third_party/git/t/t3427-rebase-subtree.sh
+++ b/third_party/git/t/t3427-rebase-subtree.sh
@@ -11,113 +11,99 @@ commit_message() {
 	git log --pretty=format:%s -1 "$1"
 }
 
+# There are a few bugs in the rebase with regards to the subtree strategy, and
+# this test script tries to document them.  First, the following commit history
+# is generated (the onelines are shown, time flows from left to right):
+#
+# topic_1 - topic_2 - topic_3
+#                             \
+# README ---------------------- Add subproject master - topic_4 - files_subtree/topic_5
+#
+# Where the merge moves the files master[123].t into the subdirectory
+# files_subtree/ and topic_4 as well as files_subtree/topic_5 add files to that
+# directory directly.
+#
+# Then, in subsequent test cases, `git filter-branch` is used to distill just
+# the commits that touch files_subtree/. To give it a final pre-rebase touch,
+# an empty commit is added on top. The pre-rebase commit history looks like
+# this:
+#
+# Add subproject master - topic_4 - files_subtree/topic_5 - Empty commit
+#
+# where the root commit adds three files: topic_1.t, topic_2.t and topic_3.t.
+#
+# This commit history is then rebased onto `topic_3` with the
+# `-Xsubtree=files_subtree` option in three different ways:
+#
+# 1. using `--preserve-merges`
+# 2. using `--preserve-merges` and --keep-empty
+# 3. without specifying a rebase backend
+
 test_expect_success 'setup' '
 	test_commit README &&
-	mkdir files &&
-	(
-		cd files &&
-		git init &&
-		test_commit master1 &&
-		test_commit master2 &&
-		test_commit master3
-	) &&
-	git fetch files master &&
-	git branch files-master FETCH_HEAD &&
-	git read-tree --prefix=files_subtree files-master &&
-	git checkout -- files_subtree &&
-	tree=$(git write-tree) &&
-	head=$(git rev-parse HEAD) &&
-	rev=$(git rev-parse --verify files-master^0) &&
-	commit=$(git commit-tree -p $head -p $rev -m "Add subproject master" $tree) &&
-	git update-ref HEAD $commit &&
-	(
-		cd files_subtree &&
-		test_commit master4
-	) &&
-	test_commit files_subtree/master5
-'
 
-# FAILURE: Does not preserve master4.
-test_expect_failure REBASE_P \
-	'Rebase -Xsubtree --preserve-merges --onto commit 4' '
-	reset_rebase &&
-	git checkout -b rebase-preserve-merges-4 master &&
-	git filter-branch --prune-empty -f --subdirectory-filter files_subtree &&
-	git commit -m "Empty commit" --allow-empty &&
-	git rebase -Xsubtree=files_subtree --preserve-merges --onto files-master master &&
-	verbose test "$(commit_message HEAD~)" = "files_subtree/master4"
-'
+	git init files &&
+	test_commit -C files topic_1 &&
+	test_commit -C files topic_2 &&
+	test_commit -C files topic_3 &&
 
-# FAILURE: Does not preserve master5.
-test_expect_failure REBASE_P \
-	'Rebase -Xsubtree --preserve-merges --onto commit 5' '
-	reset_rebase &&
-	git checkout -b rebase-preserve-merges-5 master &&
-	git filter-branch --prune-empty -f --subdirectory-filter files_subtree &&
-	git commit -m "Empty commit" --allow-empty &&
-	git rebase -Xsubtree=files_subtree --preserve-merges --onto files-master master &&
-	verbose test "$(commit_message HEAD)" = "files_subtree/master5"
-'
+	: perform subtree merge into files_subtree/ &&
+	git fetch files refs/heads/master:refs/heads/files-master &&
+	git merge -s ours --no-commit --allow-unrelated-histories \
+		files-master &&
+	git read-tree --prefix=files_subtree -u files-master &&
+	git commit -m "Add subproject master" &&
 
-# FAILURE: Does not preserve master4.
-test_expect_failure REBASE_P \
-	'Rebase -Xsubtree --keep-empty --preserve-merges --onto commit 4' '
-	reset_rebase &&
-	git checkout -b rebase-keep-empty-4 master &&
-	git filter-branch --prune-empty -f --subdirectory-filter files_subtree &&
-	git commit -m "Empty commit" --allow-empty &&
-	git rebase -Xsubtree=files_subtree --keep-empty --preserve-merges --onto files-master master &&
-	verbose test "$(commit_message HEAD~2)" = "files_subtree/master4"
+	: add two extra commits to rebase &&
+	test_commit -C files_subtree topic_4 &&
+	test_commit files_subtree/topic_5 &&
+
+	git checkout -b to-rebase &&
+	git fast-export --no-data HEAD -- files_subtree/ |
+		sed -e "s%\([0-9a-f]\{40\} \)files_subtree/%\1%" |
+		git fast-import --force --quiet &&
+	git reset --hard &&
+	git commit -m "Empty commit" --allow-empty
 '
 
-# FAILURE: Does not preserve master5.
-test_expect_failure REBASE_P \
-	'Rebase -Xsubtree --keep-empty --preserve-merges --onto commit 5' '
+# FAILURE: Does not preserve topic_4.
+test_expect_failure REBASE_P 'Rebase -Xsubtree --preserve-merges --onto commit' '
 	reset_rebase &&
-	git checkout -b rebase-keep-empty-5 master &&
-	git filter-branch --prune-empty -f --subdirectory-filter files_subtree &&
-	git commit -m "Empty commit" --allow-empty &&
-	git rebase -Xsubtree=files_subtree --keep-empty --preserve-merges --onto files-master master &&
-	verbose test "$(commit_message HEAD~)" = "files_subtree/master5"
+	git checkout -b rebase-preserve-merges to-rebase &&
+	git rebase -Xsubtree=files_subtree --preserve-merges --onto files-master master &&
+	verbose test "$(commit_message HEAD~)" = "topic_4" &&
+	verbose test "$(commit_message HEAD)" = "files_subtree/topic_5"
 '
 
-# FAILURE: Does not preserve Empty.
-test_expect_failure REBASE_P \
-	'Rebase -Xsubtree --keep-empty --preserve-merges --onto empty commit' '
+# FAILURE: Does not preserve topic_4.
+test_expect_failure REBASE_P 'Rebase -Xsubtree --keep-empty --preserve-merges --onto commit' '
 	reset_rebase &&
-	git checkout -b rebase-keep-empty-empty master &&
-	git filter-branch --prune-empty -f --subdirectory-filter files_subtree &&
-	git commit -m "Empty commit" --allow-empty &&
+	git checkout -b rebase-keep-empty to-rebase &&
 	git rebase -Xsubtree=files_subtree --keep-empty --preserve-merges --onto files-master master &&
+	verbose test "$(commit_message HEAD~2)" = "topic_4" &&
+	verbose test "$(commit_message HEAD~)" = "files_subtree/topic_5" &&
 	verbose test "$(commit_message HEAD)" = "Empty commit"
 '
 
-# FAILURE: fatal: Could not parse object
-test_expect_failure 'Rebase -Xsubtree --onto commit 4' '
+test_expect_success 'Rebase -Xsubtree --empty=ask --onto commit' '
 	reset_rebase &&
-	git checkout -b rebase-onto-4 master &&
-	git filter-branch --prune-empty -f --subdirectory-filter files_subtree &&
-	git commit -m "Empty commit" --allow-empty &&
-	git rebase -Xsubtree=files_subtree --onto files-master master &&
-	verbose test "$(commit_message HEAD~2)" = "files_subtree/master4"
+	git checkout -b rebase-onto to-rebase &&
+	test_must_fail git rebase -Xsubtree=files_subtree --empty=ask --onto files-master master &&
+	: first pick results in no changes &&
+	git rebase --skip &&
+	verbose test "$(commit_message HEAD~2)" = "topic_4" &&
+	verbose test "$(commit_message HEAD~)" = "files_subtree/topic_5" &&
+	verbose test "$(commit_message HEAD)" = "Empty commit"
 '
 
-# FAILURE: fatal: Could not parse object
-test_expect_failure 'Rebase -Xsubtree --onto commit 5' '
-	reset_rebase &&
-	git checkout -b rebase-onto-5 master &&
-	git filter-branch --prune-empty -f --subdirectory-filter files_subtree &&
-	git commit -m "Empty commit" --allow-empty &&
-	git rebase -Xsubtree=files_subtree --onto files-master master &&
-	verbose test "$(commit_message HEAD~)" = "files_subtree/master5"
-'
-# FAILURE: fatal: Could not parse object
-test_expect_failure 'Rebase -Xsubtree --onto empty commit' '
+test_expect_success 'Rebase -Xsubtree --empty=ask --rebase-merges --onto commit' '
 	reset_rebase &&
-	git checkout -b rebase-onto-empty master &&
-	git filter-branch --prune-empty -f --subdirectory-filter files_subtree &&
-	git commit -m "Empty commit" --allow-empty &&
-	git rebase -Xsubtree=files_subtree --onto files-master master &&
+	git checkout -b rebase-merges-onto to-rebase &&
+	test_must_fail git rebase -Xsubtree=files_subtree --empty=ask --rebase-merges --onto files-master --root &&
+	: first pick results in no changes &&
+	git rebase --skip &&
+	verbose test "$(commit_message HEAD~2)" = "topic_4" &&
+	verbose test "$(commit_message HEAD~)" = "files_subtree/topic_5" &&
 	verbose test "$(commit_message HEAD)" = "Empty commit"
 '
 
diff --git a/third_party/git/t/t3429-rebase-edit-todo.sh b/third_party/git/t/t3429-rebase-edit-todo.sh
index 76f6d306ea..7024d49ae7 100755
--- a/third_party/git/t/t3429-rebase-edit-todo.sh
+++ b/third_party/git/t/t3429-rebase-edit-todo.sh
@@ -3,15 +3,21 @@
 test_description='rebase should reread the todo file if an exec modifies it'
 
 . ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-rebase.sh
+
+test_expect_success 'setup' '
+	test_commit first file &&
+	test_commit second file &&
+	test_commit third file
+'
 
 test_expect_success 'rebase exec modifies rebase-todo' '
-	test_commit initial &&
 	todo=.git/rebase-merge/git-rebase-todo &&
 	git rebase HEAD -x "echo exec touch F >>$todo" &&
 	test -e F
 '
 
-test_expect_success SHA1 'loose object cache vs re-reading todo list' '
+test_expect_success 'loose object cache vs re-reading todo list' '
 	GIT_REBASE_TODO=.git/rebase-merge/git-rebase-todo &&
 	export GIT_REBASE_TODO &&
 	write_script append-todo.sh <<-\EOS &&
@@ -33,4 +39,47 @@ test_expect_success SHA1 'loose object cache vs re-reading todo list' '
 	git rebase HEAD -x "./append-todo.sh 5 6"
 '
 
+test_expect_success 'todo is re-read after reword and squash' '
+	write_script reword-editor.sh <<-\EOS &&
+	GIT_SEQUENCE_EDITOR="echo \"exec echo $(cat file) >>actual\" >>" \
+		git rebase --edit-todo
+	EOS
+
+	test_write_lines first third >expected &&
+	set_fake_editor &&
+	GIT_SEQUENCE_EDITOR="$EDITOR" FAKE_LINES="reword 1 squash 2 fixup 3" \
+		GIT_EDITOR=./reword-editor.sh git rebase -i --root third &&
+	test_cmp expected actual
+'
+
+test_expect_success 're-reading todo doesnt interfere with revert --edit' '
+	git reset --hard third &&
+
+	git revert --edit third second &&
+
+	cat >expect <<-\EOF &&
+	Revert "second"
+	Revert "third"
+	third
+	second
+	first
+	EOF
+	git log --format="%s" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 're-reading todo doesnt interfere with cherry-pick --edit' '
+	git reset --hard first &&
+
+	git cherry-pick --edit second third &&
+
+	cat >expect <<-\EOF &&
+	third
+	second
+	first
+	EOF
+	git log --format="%s" >actual &&
+	test_cmp expect actual
+'
+
 test_done
diff --git a/third_party/git/t/t3430-rebase-merges.sh b/third_party/git/t/t3430-rebase-merges.sh
index 7b6c4847ad..b454f400eb 100755
--- a/third_party/git/t/t3430-rebase-merges.sh
+++ b/third_party/git/t/t3430-rebase-merges.sh
@@ -20,12 +20,11 @@ Initial setup:
 '
 . ./test-lib.sh
 . "$TEST_DIRECTORY"/lib-rebase.sh
+. "$TEST_DIRECTORY"/lib-log-graph.sh
 
 test_cmp_graph () {
 	cat >expect &&
-	git log --graph --boundary --format=%s "$@" >output &&
-	sed "s/ *$//" <output >output.trimmed &&
-	test_cmp expect output.trimmed
+	lib_test_cmp_graph --boundary --format=%s "$@"
 }
 
 test_expect_success 'setup' '
@@ -37,20 +36,27 @@ test_expect_success 'setup' '
 	test_commit A &&
 	git checkout -b first &&
 	test_commit B &&
+	b=$(git rev-parse --short HEAD) &&
 	git checkout master &&
 	test_commit C &&
+	c=$(git rev-parse --short HEAD) &&
 	test_commit D &&
+	d=$(git rev-parse --short HEAD) &&
 	git merge --no-commit B &&
 	test_tick &&
 	git commit -m E &&
 	git tag -m E E &&
+	e=$(git rev-parse --short HEAD) &&
 	git checkout -b second C &&
 	test_commit F &&
+	f=$(git rev-parse --short HEAD) &&
 	test_commit G &&
+	g=$(git rev-parse --short HEAD) &&
 	git checkout master &&
 	git merge --no-commit G &&
 	test_tick &&
 	git commit -m H &&
+	h=$(git rev-parse --short HEAD) &&
 	git tag -m H H &&
 	git checkout A &&
 	test_commit conflicting-G G.t
@@ -93,24 +99,24 @@ test_expect_success 'create completely different structure' '
 '
 
 test_expect_success 'generate correct todo list' '
-	cat >expect <<-\EOF &&
+	cat >expect <<-EOF &&
 	label onto
 
 	reset onto
-	pick d9df450 B
+	pick $b B
 	label E
 
 	reset onto
-	pick 5dee784 C
+	pick $c C
 	label branch-point
-	pick ca2c861 F
-	pick 088b00a G
+	pick $f F
+	pick $g G
 	label H
 
 	reset branch-point # C
-	pick 12bd07b D
-	merge -C 2051b56 E # E
-	merge -C 233d48a H # H
+	pick $d D
+	merge -C $e E # E
+	merge -C $h H # H
 
 	EOF
 
@@ -151,7 +157,6 @@ test_expect_success 'failed `merge -C` writes patch (may be rescheduled, too)' '
 	test_path_is_file .git/rebase-merge/patch
 '
 
-SQ="'"
 test_expect_success 'failed `merge <branch>` does not crash' '
 	test_when_finished "test_might_fail git rebase --abort" &&
 	git checkout conflicting-G &&
@@ -340,7 +345,7 @@ test_expect_success 'A root commit can be a cousin, treat it that way' '
 	git merge --allow-unrelated-histories khnum &&
 	test_tick &&
 	git rebase -f -r HEAD^ &&
-	! test_cmp_rev HEAD^2 khnum &&
+	test_cmp_rev ! HEAD^2 khnum &&
 	test_cmp_graph HEAD^.. <<-\EOF &&
 	*   Merge branch '\''khnum'\'' into asherah
 	|\
@@ -402,7 +407,7 @@ test_expect_success 'octopus merges' '
 	| | * three
 	| * | two
 	| |/
-	* | one
+	* / one
 	|/
 	o before-octopus
 	EOF
@@ -415,7 +420,7 @@ test_expect_success 'with --autosquash and --exec' '
 	git commit --fixup B B.t &&
 	write_script show.sh <<-\EOF &&
 	subject="$(git show -s --format=%s HEAD)"
-	content="$(git diff HEAD^! | tail -n 1)"
+	content="$(git diff HEAD^ HEAD | tail -n 1)"
 	echo "$subject: $content"
 	EOF
 	test_tick &&
@@ -441,4 +446,52 @@ test_expect_success '--continue after resolving conflicts after a merge' '
 	test_path_is_missing .git/MERGE_HEAD
 '
 
+test_expect_success '--rebase-merges with strategies' '
+	git checkout -b with-a-strategy F &&
+	test_tick &&
+	git merge -m "Merge conflicting-G" conflicting-G &&
+
+	: first, test with a merge strategy option &&
+	git rebase -ir -Xtheirs G &&
+	echo conflicting-G >expect &&
+	test_cmp expect G.t &&
+
+	: now, try with a merge strategy other than recursive &&
+	git reset --hard @{1} &&
+	write_script git-merge-override <<-\EOF &&
+	echo overridden$1 >>G.t
+	git add G.t
+	EOF
+	PATH="$PWD:$PATH" git rebase -ir -s override -Xxopt G &&
+	test_write_lines G overridden--xopt >expect &&
+	test_cmp expect G.t
+'
+
+test_expect_success '--rebase-merges with commit that can generate bad characters for filename' '
+	git checkout -b colon-in-label E &&
+	git merge -m "colon: this should work" G &&
+	git rebase --rebase-merges --force-rebase E
+'
+
+test_expect_success '--rebase-merges with message matched with onto label' '
+	git checkout -b onto-label E &&
+	git merge -m onto G &&
+	git rebase --rebase-merges --force-rebase E &&
+	test_cmp_graph <<-\EOF
+	*   onto
+	|\
+	| * G
+	| * F
+	* |   E
+	|\ \
+	| * | B
+	* | | D
+	| |/
+	|/|
+	* | C
+	|/
+	* A
+	EOF
+'
+
 test_done
diff --git a/third_party/git/t/t3431-rebase-fork-point.sh b/third_party/git/t/t3431-rebase-fork-point.sh
new file mode 100755
index 0000000000..172562789e
--- /dev/null
+++ b/third_party/git/t/t3431-rebase-fork-point.sh
@@ -0,0 +1,77 @@
+#!/bin/sh
+#
+# Copyright (c) 2019 Denton Liu
+#
+
+test_description='git rebase --fork-point test'
+
+. ./test-lib.sh
+
+# A---B---D---E    (master)
+#      \
+#       C*---F---G (side)
+#
+# C was formerly part of master but master was rewound to remove C
+#
+test_expect_success setup '
+	test_commit A &&
+	test_commit B &&
+	test_commit C &&
+	git branch -t side &&
+	git reset --hard HEAD^ &&
+	test_commit D &&
+	test_commit E &&
+	git checkout side &&
+	test_commit F &&
+	test_commit G
+'
+
+test_rebase () {
+	expected="$1" &&
+	shift &&
+	test_expect_success "git rebase $*" "
+		git checkout master &&
+		git reset --hard E &&
+		git checkout side &&
+		git reset --hard G &&
+		git rebase $* &&
+		test_write_lines $expected >expect &&
+		git log --pretty=%s >actual &&
+		test_cmp expect actual
+	"
+}
+
+test_rebase 'G F E D B A'
+test_rebase 'G F D B A' --onto D
+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
diff --git a/third_party/git/t/t3432-rebase-fast-forward.sh b/third_party/git/t/t3432-rebase-fast-forward.sh
new file mode 100755
index 0000000000..a29eda87e9
--- /dev/null
+++ b/third_party/git/t/t3432-rebase-fast-forward.sh
@@ -0,0 +1,138 @@
+#!/bin/sh
+#
+# Copyright (c) 2019 Denton Liu
+#
+
+test_description='ensure rebase fast-forwards commits when possible'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+	test_commit A &&
+	test_commit B &&
+	test_commit C &&
+	test_commit D &&
+	git checkout -t -b side
+'
+
+test_rebase_same_head () {
+	status_n="$1" &&
+	shift &&
+	what_n="$1" &&
+	shift &&
+	cmp_n="$1" &&
+	shift &&
+	status_f="$1" &&
+	shift &&
+	what_f="$1" &&
+	shift &&
+	cmp_f="$1" &&
+	shift &&
+	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_ () {
+	status="$1" &&
+	shift &&
+	what="$1" &&
+	shift &&
+	cmp="$1" &&
+	shift &&
+	abbreviate="$1" &&
+	shift &&
+	flag="$1"
+	shift &&
+	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' &&
+		git reflog HEAD >expect &&
+		git rebase$flag $* >stdout &&
+		git reflog HEAD >actual &&
+		if test $what = work
+		then
+			old=\$(wc -l <expect) &&
+			test_line_count '-gt' \$old actual
+		elif test $what = noop
+		then
+			test_cmp expect actual
+		fi &&
+		newhead=\$(git rev-parse HEAD) &&
+		if test $cmp = same
+		then
+			test_cmp_rev \$oldhead \$newhead
+		elif test $cmp = diff
+		then
+			test_cmp_rev ! \$oldhead \$newhead
+		fi
+	"
+}
+
+changes='no changes'
+test_rebase_same_head success noop same success work same
+test_rebase_same_head success noop same success work same master
+test_rebase_same_head success noop same success work diff --onto B B
+test_rebase_same_head success noop same success work diff --onto B... B
+test_rebase_same_head success noop same success work same --onto master... master
+test_rebase_same_head success noop same success work same --keep-base master
+test_rebase_same_head success noop same success work same --keep-base
+test_rebase_same_head success noop same success work same --no-fork-point
+test_rebase_same_head success noop same success work same --keep-base --no-fork-point
+test_rebase_same_head success noop same success work same --fork-point master
+test_rebase_same_head success noop same success work diff --fork-point --onto B B
+test_rebase_same_head success noop same success work diff --fork-point --onto B... B
+test_rebase_same_head success noop same success work same --fork-point --onto master... master
+test_rebase_same_head success noop same success work same --keep-base --keep-base master
+
+test_expect_success 'add work same to side' '
+	test_commit E
+'
+
+changes='our changes'
+test_rebase_same_head success noop same success work same
+test_rebase_same_head success noop same success work same master
+test_rebase_same_head success noop same success work diff --onto B B
+test_rebase_same_head success noop same success work diff --onto B... B
+test_rebase_same_head success noop same success work same --onto master... master
+test_rebase_same_head success noop same success work same --keep-base master
+test_rebase_same_head success noop same success work same --keep-base
+test_rebase_same_head success noop same success work same --no-fork-point
+test_rebase_same_head success noop same success work same --keep-base --no-fork-point
+test_rebase_same_head success noop same success work same --fork-point master
+test_rebase_same_head success noop same success work diff --fork-point --onto B B
+test_rebase_same_head success noop same success work diff --fork-point --onto B... B
+test_rebase_same_head success noop same success work same --fork-point --onto master... master
+test_rebase_same_head success noop same success work same --fork-point --keep-base master
+
+test_expect_success 'add work same to upstream' '
+	git checkout master &&
+	test_commit F &&
+	git checkout side
+'
+
+changes='our and their changes'
+test_rebase_same_head success noop same success work diff --onto B B
+test_rebase_same_head success noop same success work diff --onto B... B
+test_rebase_same_head success noop same success work diff --onto master... master
+test_rebase_same_head success noop same success work diff --keep-base master
+test_rebase_same_head success noop same success work diff --keep-base
+test_rebase_same_head failure work same success work diff --fork-point --onto B B
+test_rebase_same_head failure work same success work diff --fork-point --onto B... B
+test_rebase_same_head success noop same success work diff --fork-point --onto master... master
+test_rebase_same_head success noop same success work diff --fork-point --keep-base master
+
+test_done
diff --git a/third_party/git/t/t3433-rebase-across-mode-change.sh b/third_party/git/t/t3433-rebase-across-mode-change.sh
new file mode 100755
index 0000000000..05df964670
--- /dev/null
+++ b/third_party/git/t/t3433-rebase-across-mode-change.sh
@@ -0,0 +1,48 @@
+#!/bin/sh
+
+test_description='git rebase across mode change'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	mkdir DS &&
+	>DS/whatever &&
+	git add DS &&
+	git commit -m base &&
+
+	git branch side1 &&
+	git branch side2 &&
+
+	git checkout side1 &&
+	git rm -rf DS &&
+	test_ln_s_add unrelated DS &&
+	git commit -m side1 &&
+
+	git checkout side2 &&
+	>unrelated &&
+	git add unrelated &&
+	git commit -m commit1 &&
+
+	echo >>unrelated &&
+	git commit -am commit2
+'
+
+test_expect_success 'rebase changes with the apply backend' '
+	test_when_finished "git rebase --abort || true" &&
+	git checkout -b apply-backend side2 &&
+	git rebase side1
+'
+
+test_expect_success 'rebase changes with the merge backend' '
+	test_when_finished "git rebase --abort || true" &&
+	git checkout -b merge-backend side2 &&
+	git rebase -m side1
+'
+
+test_expect_success 'rebase changes with the merge backend with a delay' '
+	test_when_finished "git rebase --abort || true" &&
+	git checkout -b merge-delay-backend side2 &&
+	git rebase -m --exec "sleep 1" side1
+'
+
+test_done
diff --git a/third_party/git/t/t3434-rebase-i18n.sh b/third_party/git/t/t3434-rebase-i18n.sh
new file mode 100755
index 0000000000..c7c835cde9
--- /dev/null
+++ b/third_party/git/t/t3434-rebase-i18n.sh
@@ -0,0 +1,84 @@
+#!/bin/sh
+#
+# Copyright (c) 2019 Doan Tran Cong Danh
+#
+
+test_description='rebase with changing encoding
+
+Initial setup:
+
+1 - 2              master
+ \
+  3 - 4            first
+   \
+    5 - 6          second
+'
+
+. ./test-lib.sh
+
+compare_msg () {
+	iconv -f "$2" -t "$3" "$TEST_DIRECTORY/t3434/$1" >expect &&
+	git cat-file commit HEAD >raw &&
+	sed "1,/^$/d" raw >actual &&
+	test_cmp expect actual
+}
+
+test_expect_success setup '
+	test_commit one &&
+	git branch first &&
+	test_commit two &&
+	git switch first &&
+	test_commit three &&
+	git branch second &&
+	test_commit four &&
+	git switch second &&
+	test_commit five &&
+	test_commit six
+'
+
+test_expect_success 'rebase --rebase-merges update encoding eucJP to UTF-8' '
+	git switch -c merge-eucJP-UTF-8 first &&
+	git config i18n.commitencoding eucJP &&
+	git merge -F "$TEST_DIRECTORY/t3434/eucJP.txt" second &&
+	git config i18n.commitencoding UTF-8 &&
+	git rebase --rebase-merges master &&
+	compare_msg eucJP.txt eucJP UTF-8
+'
+
+test_expect_success 'rebase --rebase-merges update encoding eucJP to ISO-2022-JP' '
+	git switch -c merge-eucJP-ISO-2022-JP first &&
+	git config i18n.commitencoding eucJP &&
+	git merge -F "$TEST_DIRECTORY/t3434/eucJP.txt" second &&
+	git config i18n.commitencoding ISO-2022-JP &&
+	git rebase --rebase-merges master &&
+	compare_msg eucJP.txt eucJP ISO-2022-JP
+'
+
+test_rebase_continue_update_encode () {
+	old=$1
+	new=$2
+	msgfile=$3
+	test_expect_success "rebase --continue update from $old to $new" '
+		(git rebase --abort || : abort current git-rebase failure) &&
+		git switch -c conflict-$old-$new one &&
+		echo for-conflict >two.t &&
+		git add two.t &&
+		git config i18n.commitencoding $old &&
+		git commit -F "$TEST_DIRECTORY/t3434/$msgfile" &&
+		git config i18n.commitencoding $new &&
+		test_must_fail git rebase -m master &&
+		test -f .git/rebase-merge/message &&
+		git stripspace <.git/rebase-merge/message >two.t &&
+		git add two.t &&
+		git rebase --continue &&
+		compare_msg $msgfile $old $new &&
+		: git-commit assume invalid utf-8 is latin1 &&
+		test_cmp expect two.t
+	'
+}
+
+test_rebase_continue_update_encode ISO-8859-1 UTF-8 ISO8859-1.txt
+test_rebase_continue_update_encode eucJP UTF-8 eucJP.txt
+test_rebase_continue_update_encode eucJP ISO-2022-JP eucJP.txt
+
+test_done
diff --git a/third_party/git/t/t3434/ISO8859-1.txt b/third_party/git/t/t3434/ISO8859-1.txt
new file mode 100644
index 0000000000..7cbef0ee6f
--- /dev/null
+++ b/third_party/git/t/t3434/ISO8859-1.txt
@@ -0,0 +1,3 @@
+ฤหัฯึ
+
+มb็d่fg
diff --git a/third_party/git/t/t3434/eucJP.txt b/third_party/git/t/t3434/eucJP.txt
new file mode 100644
index 0000000000..546f2aac01
--- /dev/null
+++ b/third_party/git/t/t3434/eucJP.txt
@@ -0,0 +1,4 @@
+คฯค์คาคคี
+
+คทคฦคคค๋คฮคฌกขคคค๋คฮควกฃ
+฿ภษอคค์คืค๊คค์คคำคฐค๊คํคุกฃ
diff --git a/third_party/git/t/t3435-rebase-gpg-sign.sh b/third_party/git/t/t3435-rebase-gpg-sign.sh
new file mode 100755
index 0000000000..b47c59c190
--- /dev/null
+++ b/third_party/git/t/t3435-rebase-gpg-sign.sh
@@ -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
diff --git a/third_party/git/t/t3436-rebase-more-options.sh b/third_party/git/t/t3436-rebase-more-options.sh
new file mode 100755
index 0000000000..eaaf4c8d1d
--- /dev/null
+++ b/third_party/git/t/t3436-rebase-more-options.sh
@@ -0,0 +1,180 @@
+#!/bin/sh
+#
+# Copyright (c) 2019 Rohit Ashiwal
+#
+
+test_description='tests to ensure compatibility between am and interactive backends'
+
+. ./test-lib.sh
+
+. "$TEST_DIRECTORY"/lib-rebase.sh
+
+GIT_AUTHOR_DATE="1999-04-02T08:03:20+05:30"
+export GIT_AUTHOR_DATE
+
+# This is a special case in which both am and interactive backends
+# provide the same output. It was done intentionally because
+# both the backends fall short of optimal behaviour.
+test_expect_success 'setup' '
+	git checkout -b topic &&
+	test_write_lines "line 1" "	line 2" "line 3" >file &&
+	git add file &&
+	git commit -m "add file" &&
+
+	test_write_lines "line 1" "new line 2" "line 3" >file &&
+	git commit -am "update file" &&
+	git tag side &&
+	test_commit commit1 foo foo1 &&
+	test_commit commit2 foo foo2 &&
+	test_commit commit3 foo foo3 &&
+
+	git checkout --orphan master &&
+	rm foo &&
+	test_write_lines "line 1" "        line 2" "line 3" >file &&
+	git commit -am "add file" &&
+	git tag main &&
+
+	mkdir test-bin &&
+	write_script test-bin/git-merge-test <<-\EOF
+	exec git merge-recursive "$@"
+	EOF
+'
+
+test_expect_success '--ignore-whitespace works with apply backend' '
+	test_must_fail git rebase --apply main side &&
+	git rebase --abort &&
+	git rebase --apply --ignore-whitespace main side &&
+	git diff --exit-code side
+'
+
+test_expect_success '--ignore-whitespace works with merge backend' '
+	test_must_fail git rebase --merge main side &&
+	git rebase --abort &&
+	git rebase --merge --ignore-whitespace main side &&
+	git diff --exit-code side
+'
+
+test_expect_success '--ignore-whitespace is remembered when continuing' '
+	(
+		set_fake_editor &&
+		FAKE_LINES="break 1" git rebase -i --ignore-whitespace \
+			main side &&
+		git rebase --continue
+	) &&
+	git diff --exit-code side
+'
+
+test_ctime_is_atime () {
+	git log $1 --format="$GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> %ai" >authortime &&
+	git log $1 --format="%cn <%ce> %ci" >committertime &&
+	test_cmp authortime committertime
+}
+
+test_expect_success '--committer-date-is-author-date works with apply backend' '
+	GIT_AUTHOR_DATE="@1234 +0300" git commit --amend --reset-author &&
+	git rebase --apply --committer-date-is-author-date HEAD^ &&
+	test_ctime_is_atime -1
+'
+
+test_expect_success '--committer-date-is-author-date works with merge backend' '
+	GIT_AUTHOR_DATE="@1234 +0300" git commit --amend --reset-author &&
+	git rebase -m --committer-date-is-author-date HEAD^ &&
+	test_ctime_is_atime -1
+'
+
+test_expect_success '--committer-date-is-author-date works with rebase -r' '
+	git checkout side &&
+	GIT_AUTHOR_DATE="@1234 +0300" git merge --no-ff commit3 &&
+	git rebase -r --root --committer-date-is-author-date &&
+	test_ctime_is_atime
+'
+
+test_expect_success '--committer-date-is-author-date works when forking merge' '
+	git checkout side &&
+	GIT_AUTHOR_DATE="@1234 +0300" git merge --no-ff commit3 &&
+	PATH="./test-bin:$PATH" git rebase -r --root --strategy=test \
+					--committer-date-is-author-date &&
+	test_ctime_is_atime
+'
+
+test_expect_success '--committer-date-is-author-date works when committing conflict resolution' '
+	git checkout commit2 &&
+	GIT_AUTHOR_DATE="@1980 +0000" git commit --amend --only --reset-author &&
+	test_must_fail git rebase -m --committer-date-is-author-date \
+		--onto HEAD^^ HEAD^ &&
+	echo resolved > foo &&
+	git add foo &&
+	git rebase --continue &&
+	test_ctime_is_atime -1
+'
+
+# Checking for +0000 in the author date is sufficient since the
+# default timezone is UTC but the timezone used while committing is
+# +0530. The inverted logic in the grep is necessary to check all the
+# author dates in the file.
+test_atime_is_ignored () {
+	git log $1 --format=%ai >authortime &&
+	! grep -v +0000 authortime
+}
+
+test_expect_success '--reset-author-date works with apply backend' '
+	git commit --amend --date="$GIT_AUTHOR_DATE" &&
+	git rebase --apply --reset-author-date HEAD^ &&
+	test_atime_is_ignored -1
+'
+
+test_expect_success '--reset-author-date works with merge backend' '
+	git commit --amend --date="$GIT_AUTHOR_DATE" &&
+	git rebase --reset-author-date -m HEAD^ &&
+	test_atime_is_ignored -1
+'
+
+test_expect_success '--reset-author-date works after conflict resolution' '
+	test_must_fail git rebase --reset-author-date -m \
+		--onto commit2^^ commit2^ commit2 &&
+	echo resolved >foo &&
+	git add foo &&
+	git rebase --continue &&
+	test_atime_is_ignored -1
+'
+
+test_expect_success '--reset-author-date works with rebase -r' '
+	git checkout side &&
+	git merge --no-ff commit3 &&
+	git rebase -r --root --reset-author-date &&
+	test_atime_is_ignored
+'
+
+test_expect_success '--reset-author-date with --committer-date-is-author-date works' '
+	test_must_fail git rebase -m --committer-date-is-author-date \
+		--reset-author-date --onto commit2^^ commit2^ commit3 &&
+	git checkout --theirs foo &&
+	git add foo &&
+	git rebase --continue &&
+	test_ctime_is_atime -2 &&
+	test_atime_is_ignored -2
+'
+
+test_expect_success '--reset-author-date --committer-date-is-author-date works when forking merge' '
+	GIT_SEQUENCE_EDITOR="echo \"merge -C $(git rev-parse HEAD) commit3\">" \
+		PATH="./test-bin:$PATH" git rebase -i --strategy=test \
+				--reset-author-date \
+				--committer-date-is-author-date side side &&
+	test_ctime_is_atime -1 &&
+	test_atime_is_ignored -1
+ '
+
+test_expect_success '--ignore-date is an alias for --reset-author-date' '
+	git commit --amend --date="$GIT_AUTHOR_DATE" &&
+	git rebase --apply --ignore-date HEAD^ &&
+	git commit --allow-empty -m empty --date="$GIT_AUTHOR_DATE" &&
+	git rebase -m --ignore-date HEAD^ &&
+	test_atime_is_ignored -2
+'
+
+# This must be the last test in this file
+test_expect_success '$EDITOR and friends are unchanged' '
+	test_editor_unchanged
+'
+
+test_done
diff --git a/third_party/git/t/t3500-cherry.sh b/third_party/git/t/t3500-cherry.sh
index f038f34b7c..2b8d9cb38e 100755
--- a/third_party/git/t/t3500-cherry.sh
+++ b/third_party/git/t/t3500-cherry.sh
@@ -55,4 +55,27 @@ test_expect_success \
      expr "$(echo $(git cherry master my-topic-branch) )" : "+ [^ ]* - .*"
 '
 
+test_expect_success 'cherry ignores whitespace' '
+	git switch --orphan=upstream-with-space &&
+	test_commit initial file &&
+	>expect &&
+	git switch --create=feature-without-space &&
+
+	# A spaceless file on the feature branch.  Expect a match upstream.
+	printf space >file &&
+	git add file &&
+	git commit -m"file without space" &&
+	git log --format="- %H" -1 >>expect &&
+
+	# A further change.  Should not match upstream.
+	test_commit change file &&
+	git log --format="+ %H" -1 >>expect &&
+
+	git switch upstream-with-space &&
+	# Same as the spaceless file, just with spaces and on upstream.
+	test_commit "file with space" file "s p a c e" file-with-space &&
+	git cherry upstream-with-space feature-without-space >actual &&
+	test_cmp expect actual
+'
+
 test_done
diff --git a/third_party/git/t/t3501-revert-cherry-pick.sh b/third_party/git/t/t3501-revert-cherry-pick.sh
index d1c68af8c5..3669dfb1be 100755
--- a/third_party/git/t/t3501-revert-cherry-pick.sh
+++ b/third_party/git/t/t3501-revert-cherry-pick.sh
@@ -47,7 +47,7 @@ test_expect_success 'cherry-pick --nonsense' '
 	git diff --exit-code HEAD &&
 	test_must_fail git cherry-pick --nonsense 2>msg &&
 	git diff --exit-code HEAD "$pos" &&
-	test_i18ngrep '[Uu]sage:' msg
+	test_i18ngrep "[Uu]sage:" msg
 '
 
 test_expect_success 'revert --nonsense' '
@@ -56,7 +56,7 @@ test_expect_success 'revert --nonsense' '
 	git diff --exit-code HEAD &&
 	test_must_fail git revert --nonsense 2>msg &&
 	git diff --exit-code HEAD "$pos" &&
-	test_i18ngrep '[Uu]sage:' msg
+	test_i18ngrep "[Uu]sage:" msg
 '
 
 test_expect_success 'cherry-pick after renaming branch' '
@@ -106,7 +106,7 @@ test_expect_success 'cherry-pick on unborn branch' '
 	rm -rf * &&
 	git cherry-pick initial &&
 	git diff --quiet initial &&
-	! test_cmp_rev initial HEAD
+	test_cmp_rev ! initial HEAD
 '
 
 test_expect_success 'cherry-pick "-" to pick from previous branch' '
@@ -150,7 +150,7 @@ test_expect_success 'cherry-pick works with dirty renamed file' '
 	test_tick &&
 	git commit -m renamed &&
 	echo modified >renamed &&
-	git cherry-pick refs/heads/unrelated >out &&
+	git cherry-pick refs/heads/unrelated &&
 	test $(git rev-parse :0:renamed) = $(git rev-parse HEAD~2:to-rename.t) &&
 	grep -q "^modified$" renamed
 '
diff --git a/third_party/git/t/t3504-cherry-pick-rerere.sh b/third_party/git/t/t3504-cherry-pick-rerere.sh
index a267b2d144..80a0d08706 100755
--- a/third_party/git/t/t3504-cherry-pick-rerere.sh
+++ b/third_party/git/t/t3504-cherry-pick-rerere.sh
@@ -94,8 +94,10 @@ test_expect_success 'cherry-pick --rerere-autoupdate more than once' '
 
 test_expect_success 'cherry-pick conflict without rerere' '
 	test_config rerere.enabled false &&
-	test_must_fail git cherry-pick master &&
-	test_must_fail test_cmp expect foo
+	test_must_fail git cherry-pick foo-master &&
+	grep ===== foo &&
+	grep foo-dev foo &&
+	grep foo-master foo
 '
 
 test_done
diff --git a/third_party/git/t/t3506-cherry-pick-ff.sh b/third_party/git/t/t3506-cherry-pick-ff.sh
index 127dd0082f..9d5adbc130 100755
--- a/third_party/git/t/t3506-cherry-pick-ff.sh
+++ b/third_party/git/t/t3506-cherry-pick-ff.sh
@@ -16,7 +16,11 @@ test_expect_success setup '
 	git add file1 &&
 	test_tick &&
 	git commit -m "second" &&
-	git tag second
+	git tag second &&
+	test_oid_cache <<-EOF
+	cp_ff sha1:1df192cd8bc58a2b275d842cede4d221ad9000d1
+	cp_ff sha256:e70d6b7fc064bddb516b8d512c9057094b96ce6ff08e12080acc4fe7f1d60a1d
+	EOF
 '
 
 test_expect_success 'cherry-pick using --ff fast forwards' '
@@ -102,7 +106,7 @@ test_expect_success 'cherry pick a root commit with --ff' '
 	git add file2 &&
 	git commit --amend -m "file2" &&
 	git cherry-pick --ff first &&
-	test "$(git rev-parse --verify HEAD)" = "1df192cd8bc58a2b275d842cede4d221ad9000d1"
+	test "$(git rev-parse --verify HEAD)" = "$(test_oid cp_ff)"
 '
 
 test_expect_success 'cherry-pick --ff on unborn branch' '
diff --git a/third_party/git/t/t3507-cherry-pick-conflict.sh b/third_party/git/t/t3507-cherry-pick-conflict.sh
index 9b9b4ca8d4..a21adcf0e4 100755
--- a/third_party/git/t/t3507-cherry-pick-conflict.sh
+++ b/third_party/git/t/t3507-cherry-pick-conflict.sh
@@ -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 &&
 
@@ -168,7 +191,7 @@ test_expect_success 'successful final commit clears cherry-pick state' '
 	echo resolved >foo &&
 	test_path_is_file .git/sequencer/todo &&
 	git commit -a &&
-	test_must_fail test_path_exists .git/sequencer
+	test_path_is_missing .git/sequencer
 '
 
 test_expect_success 'reset after final pick clears cherry-pick state' '
@@ -178,7 +201,7 @@ test_expect_success 'reset after final pick clears cherry-pick state' '
 	echo resolved >foo &&
 	test_path_is_file .git/sequencer/todo &&
 	git reset &&
-	test_must_fail test_path_exists .git/sequencer
+	test_path_is_missing .git/sequencer
 '
 
 test_expect_success 'failed cherry-pick produces dirty index' '
@@ -260,12 +283,12 @@ test_expect_success 'failed cherry-pick describes conflict in work tree' '
 	a
 	=======
 	c
-	>>>>>>> objid picked
+	>>>>>>> objid (picked)
 	EOF
 
 	test_must_fail git cherry-pick picked &&
 
-	sed "s/[a-f0-9]*\.\.\./objid/" foo >actual &&
+	sed "s/[a-f0-9]* (/objid (/" foo >actual &&
 	test_cmp expected actual
 '
 
@@ -275,16 +298,16 @@ test_expect_success 'diff3 -m style' '
 	cat <<-EOF >expected &&
 	<<<<<<< HEAD
 	a
-	||||||| parent of objid picked
+	||||||| parent of objid (picked)
 	b
 	=======
 	c
-	>>>>>>> objid picked
+	>>>>>>> objid (picked)
 	EOF
 
 	test_must_fail git cherry-pick picked &&
 
-	sed "s/[a-f0-9]*\.\.\./objid/" foo >actual &&
+	sed "s/[a-f0-9]* (/objid (/" foo >actual &&
 	test_cmp expected actual
 '
 
@@ -296,7 +319,7 @@ test_expect_success 'revert also handles conflicts sanely' '
 	a
 	=======
 	b
-	>>>>>>> parent of objid picked
+	>>>>>>> parent of objid (picked)
 	EOF
 	{
 		git checkout picked -- foo &&
@@ -322,7 +345,7 @@ test_expect_success 'revert also handles conflicts sanely' '
 	test_must_fail git update-index --refresh -q &&
 	test_must_fail git diff-index --exit-code HEAD &&
 	test_cmp expected-stages actual-stages &&
-	sed "s/[a-f0-9]*\.\.\./objid/" foo >actual &&
+	sed "s/[a-f0-9]* (/objid (/" foo >actual &&
 	test_cmp expected actual
 '
 
@@ -381,23 +404,23 @@ test_expect_success 'failed commit does not clear REVERT_HEAD' '
 '
 
 test_expect_success 'successful final commit clears revert state' '
-       pristine_detach picked-signed &&
+	pristine_detach picked-signed &&
 
-       test_must_fail git revert picked-signed base &&
-       echo resolved >foo &&
-       test_path_is_file .git/sequencer/todo &&
-       git commit -a &&
-       test_must_fail test_path_exists .git/sequencer
+	test_must_fail git revert picked-signed base &&
+	echo resolved >foo &&
+	test_path_is_file .git/sequencer/todo &&
+	git commit -a &&
+	test_path_is_missing .git/sequencer
 '
 
 test_expect_success 'reset after final pick clears revert state' '
-       pristine_detach picked-signed &&
+	pristine_detach picked-signed &&
 
-       test_must_fail git revert picked-signed base &&
-       echo resolved >foo &&
-       test_path_is_file .git/sequencer/todo &&
-       git reset &&
-       test_must_fail test_path_exists .git/sequencer
+	test_must_fail git revert picked-signed base &&
+	echo resolved >foo &&
+	test_path_is_file .git/sequencer/todo &&
+	git reset &&
+	test_path_is_missing .git/sequencer
 '
 
 test_expect_success 'revert conflict, diff3 -m style' '
@@ -406,16 +429,16 @@ test_expect_success 'revert conflict, diff3 -m style' '
 	cat <<-EOF >expected &&
 	<<<<<<< HEAD
 	a
-	||||||| objid picked
+	||||||| objid (picked)
 	c
 	=======
 	b
-	>>>>>>> parent of objid picked
+	>>>>>>> parent of objid (picked)
 	EOF
 
 	test_must_fail git revert picked &&
 
-	sed "s/[a-f0-9]*\.\.\./objid/" foo >actual &&
+	sed "s/[a-f0-9]* (/objid (/" foo >actual &&
 	test_cmp expected actual
 '
 
@@ -489,7 +512,7 @@ test_expect_success 'commit after failed cherry-pick adds -s at the right place'
 	Signed-off-by: C O Mitter <committer@example.com>
 	# Conflicts:
 	EOF
-	grep -e "^# Conflicts:" -e '^Signed-off-by' .git/COMMIT_EDITMSG >actual &&
+	grep -e "^# Conflicts:" -e "^Signed-off-by" .git/COMMIT_EDITMSG >actual &&
 	test_cmp expect actual &&
 
 	cat <<-\EOF >expected &&
@@ -518,7 +541,7 @@ test_expect_success 'commit --amend -s places the sign-off at the right place' '
 	Signed-off-by: C O Mitter <committer@example.com>
 	Conflicts:
 	EOF
-	grep -e "^Conflicts:" -e '^Signed-off-by' .git/COMMIT_EDITMSG >actual &&
+	grep -e "^Conflicts:" -e "^Signed-off-by" .git/COMMIT_EDITMSG >actual &&
 	test_cmp expect actual
 '
 
diff --git a/third_party/git/t/t3508-cherry-pick-many-commits.sh b/third_party/git/t/t3508-cherry-pick-many-commits.sh
index b457333e18..23070a7b73 100755
--- a/third_party/git/t/t3508-cherry-pick-many-commits.sh
+++ b/third_party/git/t/t3508-cherry-pick-many-commits.sh
@@ -5,7 +5,7 @@ test_description='test cherry-picking many commits'
 . ./test-lib.sh
 
 check_head_differs_from() {
-	! test_cmp_rev HEAD "$1"
+	test_cmp_rev ! HEAD "$1"
 }
 
 check_head_equals() {
diff --git a/third_party/git/t/t3510-cherry-pick-sequence.sh b/third_party/git/t/t3510-cherry-pick-sequence.sh
index 793bcc7fe3..5b94fdaa67 100755
--- a/third_party/git/t/t3510-cherry-pick-sequence.sh
+++ b/third_party/git/t/t3510-cherry-pick-sequence.sh
@@ -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/third_party/git/t/t3512-cherry-pick-submodule.sh b/third_party/git/t/t3512-cherry-pick-submodule.sh
index bd78287841..6ece1d8573 100755
--- a/third_party/git/t/t3512-cherry-pick-submodule.sh
+++ b/third_party/git/t/t3512-cherry-pick-submodule.sh
@@ -7,7 +7,7 @@ test_description='cherry-pick can handle submodules'
 
 KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1
 KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES=1
-test_submodule_switch "git cherry-pick"
+test_submodule_switch "cherry-pick"
 
 test_expect_success 'unrelated submodule/file conflict is ignored' '
 	test_create_repo sub &&
diff --git a/third_party/git/t/t3513-revert-submodule.sh b/third_party/git/t/t3513-revert-submodule.sh
index 5e39fcdb66..a759f12cbb 100755
--- a/third_party/git/t/t3513-revert-submodule.sh
+++ b/third_party/git/t/t3513-revert-submodule.sh
@@ -15,7 +15,12 @@ git_revert () {
 	git status -su >expect &&
 	ls -1pR * >>expect &&
 	tar cf "$TRASH_DIRECTORY/tmp.tar" * &&
-	git checkout "$1" &&
+	may_only_be_test_must_fail "$2" &&
+	$2 git checkout "$1" &&
+	if test -n "$2"
+	then
+		return
+	fi &&
 	git revert HEAD &&
 	rm -rf * &&
 	tar xf "$TRASH_DIRECTORY/tmp.tar" &&
@@ -26,6 +31,6 @@ git_revert () {
 }
 
 KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1
-test_submodule_switch "git_revert"
+test_submodule_switch_func "git_revert"
 
 test_done
diff --git a/third_party/git/t/t3514-cherry-pick-revert-gpg.sh b/third_party/git/t/t3514-cherry-pick-revert-gpg.sh
new file mode 100755
index 0000000000..5b2e250eaa
--- /dev/null
+++ b/third_party/git/t/t3514-cherry-pick-revert-gpg.sh
@@ -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
diff --git a/third_party/git/t/t3600-rm.sh b/third_party/git/t/t3600-rm.sh
index 66282a720e..efec8d13b6 100755
--- a/third_party/git/t/t3600-rm.sh
+++ b/third_party/git/t/t3600-rm.sh
@@ -113,9 +113,10 @@ test_expect_success '"rm" command printed' '
 	echo frotz >test-file &&
 	git add test-file &&
 	git commit -m "add file for rm test" &&
-	git rm test-file >rm-output &&
-	test $(grep "^rm " rm-output | wc -l) = 1 &&
-	rm -f test-file rm-output &&
+	git rm test-file >rm-output.raw &&
+	grep "^rm " rm-output.raw >rm-output &&
+	test_line_count = 1 rm-output &&
+	rm -f test-file rm-output.raw rm-output &&
 	git commit -m "remove file from rm test"
 '
 
@@ -243,11 +244,13 @@ test_expect_success 'choking "git rm" should not let it die with cruft' '
 	git reset -q --hard &&
 	test_when_finished "rm -f .git/index.lock && git reset -q --hard" &&
 	i=0 &&
+	hash=$(test_oid deadbeef) &&
 	while test $i -lt 12000
 	do
-		echo "100644 1234567890123456789012345678901234567890 0	some-file-$i"
+		echo "100644 $hash 0	some-file-$i"
 		i=$(( $i + 1 ))
 	done | git update-index --index-info &&
+	# git command is intentionally placed upstream of pipe to induce SIGPIPE
 	git rm -n "some-file-*" | : &&
 	test_path_is_missing .git/index.lock
 '
@@ -301,7 +304,8 @@ EOF
 
 test_expect_success 'rm removes empty submodules from work tree' '
 	mkdir submod &&
-	git update-index --add --cacheinfo 160000 $(git rev-parse HEAD) submod &&
+	hash=$(git rev-parse HEAD) &&
+	git update-index --add --cacheinfo 160000 "$hash" submod &&
 	git config -f .gitmodules submodule.sub.url ./. &&
 	git config -f .gitmodules submodule.sub.path submod &&
 	git submodule init &&
@@ -420,6 +424,13 @@ test_expect_success 'rm will error out on a modified .gitmodules file unless sta
 	git status -s -uno >actual &&
 	test_cmp expect actual
 '
+test_expect_success 'rm will not error out on .gitmodules file with zero stat data' '
+	git reset --hard &&
+	git submodule update &&
+	git read-tree HEAD &&
+	git rm submod &&
+	test_path_is_missing submod
+'
 
 test_expect_success 'rm issues a warning when section is not found in .gitmodules' '
 	git reset --hard &&
@@ -620,7 +631,8 @@ test_expect_success 'setup subsubmodule' '
 	git submodule update &&
 	(
 		cd submod &&
-		git update-index --add --cacheinfo 160000 $(git rev-parse HEAD) subsubmod &&
+		hash=$(git rev-parse HEAD) &&
+		git update-index --add --cacheinfo 160000 "$hash" subsubmod &&
 		git config -f .gitmodules submodule.sub.url ../. &&
 		git config -f .gitmodules submodule.sub.path subsubmod &&
 		git submodule init &&
diff --git a/third_party/git/t/t3601-rm-pathspec-file.sh b/third_party/git/t/t3601-rm-pathspec-file.sh
new file mode 100755
index 0000000000..7de21f8bcf
--- /dev/null
+++ b/third_party/git/t/t3601-rm-pathspec-file.sh
@@ -0,0 +1,79 @@
+#!/bin/sh
+
+test_description='rm --pathspec-from-file'
+
+. ./test-lib.sh
+
+test_tick
+
+test_expect_success setup '
+	echo A >fileA.t &&
+	echo B >fileB.t &&
+	echo C >fileC.t &&
+	echo D >fileD.t &&
+	git add fileA.t fileB.t fileC.t fileD.t &&
+	git commit -m "files" &&
+
+	git tag checkpoint
+'
+
+restore_checkpoint () {
+	git reset --hard checkpoint
+}
+
+verify_expect () {
+	git status --porcelain --untracked-files=no -- fileA.t fileB.t fileC.t fileD.t >actual &&
+	test_cmp expect actual
+}
+
+test_expect_success 'simplest' '
+	restore_checkpoint &&
+
+	cat >expect <<-\EOF &&
+	D  fileA.t
+	EOF
+
+	echo fileA.t | git rm --pathspec-from-file=- &&
+	verify_expect
+'
+
+test_expect_success '--pathspec-file-nul' '
+	restore_checkpoint &&
+
+	cat >expect <<-\EOF &&
+	D  fileA.t
+	D  fileB.t
+	EOF
+
+	printf "fileA.t\0fileB.t\0" | git rm --pathspec-from-file=- --pathspec-file-nul &&
+	verify_expect
+'
+
+test_expect_success 'only touches what was listed' '
+	restore_checkpoint &&
+
+	cat >expect <<-\EOF &&
+	D  fileB.t
+	D  fileC.t
+	EOF
+
+	printf "fileB.t\nfileC.t\n" | git rm --pathspec-from-file=- &&
+	verify_expect
+'
+
+test_expect_success 'error conditions' '
+	restore_checkpoint &&
+	echo fileA.t >list &&
+
+	test_must_fail git rm --pathspec-from-file=list -- fileA.t 2>err &&
+	test_i18ngrep -e "--pathspec-from-file is incompatible with pathspec arguments" err &&
+
+	test_must_fail git rm --pathspec-file-nul 2>err &&
+	test_i18ngrep -e "--pathspec-file-nul requires --pathspec-from-file" err &&
+
+	>empty_list &&
+	test_must_fail git rm --pathspec-from-file=empty_list 2>err &&
+	test_i18ngrep -e "No pathspec was given. Which files should I remove?" err
+'
+
+test_done
diff --git a/third_party/git/t/t3700-add.sh b/third_party/git/t/t3700-add.sh
index c325167b90..b7d4ba608c 100755
--- a/third_party/git/t/t3700-add.sh
+++ b/third_party/git/t/t3700-add.sh
@@ -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
 '
 
@@ -326,7 +326,9 @@ test_expect_success 'git add --dry-run of an existing file output' "
 cat >expect.err <<\EOF
 The following paths are ignored by one of your .gitignore files:
 ignored-file
-Use -f if you really want to add them.
+hint: Use -f if you really want to add them.
+hint: Turn this message off by running
+hint: "git config advice.addIgnoredFile false"
 EOF
 cat >expect.out <<\EOF
 add 'track-this'
diff --git a/third_party/git/t/t3701-add-interactive.sh b/third_party/git/t/t3701-add-interactive.sh
index 69991a3168..ca04fac417 100755
--- a/third_party/git/t/t3701-add-interactive.sh
+++ b/third_party/git/t/t3701-add-interactive.sh
@@ -23,6 +23,26 @@ diff_cmp () {
 	test_cmp "$1.filtered" "$2.filtered"
 }
 
+# This function uses a trick to manipulate the interactive add to use color:
+# the `want_color()` function special-cases the situation where a pager was
+# spawned and Git now wants to output colored text: to detect that situation,
+# the environment variable `GIT_PAGER_IN_USE` is set. However, color is
+# suppressed despite that environment variable if the `TERM` variable
+# indicates a dumb terminal, so we set that variable, too.
+
+force_color () {
+	# The first element of $@ may be a shell function, as a result POSIX
+	# does not guarantee that "one-shot assignment" will not persist after
+	# the function call. Thus, we prevent these variables from escaping
+	# this function's context with this subshell.
+	(
+		GIT_PAGER_IN_USE=true &&
+		TERM=vt100 &&
+		export GIT_PAGER_IN_USE TERM &&
+		"$@"
+	)
+}
+
 test_expect_success 'setup (initial)' '
 	echo content >file &&
 	git add file &&
@@ -57,6 +77,15 @@ test_expect_success 'revert works (initial)' '
 	! grep . output
 '
 
+test_expect_success 'add untracked (multiple)' '
+	test_when_finished "git reset && rm [1-9]" &&
+	touch $(test_seq 9) &&
+	test_write_lines a "2-5 8-" | git add -i -- [1-9] &&
+	test_write_lines 2 3 4 5 8 9 >expected &&
+	git ls-files [1-9] >output &&
+	test_cmp expected output
+'
+
 test_expect_success 'setup (commit)' '
 	echo baseline >file &&
 	git add file &&
@@ -94,7 +123,6 @@ test_expect_success 'revert works (commit)' '
 	grep "unchanged *+3/-0 file" output
 '
 
-
 test_expect_success 'setup expected' '
 	cat >expected <<-\EOF
 	EOF
@@ -263,6 +291,35 @@ test_expect_success FILEMODE 'stage mode and hunk' '
 
 # end of tests disabled when filemode is not usable
 
+test_expect_success 'different prompts for mode change/deleted' '
+	git reset --hard &&
+	>file &&
+	>deleted &&
+	git add --chmod=+x file deleted &&
+	echo changed >file &&
+	rm deleted &&
+	test_write_lines n n n |
+	git -c core.filemode=true add -p >actual &&
+	sed -n "s/^\(([0-9/]*) Stage .*?\).*/\1/p" actual >actual.filtered &&
+	cat >expect <<-\EOF &&
+	(1/1) Stage deletion [y,n,q,a,d,?]?
+	(1/2) Stage mode change [y,n,q,a,d,j,J,g,/,?]?
+	(2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,?]?
+	EOF
+	test_cmp expect actual.filtered
+'
+
+test_expect_success 'correct message when there is nothing to do' '
+	git reset --hard &&
+	git add -p 2>err &&
+	test_i18ngrep "No changes" err &&
+	printf "\\0123" >binary &&
+	git add binary &&
+	printf "\\0abc" >binary &&
+	git add -p 2>err &&
+	test_i18ngrep "Only binary files changed" err
+'
+
 test_expect_success 'setup again' '
 	git reset --hard &&
 	test_chmod +x file &&
@@ -314,7 +371,7 @@ test_expect_success C_LOCALE_OUTPUT 'add first line works' '
 	git commit -am "clear local changes" &&
 	git apply patch &&
 	printf "%s\n" s y y | git add -p file 2>error |
-		sed -n -e "s/^Stage this hunk[^@]*\(@@ .*\)/\1/" \
+		sed -n -e "s/^([1-2]\/[1-2]) Stage this hunk[^@]*\(@@ .*\)/\1/" \
 		       -e "/^[-+@ \\\\]"/p  >output &&
 	test_must_be_empty error &&
 	git diff --cached >diff &&
@@ -364,6 +421,25 @@ test_expect_success 'deleting an empty file' '
 	diff_cmp expected diff
 '
 
+test_expect_success 'adding an empty file' '
+	git init added &&
+	(
+		cd added &&
+		test_commit initial &&
+		>empty &&
+		git add empty &&
+		test_tick &&
+		git commit -m empty &&
+		git tag added-file &&
+		git reset --hard HEAD^ &&
+		test_path_is_missing empty &&
+
+		echo y | git checkout -p added-file -- >actual &&
+		test_path_is_file empty &&
+		test_i18ngrep "Apply addition to index and worktree" actual
+	)
+'
+
 test_expect_success 'split hunk setup' '
 	git reset --hard &&
 	test_write_lines 10 20 30 40 50 60 >test &&
@@ -374,6 +450,36 @@ test_expect_success 'split hunk setup' '
 	test_write_lines 10 15 20 21 22 23 24 30 40 50 60 >test
 '
 
+test_expect_success 'goto hunk' '
+	test_when_finished "git reset" &&
+	tr _ " " >expect <<-EOF &&
+	(2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,?]? + 1:  -1,2 +1,3          +15
+	_ 2:  -2,4 +3,8          +21
+	go to which hunk? @@ -1,2 +1,3 @@
+	_10
+	+15
+	_20
+	(1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]?_
+	EOF
+	test_write_lines s y g 1 | git add -p >actual &&
+	tail -n 7 <actual >actual.trimmed &&
+	test_cmp expect actual.trimmed
+'
+
+test_expect_success 'navigate to hunk via regex' '
+	test_when_finished "git reset" &&
+	tr _ " " >expect <<-EOF &&
+	(2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,?]? @@ -1,2 +1,3 @@
+	_10
+	+15
+	_20
+	(1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]?_
+	EOF
+	test_write_lines s y /1,2 | git add -p >actual &&
+	tail -n 5 <actual >actual.trimmed &&
+	test_cmp expect actual.trimmed
+'
+
 test_expect_success 'split hunk "add -p (edit)"' '
 	# Split, say Edit and do nothing.  Then:
 	#
@@ -403,6 +509,40 @@ test_expect_failure 'split hunk "add -p (no, yes, edit)"' '
 	! grep "^+31" actual
 '
 
+test_expect_success 'split hunk with incomplete line at end' '
+	git reset --hard &&
+	printf "missing LF" >>test &&
+	git add test &&
+	test_write_lines before 10 20 30 40 50 60 70 >test &&
+	git grep --cached missing &&
+	test_write_lines s n y q | git add -p &&
+	test_must_fail git grep --cached missing &&
+	git grep before &&
+	test_must_fail git grep --cached before
+'
+
+test_expect_failure 'edit, adding lines to the first hunk' '
+	test_write_lines 10 11 20 30 40 50 51 60 >test &&
+	git reset &&
+	tr _ " " >patch <<-EOF &&
+	@@ -1,5 +1,6 @@
+	_10
+	+11
+	+12
+	_20
+	+21
+	+22
+	_30
+	EOF
+	# test sequence is s(plit), e(dit), n(o)
+	# q n q q is there to make sure we exit at the end.
+	printf "%s\n" s e n   q n q q |
+	EDITOR=./fake_editor.sh git add -p 2>error &&
+	test_must_be_empty error &&
+	git diff --cached >actual &&
+	grep "^+22" actual
+'
+
 test_expect_success 'patch mode ignores unmerged entries' '
 	git reset --hard &&
 	test_commit conflict &&
@@ -429,35 +569,69 @@ test_expect_success 'patch mode ignores unmerged entries' '
 	diff_cmp expected diff
 '
 
-test_expect_success TTY 'diffs can be colorized' '
+test_expect_success 'index is refreshed after applying patch' '
+	git reset --hard &&
+	echo content >test &&
+	printf y | git add -p &&
+	git diff-files --exit-code
+'
+
+test_expect_success 'diffs can be colorized' '
 	git reset --hard &&
 
 	echo content >test &&
-	printf y | test_terminal git add -p >output 2>&1 &&
+	printf y >y &&
+	force_color git add -p >output 2>&1 <y &&
+	git diff-files --exit-code &&
 
 	# We do not want to depend on the exact coloring scheme
 	# git uses for diffs, so just check that we saw some kind of color.
 	grep "$(printf "\\033")" output
 '
 
-test_expect_success TTY 'diffFilter filters diff' '
+test_expect_success 'colorized diffs respect diff.wsErrorHighlight' '
+	git reset --hard &&
+
+	echo "old " >test &&
+	git add test &&
+	echo "new " >test &&
+
+	printf y >y &&
+	force_color git -c diff.wsErrorHighlight=all add -p >output.raw 2>&1 <y &&
+	test_decode_color <output.raw >output &&
+	grep "old<" output
+'
+
+test_expect_success 'diffFilter filters diff' '
 	git reset --hard &&
 
 	echo content >test &&
 	test_config interactive.diffFilter "sed s/^/foo:/" &&
-	printf y | test_terminal git add -p >output 2>&1 &&
+	printf y >y &&
+	force_color git add -p >output 2>&1 <y &&
 
 	# avoid depending on the exact coloring or content of the prompts,
 	# and just make sure we saw our diff prefixed
 	grep foo:.*content output
 '
 
-test_expect_success TTY 'detect bogus diffFilter output' '
+test_expect_success 'detect bogus diffFilter output' '
 	git reset --hard &&
 
 	echo content >test &&
-	test_config interactive.diffFilter "echo too-short" &&
-	printf y | test_must_fail test_terminal git add -p
+	test_config interactive.diffFilter "sed 1d" &&
+	printf y >y &&
+	force_color test_must_fail git add -p <y
+'
+
+test_expect_success 'diff.algorithm is passed to `git diff-files`' '
+	git reset --hard &&
+
+	>file &&
+	git add file &&
+	echo changed >file &&
+	test_must_fail git -c diff.algorithm=bogus add -p 2>err &&
+	test_i18ngrep "error: option diff-algorithm accepts " err
 '
 
 test_expect_success 'patch-mode via -i prompts for files' '
@@ -642,9 +816,72 @@ 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 >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
 '
+
+# This should be called from a subshell as it sets a temporary editor
+setup_new_file() {
+	write_script new-file-editor.sh <<-\EOF &&
+	sed /^#/d "$1" >patch &&
+	sed /^+c/d patch >"$1"
+	EOF
+	test_set_editor "$(pwd)/new-file-editor.sh" &&
+	test_write_lines a b c d e f >new-file &&
+	test_write_lines a b d e f >new-file-expect &&
+	test_write_lines "@@ -0,0 +1,6 @@" +a +b +c +d +e +f >patch-expect
+}
+
+test_expect_success 'add -N followed by add -p patch editing' '
+	git reset --hard &&
+	(
+		setup_new_file &&
+		git add -N new-file &&
+		test_write_lines e n q | git add -p &&
+		git cat-file blob :new-file >actual &&
+		test_cmp new-file-expect actual &&
+		test_cmp patch-expect patch
+	)
+'
+
+test_expect_success 'checkout -p patch editing of added file' '
+	git reset --hard &&
+	(
+		setup_new_file &&
+		git add new-file &&
+		git commit -m "add new file" &&
+		git rm new-file &&
+		git commit -m "remove new file" &&
+		test_write_lines e n q | git checkout -p HEAD^ &&
+		test_cmp new-file-expect new-file &&
+		test_cmp patch-expect patch
+	)
+'
+
+test_expect_success 'show help from add--helper' '
+	git reset --hard &&
+	cat >expect <<-EOF &&
+
+	<BOLD>*** Commands ***<RESET>
+	  1: <BOLD;BLUE>s<RESET>tatus	  2: <BOLD;BLUE>u<RESET>pdate	  3: <BOLD;BLUE>r<RESET>evert	  4: <BOLD;BLUE>a<RESET>dd untracked
+	  5: <BOLD;BLUE>p<RESET>atch	  6: <BOLD;BLUE>d<RESET>iff	  7: <BOLD;BLUE>q<RESET>uit	  8: <BOLD;BLUE>h<RESET>elp
+	<BOLD;BLUE>What now<RESET>> <BOLD;RED>status        - show paths with changes<RESET>
+	<BOLD;RED>update        - add working tree state to the staged set of changes<RESET>
+	<BOLD;RED>revert        - revert staged set of changes back to the HEAD version<RESET>
+	<BOLD;RED>patch         - pick hunks and update selectively<RESET>
+	<BOLD;RED>diff          - view diff between HEAD and index<RESET>
+	<BOLD;RED>add untracked - add contents of untracked files to the staged set of changes<RESET>
+	<BOLD>*** Commands ***<RESET>
+	  1: <BOLD;BLUE>s<RESET>tatus	  2: <BOLD;BLUE>u<RESET>pdate	  3: <BOLD;BLUE>r<RESET>evert	  4: <BOLD;BLUE>a<RESET>dd untracked
+	  5: <BOLD;BLUE>p<RESET>atch	  6: <BOLD;BLUE>d<RESET>iff	  7: <BOLD;BLUE>q<RESET>uit	  8: <BOLD;BLUE>h<RESET>elp
+	<BOLD;BLUE>What now<RESET>>$SP
+	Bye.
+	EOF
+	test_write_lines h | force_color git add -i >actual.colored &&
+	test_decode_color <actual.colored >actual &&
+	test_i18ncmp expect actual
+'
+
 test_done
diff --git a/third_party/git/t/t3704-add-pathspec-file.sh b/third_party/git/t/t3704-add-pathspec-file.sh
new file mode 100755
index 0000000000..9e35c1fbca
--- /dev/null
+++ b/third_party/git/t/t3704-add-pathspec-file.sh
@@ -0,0 +1,159 @@
+#!/bin/sh
+
+test_description='add --pathspec-from-file'
+
+. ./test-lib.sh
+
+test_tick
+
+test_expect_success setup '
+	test_commit file0 &&
+	echo A >fileA.t &&
+	echo B >fileB.t &&
+	echo C >fileC.t &&
+	echo D >fileD.t
+'
+
+restore_checkpoint () {
+	git reset
+}
+
+verify_expect () {
+	git status --porcelain --untracked-files=no -- fileA.t fileB.t fileC.t fileD.t >actual &&
+	test_cmp expect actual
+}
+
+test_expect_success '--pathspec-from-file from stdin' '
+	restore_checkpoint &&
+
+	echo fileA.t | git add --pathspec-from-file=- &&
+
+	cat >expect <<-\EOF &&
+	A  fileA.t
+	EOF
+	verify_expect
+'
+
+test_expect_success '--pathspec-from-file from file' '
+	restore_checkpoint &&
+
+	echo fileA.t >list &&
+	git add --pathspec-from-file=list &&
+
+	cat >expect <<-\EOF &&
+	A  fileA.t
+	EOF
+	verify_expect
+'
+
+test_expect_success 'NUL delimiters' '
+	restore_checkpoint &&
+
+	printf "fileA.t\0fileB.t\0" | git add --pathspec-from-file=- --pathspec-file-nul &&
+
+	cat >expect <<-\EOF &&
+	A  fileA.t
+	A  fileB.t
+	EOF
+	verify_expect
+'
+
+test_expect_success 'LF delimiters' '
+	restore_checkpoint &&
+
+	printf "fileA.t\nfileB.t\n" | git add --pathspec-from-file=- &&
+
+	cat >expect <<-\EOF &&
+	A  fileA.t
+	A  fileB.t
+	EOF
+	verify_expect
+'
+
+test_expect_success 'no trailing delimiter' '
+	restore_checkpoint &&
+
+	printf "fileA.t\nfileB.t" | git add --pathspec-from-file=- &&
+
+	cat >expect <<-\EOF &&
+	A  fileA.t
+	A  fileB.t
+	EOF
+	verify_expect
+'
+
+test_expect_success 'CRLF delimiters' '
+	restore_checkpoint &&
+
+	printf "fileA.t\r\nfileB.t\r\n" | git add --pathspec-from-file=- &&
+
+	cat >expect <<-\EOF &&
+	A  fileA.t
+	A  fileB.t
+	EOF
+	verify_expect
+'
+
+test_expect_success 'quotes' '
+	restore_checkpoint &&
+
+	cat >list <<-\EOF &&
+	"file\101.t"
+	EOF
+
+	git add --pathspec-from-file=list &&
+
+	cat >expect <<-\EOF &&
+	A  fileA.t
+	EOF
+	verify_expect
+'
+
+test_expect_success 'quotes not compatible with --pathspec-file-nul' '
+	restore_checkpoint &&
+
+	cat >list <<-\EOF &&
+	"file\101.t"
+	EOF
+
+	test_must_fail git add --pathspec-from-file=list --pathspec-file-nul
+'
+
+test_expect_success 'only touches what was listed' '
+	restore_checkpoint &&
+
+	printf "fileB.t\nfileC.t\n" | git add --pathspec-from-file=- &&
+
+	cat >expect <<-\EOF &&
+	A  fileB.t
+	A  fileC.t
+	EOF
+	verify_expect
+'
+
+test_expect_success 'error conditions' '
+	restore_checkpoint &&
+	echo fileA.t >list &&
+	>empty_list &&
+
+	test_must_fail git add --pathspec-from-file=list --interactive 2>err &&
+	test_i18ngrep -e "--pathspec-from-file is incompatible with --interactive/--patch" err &&
+
+	test_must_fail git add --pathspec-from-file=list --patch 2>err &&
+	test_i18ngrep -e "--pathspec-from-file is incompatible with --interactive/--patch" err &&
+
+	test_must_fail git add --pathspec-from-file=list --edit 2>err &&
+	test_i18ngrep -e "--pathspec-from-file is incompatible with --edit" err &&
+
+	test_must_fail git add --pathspec-from-file=list -- fileA.t 2>err &&
+	test_i18ngrep -e "--pathspec-from-file is incompatible with pathspec arguments" err &&
+
+	test_must_fail git add --pathspec-file-nul 2>err &&
+	test_i18ngrep -e "--pathspec-file-nul requires --pathspec-from-file" err &&
+
+	# This case succeeds, but still prints to stderr
+	git add --pathspec-from-file=empty_list 2>err &&
+	test_i18ngrep -e "Nothing specified, nothing added." err
+'
+
+test_done
diff --git a/third_party/git/t/t3800-mktag.sh b/third_party/git/t/t3800-mktag.sh
index 8eb47942e2..d696aa4e52 100755
--- a/third_party/git/t/t3800-mktag.sh
+++ b/third_party/git/t/t3800-mktag.sh
@@ -69,28 +69,28 @@ check_verify_failure '"object" line SHA1 check' '^error: char7: .*SHA1 hash$'
 #  4. type line label check
 
 cat >tag.sig <<EOF
-object 779e9b33986b1c2670fff52c5067603117b3e895
+object $head
 xxxx tag
 tag mytag
 tagger . <> 0 +0000
 
 EOF
 
-check_verify_failure '"type" line label check' '^error: char47: .*"\\ntype "$'
+check_verify_failure '"type" line label check' '^error: char.*: .*"\\ntype "$'
 
 ############################################################
 #  5. type line eol check
 
-echo "object 779e9b33986b1c2670fff52c5067603117b3e895" >tag.sig
+echo "object $head" >tag.sig
 printf "type tagsssssssssssssssssssssssssssssss" >>tag.sig
 
-check_verify_failure '"type" line eol check' '^error: char48: .*"\\n"$'
+check_verify_failure '"type" line eol check' '^error: char.*: .*"\\n"$'
 
 ############################################################
 #  6. tag line label check #1
 
 cat >tag.sig <<EOF
-object 779e9b33986b1c2670fff52c5067603117b3e895
+object $head
 type tag
 xxx mytag
 tagger . <> 0 +0000
@@ -98,37 +98,37 @@ tagger . <> 0 +0000
 EOF
 
 check_verify_failure '"tag" line label check #1' \
-	'^error: char57: no "tag " found$'
+	'^error: char.*: no "tag " found$'
 
 ############################################################
 #  7. tag line label check #2
 
 cat >tag.sig <<EOF
-object 779e9b33986b1c2670fff52c5067603117b3e895
+object $head
 type taggggggggggggggggggggggggggggggg
 tag
 EOF
 
 check_verify_failure '"tag" line label check #2' \
-	'^error: char87: no "tag " found$'
+	'^error: char.*: no "tag " found$'
 
 ############################################################
 #  8. type line type-name length check
 
 cat >tag.sig <<EOF
-object 779e9b33986b1c2670fff52c5067603117b3e895
+object $head
 type taggggggggggggggggggggggggggggggg
 tag mytag
 EOF
 
 check_verify_failure '"type" line type-name length check' \
-	'^error: char53: type too long$'
+	'^error: char.*: type too long$'
 
 ############################################################
 #  9. verify object (SHA1/type) check
 
 cat >tag.sig <<EOF
-object 779e9b33986b1c2670fff52c5067603117b3e895
+object $(test_oid deadbeef)
 type tagggg
 tag mytag
 tagger . <> 0 +0000
@@ -150,7 +150,7 @@ tagger . <> 0 +0000
 EOF
 
 check_verify_failure 'verify tag-name check' \
-	'^error: char67: could not verify tag name$'
+	'^error: char.*: could not verify tag name$'
 
 ############################################################
 # 11. tagger line label check #1
@@ -164,7 +164,7 @@ This is filler
 EOF
 
 check_verify_failure '"tagger" line label check #1' \
-	'^error: char70: could not find "tagger "$'
+	'^error: char.*: could not find "tagger "$'
 
 ############################################################
 # 12. tagger line label check #2
@@ -179,7 +179,7 @@ This is filler
 EOF
 
 check_verify_failure '"tagger" line label check #2' \
-	'^error: char70: could not find "tagger "$'
+	'^error: char.*: could not find "tagger "$'
 
 ############################################################
 # 13. disallow missing tag author name
@@ -194,7 +194,7 @@ This is filler
 EOF
 
 check_verify_failure 'disallow missing tag author name' \
-	'^error: char77: missing tagger name$'
+	'^error: char.*: missing tagger name$'
 
 ############################################################
 # 14. disallow missing tag author name
@@ -209,7 +209,7 @@ tagger T A Gger <
 EOF
 
 check_verify_failure 'disallow malformed tagger' \
-	'^error: char77: malformed tagger field$'
+	'^error: char.*: malformed tagger field$'
 
 ############################################################
 # 15. allow empty tag email
@@ -238,7 +238,7 @@ tagger T A Gger <tag ger@example.com> 0 +0000
 EOF
 
 check_verify_failure 'disallow spaces in tag email' \
-	'^error: char77: malformed tagger field$'
+	'^error: char.*: malformed tagger field$'
 
 ############################################################
 # 17. disallow missing tag timestamp
@@ -252,7 +252,7 @@ tagger T A Gger <tagger@example.com>__
 EOF
 
 check_verify_failure 'disallow missing tag timestamp' \
-	'^error: char107: missing tag timestamp$'
+	'^error: char.*: missing tag timestamp$'
 
 ############################################################
 # 18. detect invalid tag timestamp1
@@ -266,7 +266,7 @@ tagger T A Gger <tagger@example.com> Tue Mar 25 15:47:44 2008
 EOF
 
 check_verify_failure 'detect invalid tag timestamp1' \
-	'^error: char107: missing tag timestamp$'
+	'^error: char.*: missing tag timestamp$'
 
 ############################################################
 # 19. detect invalid tag timestamp2
@@ -280,7 +280,7 @@ tagger T A Gger <tagger@example.com> 2008-03-31T12:20:15-0500
 EOF
 
 check_verify_failure 'detect invalid tag timestamp2' \
-	'^error: char111: malformed tag timestamp$'
+	'^error: char.*: malformed tag timestamp$'
 
 ############################################################
 # 20. detect invalid tag timezone1
@@ -294,7 +294,7 @@ tagger T A Gger <tagger@example.com> 1206478233 GMT
 EOF
 
 check_verify_failure 'detect invalid tag timezone1' \
-	'^error: char118: malformed tag timezone$'
+	'^error: char.*: malformed tag timezone$'
 
 ############################################################
 # 21. detect invalid tag timezone2
@@ -308,7 +308,7 @@ tagger T A Gger <tagger@example.com> 1206478233 +  30
 EOF
 
 check_verify_failure 'detect invalid tag timezone2' \
-	'^error: char118: malformed tag timezone$'
+	'^error: char.*: malformed tag timezone$'
 
 ############################################################
 # 22. detect invalid tag timezone3
@@ -322,7 +322,7 @@ tagger T A Gger <tagger@example.com> 1206478233 -1430
 EOF
 
 check_verify_failure 'detect invalid tag timezone3' \
-	'^error: char118: malformed tag timezone$'
+	'^error: char.*: malformed tag timezone$'
 
 ############################################################
 # 23. detect invalid header entry
@@ -337,7 +337,7 @@ this line should not be here
 EOF
 
 check_verify_failure 'detect invalid header entry' \
-	'^error: char124: trailing garbage in tag header$'
+	'^error: char.*: trailing garbage in tag header$'
 
 ############################################################
 # 24. create valid tag
diff --git a/third_party/git/t/t3900-i18n-commit.sh b/third_party/git/t/t3900-i18n-commit.sh
index b92ff95977..d277a9f4b7 100755
--- a/third_party/git/t/t3900-i18n-commit.sh
+++ b/third_party/git/t/t3900-i18n-commit.sh
@@ -204,4 +204,41 @@ test_commit_autosquash_flags eucJP fixup
 
 test_commit_autosquash_flags ISO-2022-JP squash
 
+test_commit_autosquash_multi_encoding () {
+	flag=$1
+	old=$2
+	new=$3
+	msg=$4
+	test_expect_success "commit --$flag into $old from $new" '
+		git checkout -b $flag-$old-$new C0 &&
+		git config i18n.commitencoding $old &&
+		echo $old >>F &&
+		git commit -a -F "$TEST_DIRECTORY"/t3900/$msg &&
+		test_tick &&
+		echo intermediate stuff >>G &&
+		git add G &&
+		git commit -a -m "intermediate commit" &&
+		test_tick &&
+		git config i18n.commitencoding $new &&
+		echo $new-$flag >>F &&
+		git commit -a --$flag HEAD^ &&
+		git rebase --autosquash -i HEAD^^^ &&
+		git rev-list HEAD >actual &&
+		test_line_count = 3 actual &&
+		iconv -f $old -t UTF-8 "$TEST_DIRECTORY"/t3900/$msg >expect &&
+		if test $flag = squash; then
+			subject="$(head -1 expect)" &&
+			printf "\nsquash! %s\n" "$subject" >>expect
+		fi &&
+		git cat-file commit HEAD^ >raw &&
+		(sed "1,/^$/d" raw | iconv -f $new -t utf-8) >actual &&
+		test_cmp expect actual
+	'
+}
+
+test_commit_autosquash_multi_encoding fixup UTF-8 ISO-8859-1 1-UTF-8.txt
+test_commit_autosquash_multi_encoding squash ISO-8859-1 UTF-8 ISO8859-1.txt
+test_commit_autosquash_multi_encoding squash eucJP ISO-2022-JP eucJP.txt
+test_commit_autosquash_multi_encoding fixup ISO-2022-JP UTF-8 ISO-2022-JP.txt
+
 test_done
diff --git a/third_party/git/t/t3903-stash.sh b/third_party/git/t/t3903-stash.sh
index b8e337893f..9f7ca98967 100755
--- a/third_party/git/t/t3903-stash.sh
+++ b/third_party/git/t/t3903-stash.sh
@@ -7,6 +7,18 @@ test_description='Test git stash'
 
 . ./test-lib.sh
 
+diff_cmp () {
+	for i in "$1" "$2"
+	do
+		sed -e 's/^index 0000000\.\.[0-9a-f]*/index 0000000..1234567/' \
+		-e 's/^index [0-9a-f]*\.\.[0-9a-f]*/index 1234567..89abcde/' \
+		-e 's/^index [0-9a-f]*,[0-9a-f]*\.\.[0-9a-f]*/index 1234567,7654321..89abcde/' \
+		"$i" >"$i.compare" || return 1
+	done &&
+	test_cmp "$1.compare" "$2.compare" &&
+	rm -f "$1.compare" "$2.compare"
+}
+
 test_expect_success 'stash some dirty working directory' '
 	echo 1 >file &&
 	git add file &&
@@ -36,7 +48,7 @@ EOF
 test_expect_success 'parents of stash' '
 	test $(git rev-parse stash^) = $(git rev-parse HEAD) &&
 	git diff stash^2..stash >output &&
-	test_cmp expect output
+	diff_cmp expect output
 '
 
 test_expect_success 'applying bogus stash does nothing' '
@@ -210,13 +222,13 @@ test_expect_success 'stash branch' '
 	test refs/heads/stashbranch = $(git symbolic-ref HEAD) &&
 	test $(git rev-parse HEAD) = $(git rev-parse master^) &&
 	git diff --cached >output &&
-	test_cmp expect output &&
+	diff_cmp expect output &&
 	git diff >output &&
-	test_cmp expect1 output &&
+	diff_cmp expect1 output &&
 	git add file &&
 	git commit -m alternate\ second &&
 	git diff master..stashbranch >output &&
-	test_cmp output expect2 &&
+	diff_cmp output expect2 &&
 	test 0 = $(git stash list | wc -l)
 '
 
@@ -232,8 +244,11 @@ test_expect_success 'save -q is quiet' '
 	test_must_be_empty output.out
 '
 
-test_expect_success 'pop -q is quiet' '
+test_expect_success 'pop -q works and is quiet' '
 	git stash pop -q >output.out 2>&1 &&
+	echo bar >expect &&
+	git show :file >actual &&
+	test_cmp expect actual &&
 	test_must_be_empty output.out
 '
 
@@ -242,6 +257,8 @@ test_expect_success 'pop -q --index works and is quiet' '
 	git add file &&
 	git stash save --quiet &&
 	git stash pop -q --index >output.out 2>&1 &&
+	git diff-files file2 >file2.diff &&
+	test_must_be_empty file2.diff &&
 	test foo = "$(git show :file)" &&
 	test_must_be_empty output.out
 '
@@ -268,6 +285,11 @@ test_expect_success 'stash --no-keep-index' '
 	test bar,bar2 = $(cat file),$(cat file2)
 '
 
+test_expect_success 'dont assume push with non-option args' '
+	test_must_fail git stash -q drop 2>err &&
+	test_i18ngrep -e "subcommand wasn'\''t specified; '\''push'\'' can'\''t be assumed due to unexpected token '\''drop'\''" err
+'
+
 test_expect_success 'stash --invalid-option' '
 	echo bar5 >file &&
 	echo bar6 >file2 &&
@@ -577,7 +599,7 @@ test_expect_success 'stash show -p - stashes on stack, stash-like argument' '
 	+bar
 	EOF
 	git stash show -p ${STASH_ID} >actual &&
-	test_cmp expected actual
+	diff_cmp expected actual
 '
 
 test_expect_success 'stash show - no stashes on stack, stash-like argument' '
@@ -609,7 +631,7 @@ test_expect_success 'stash show -p - no stashes on stack, stash-like argument' '
 	+foo
 	EOF
 	git stash show -p ${STASH_ID} >actual &&
-	test_cmp expected actual
+	diff_cmp expected actual
 '
 
 test_expect_success 'stash show --patience shows diff' '
@@ -627,7 +649,7 @@ test_expect_success 'stash show --patience shows diff' '
 	+foo
 	EOF
 	git stash show --patience ${STASH_ID} >actual &&
-	test_cmp expected actual
+	diff_cmp expected actual
 '
 
 test_expect_success 'drop: fail early if specified stash is not a stash ref' '
@@ -791,7 +813,7 @@ test_expect_success 'stash where working directory contains "HEAD" file' '
 	git diff-index --cached --quiet HEAD &&
 	test "$(git rev-parse stash^)" = "$(git rev-parse HEAD)" &&
 	git diff stash^..stash >output &&
-	test_cmp expect output
+	diff_cmp expect output
 '
 
 test_expect_success 'store called with invalid commit' '
@@ -847,7 +869,7 @@ test_expect_success 'stash list implies --first-parent -m' '
 	+working
 	EOF
 	git stash list --format=%gd -p >actual &&
-	test_cmp expect actual
+	diff_cmp expect actual
 '
 
 test_expect_success 'stash list --cc shows combined diff' '
@@ -864,7 +886,7 @@ test_expect_success 'stash list --cc shows combined diff' '
 	++working
 	EOF
 	git stash list --format=%gd -p --cc >actual &&
-	test_cmp expect actual
+	diff_cmp expect actual
 '
 
 test_expect_success 'stash is not confused by partial renames' '
@@ -1241,4 +1263,45 @@ test_expect_success 'stash --keep-index with file deleted in index does not resu
 	test_path_is_missing to-remove
 '
 
+test_expect_success 'stash apply should succeed with unmodified file' '
+	echo base >file &&
+	git add file &&
+	git commit -m base &&
+
+	# now stash a modification
+	echo modified >file &&
+	git stash &&
+
+	# make the file stat dirty
+	cp file other &&
+	mv other file &&
+
+	git stash apply
+'
+
+test_expect_success 'stash handles skip-worktree entries nicely' '
+	test_commit A &&
+	echo changed >A.t &&
+	git add A.t &&
+	git update-index --skip-worktree A.t &&
+	rm A.t &&
+	git stash &&
+
+	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
diff --git a/third_party/git/t/t3904-stash-patch.sh b/third_party/git/t/t3904-stash-patch.sh
index 9546b6f8a4..accfe3845c 100755
--- a/third_party/git/t/t3904-stash-patch.sh
+++ b/third_party/git/t/t3904-stash-patch.sh
@@ -89,7 +89,7 @@ test_expect_success 'none of this moved HEAD' '
 	verify_saved_head
 '
 
-test_expect_failure 'stash -p with split hunk' '
+test_expect_success 'stash -p with split hunk' '
 	git reset --hard &&
 	cat >test <<-\EOF &&
 	aaa
@@ -106,8 +106,8 @@ test_expect_failure 'stash -p with split hunk' '
 	ccc
 	EOF
 	printf "%s\n" s n y q |
-	test_might_fail git stash -p 2>error &&
-	! test_must_be_empty error &&
+	git stash -p 2>error &&
+	test_must_be_empty error &&
 	grep "added line 1" test &&
 	! grep "added line 2" test
 '
diff --git a/third_party/git/t/t3905-stash-include-untracked.sh b/third_party/git/t/t3905-stash-include-untracked.sh
index 29ca76f2fb..f075c7f1f3 100755
--- a/third_party/git/t/t3905-stash-include-untracked.sh
+++ b/third_party/git/t/t3905-stash-include-untracked.sh
@@ -277,8 +277,8 @@ test_expect_success 'stash -u -- <ignored> leaves ignored file alone' '
 	test_path_is_file ignored.d/bar
 '
 
-test_expect_success 'stash -u -- <non-existant> shows no changes when there are none' '
-	git stash push -u -- non-existant >actual &&
+test_expect_success 'stash -u -- <non-existent> shows no changes when there are none' '
+	git stash push -u -- non-existent >actual &&
 	echo "No local changes to save" >expect &&
 	test_i18ncmp expect actual
 '
diff --git a/third_party/git/t/t3906-stash-submodule.sh b/third_party/git/t/t3906-stash-submodule.sh
index d7219d6f8f..a52e53dd2d 100755
--- a/third_party/git/t/t3906-stash-submodule.sh
+++ b/third_party/git/t/t3906-stash-submodule.sh
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-test_description='stash apply can handle submodules'
+test_description='stash can handle submodules'
 
 . ./test-lib.sh
 . "$TEST_DIRECTORY"/lib-submodule-update.sh
@@ -8,7 +8,12 @@ test_description='stash apply can handle submodules'
 git_stash () {
 	git status -su >expect &&
 	ls -1pR * >>expect &&
-	git read-tree -u -m "$1" &&
+	may_only_be_test_must_fail "$2" &&
+	$2 git read-tree -u -m "$1" &&
+	if test -n "$2"
+	then
+		return
+	fi &&
 	git stash &&
 	git status -su >actual &&
 	ls -1pR * >>actual &&
@@ -19,6 +24,46 @@ git_stash () {
 KNOWN_FAILURE_STASH_DOES_IGNORE_SUBMODULE_CHANGES=1
 KNOWN_FAILURE_CHERRY_PICK_SEES_EMPTY_COMMIT=1
 KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1
-test_submodule_switch "git_stash"
+test_submodule_switch_func "git_stash"
+
+setup_basic () {
+	test_when_finished "rm -rf main sub" &&
+	git init sub &&
+	(
+		cd sub &&
+		test_commit sub_file
+	) &&
+	git init main &&
+	(
+		cd main &&
+		git submodule add ../sub &&
+		test_commit main_file
+	)
+}
+
+test_expect_success 'stash push with submodule.recurse=true preserves dirty submodule worktree' '
+	setup_basic &&
+	(
+		cd main &&
+		git config submodule.recurse true &&
+		echo "x" >main_file.t &&
+		echo "y" >sub/sub_file.t &&
+		git stash push &&
+		test_must_fail git -C sub diff --quiet
+	)
+'
+
+test_expect_success 'stash push and pop with submodule.recurse=true preserves dirty submodule worktree' '
+	setup_basic &&
+	(
+		cd main &&
+		git config submodule.recurse true &&
+		echo "x" >main_file.t &&
+		echo "y" >sub/sub_file.t &&
+		git stash push &&
+		git stash pop &&
+		test_must_fail git -C sub diff --quiet
+	)
+'
 
 test_done
diff --git a/third_party/git/t/t3908-stash-in-worktree.sh b/third_party/git/t/t3908-stash-in-worktree.sh
new file mode 100755
index 0000000000..2b2b366ef9
--- /dev/null
+++ b/third_party/git/t/t3908-stash-in-worktree.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+#
+# Copyright (c) 2019 Johannes E Schindelin
+#
+
+test_description='Test git stash in a worktree'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	test_commit initial &&
+	git worktree add wt &&
+	test_commit -C wt in-worktree
+'
+
+test_expect_success 'apply in subdirectory' '
+	mkdir wt/subdir &&
+	(
+		cd wt/subdir &&
+		echo modified >../initial.t &&
+		git stash &&
+		git stash apply >out
+	) &&
+	grep "\.\.\/initial\.t" wt/subdir/out
+'
+
+test_done
diff --git a/third_party/git/t/t3909-stash-pathspec-file.sh b/third_party/git/t/t3909-stash-pathspec-file.sh
new file mode 100755
index 0000000000..55e050cfd4
--- /dev/null
+++ b/third_party/git/t/t3909-stash-pathspec-file.sh
@@ -0,0 +1,100 @@
+#!/bin/sh
+
+test_description='stash --pathspec-from-file'
+
+. ./test-lib.sh
+
+test_tick
+
+test_expect_success setup '
+	>fileA.t &&
+	>fileB.t &&
+	>fileC.t &&
+	>fileD.t &&
+	git add fileA.t fileB.t fileC.t fileD.t &&
+	git commit -m "Files" &&
+
+	git tag checkpoint
+'
+
+restore_checkpoint () {
+	git reset --hard checkpoint
+}
+
+verify_expect () {
+	git stash show --name-status >actual &&
+	test_cmp expect actual
+}
+
+test_expect_success 'simplest' '
+	restore_checkpoint &&
+
+	# More files are written to make sure that git didnt ignore
+	# --pathspec-from-file, stashing everything
+	echo A >fileA.t &&
+	echo B >fileB.t &&
+	echo C >fileC.t &&
+	echo D >fileD.t &&
+
+	cat >expect <<-\EOF &&
+	M	fileA.t
+	EOF
+
+	echo fileA.t | git stash push --pathspec-from-file=- &&
+	verify_expect
+'
+
+test_expect_success '--pathspec-file-nul' '
+	restore_checkpoint &&
+
+	# More files are written to make sure that git didnt ignore
+	# --pathspec-from-file, stashing everything
+	echo A >fileA.t &&
+	echo B >fileB.t &&
+	echo C >fileC.t &&
+	echo D >fileD.t &&
+
+	cat >expect <<-\EOF &&
+	M	fileA.t
+	M	fileB.t
+	EOF
+
+	printf "fileA.t\0fileB.t\0" | git stash push --pathspec-from-file=- --pathspec-file-nul &&
+	verify_expect
+'
+
+test_expect_success 'only touches what was listed' '
+	restore_checkpoint &&
+
+	# More files are written to make sure that git didnt ignore
+	# --pathspec-from-file, stashing everything
+	echo A >fileA.t &&
+	echo B >fileB.t &&
+	echo C >fileC.t &&
+	echo D >fileD.t &&
+
+	cat >expect <<-\EOF &&
+	M	fileB.t
+	M	fileC.t
+	EOF
+
+	printf "fileB.t\nfileC.t\n" | git stash push --pathspec-from-file=- &&
+	verify_expect
+'
+
+test_expect_success 'error conditions' '
+	restore_checkpoint &&
+	echo A >fileA.t &&
+	echo fileA.t >list &&
+
+	test_must_fail git stash push --pathspec-from-file=list --patch 2>err &&
+	test_i18ngrep -e "--pathspec-from-file is incompatible with --patch" err &&
+
+	test_must_fail git stash push --pathspec-from-file=list -- fileA.t 2>err &&
+	test_i18ngrep -e "--pathspec-from-file is incompatible with pathspec arguments" err &&
+
+	test_must_fail git stash push --pathspec-file-nul 2>err &&
+	test_i18ngrep -e "--pathspec-file-nul requires --pathspec-from-file" err
+'
+
+test_done
diff --git a/third_party/git/t/t4000-diff-format.sh b/third_party/git/t/t4000-diff-format.sh
index 8de36b7d12..e5116a76a1 100755
--- a/third_party/git/t/t4000-diff-format.sh
+++ b/third_party/git/t/t4000-diff-format.sh
@@ -78,7 +78,7 @@ test_expect_success 'git diff-files --no-patch --patch shows the patch' '
 
 test_expect_success 'git diff-files --no-patch --patch-with-raw shows the patch and raw data' '
 	git diff-files --no-patch --patch-with-raw >actual &&
-	grep -q "^:100644 100755 .* 0000000000000000000000000000000000000000 M	path0\$" actual &&
+	grep -q "^:100644 100755 .* $ZERO_OID M	path0\$" actual &&
 	tail -n +4 actual >actual-patch &&
 	compare_diff_patch expected actual-patch
 '
diff --git a/third_party/git/t/t4002-diff-basic.sh b/third_party/git/t/t4002-diff-basic.sh
index 3a6c21e825..6a9f010197 100755
--- a/third_party/git/t/t4002-diff-basic.sh
+++ b/third_party/git/t/t4002-diff-basic.sh
@@ -7,123 +7,270 @@ test_description='Test diff raw-output.
 
 '
 . ./test-lib.sh
+
 . "$TEST_DIRECTORY"/lib-read-tree-m-3way.sh
 
-cat >.test-plain-OA <<\EOF
-:000000 100644 0000000000000000000000000000000000000000 ccba72ad3888a3520b39efcf780b9ee64167535d A	AA
-:000000 100644 0000000000000000000000000000000000000000 7e426fb079479fd67f6d81f984e4ec649a44bc25 A	AN
-:100644 000000 bcc68ef997017466d5c9094bcf7692295f588c9a 0000000000000000000000000000000000000000 D	DD
-:000000 040000 0000000000000000000000000000000000000000 6d50f65d3bdab91c63444294d38f08aeff328e42 A	DF
-:100644 000000 141c1f1642328e4bc46a7d801a71da392e66791e 0000000000000000000000000000000000000000 D	DM
-:100644 000000 35abde1506ddf806572ff4d407bd06885d0f8ee9 0000000000000000000000000000000000000000 D	DN
-:000000 100644 0000000000000000000000000000000000000000 1d41122ebdd7a640f29d3c9cc4f9d70094374762 A	LL
-:100644 100644 03f24c8c4700babccfd28b654e7e8eac402ad6cd 103d9f89b50b9aad03054b579be5e7aa665f2d57 M	MD
-:100644 100644 b258508afb7ceb449981bd9d63d2d3e971bf8d34 b431b272d829ff3aa4d1a5085f4394ab4d3305b6 M	MM
-:100644 100644 bd084b0c27c7b6cc34f11d6d0509a29be3caf970 a716d58de4a570e0038f5c307bd8db34daea021f M	MN
-:100644 100644 40c959f984c8b89a2b02520d17f00d717f024397 2ac547ae9614a00d1b28275de608131f7a0e259f M	SS
-:100644 100644 4ac13458899ab908ef3b1128fa378daefc88d356 4c86f9a85fbc5e6804ee2e17a797538fbe785bca M	TT
-:040000 040000 7d670fdcdb9929f6c7dac196ff78689cd1c566a1 5e5f22072bb39f6e12cf663a57cb634c76eefb49 M	Z
+test_oid_cache <<\EOF
+aa_1 sha1:ccba72ad3888a3520b39efcf780b9ee64167535d
+aa_1 sha256:9febfbf18197819b2735c45291f138525d2476d59470f98239647544586ba403
+
+aa_2 sha1:6aa2b5335b16431a0ef71e5c0a28be69183cf6a2
+aa_2 sha256:6eaa3437de83f145a4aaa6ba355303075ade547b128ec6a2cd00a81ff7ce7a56
+
+an_1 sha1:7e426fb079479fd67f6d81f984e4ec649a44bc25
+an_1 sha256:8f92a0bec99e399a38e3bd0e1bf19fbf121e0160efb29b857df79d439f1c4536
+
+dd_1 sha1:bcc68ef997017466d5c9094bcf7692295f588c9a
+dd_1 sha256:07e17428b00639b85485d2b01083d219e2f3e3ba8579e9ca44e9cc8dd554d952
+
+df_1 sha1:6d50f65d3bdab91c63444294d38f08aeff328e42
+df_1 sha256:e367cecc27e9bf5451b1c65828cb21938d36a5f8e39c1b03ad6509cc36bb8e9d
+
+df_2 sha1:71420ab81e254145d26d6fc0cddee64c1acd4787
+df_2 sha256:0f0a86d10347ff6921d03a3c954679f3f1d14fa3d5cd82f57b32c09755f3a47d
+
+dfd1 sha1:68a6d8b91da11045cf4aa3a5ab9f2a781c701249
+dfd1 sha256:f3bd3265b02b6978ce86490d8ad026c573639c974b3de1d9faf30d8d5a77d3d5
+
+dm_1 sha1:141c1f1642328e4bc46a7d801a71da392e66791e
+dm_1 sha256:c89f8656e7b94e21ee5fbaf0e2149bbf783c51edbe2ce110349cac13059ee7ed
+
+dm_2 sha1:3c4d8de5fbad08572bab8e10eef8dbb264cf0231
+dm_2 sha256:83a572e37e0c94086294dae2cecc43d9131afd6f6c906e495c78972230b54988
+
+dn_1 sha1:35abde1506ddf806572ff4d407bd06885d0f8ee9
+dn_1 sha256:775d5852582070e620be63327bfa515fab8f71c7ac3e4f0c3cd6267b4377ba28
+
+ll_2 sha1:1d41122ebdd7a640f29d3c9cc4f9d70094374762
+ll_2 sha256:7917b4948a883cfed0a77d3d5a625dc8577d6ddcc3c6c3bbc56c4d4226a2246d
+
+md_1 sha1:03f24c8c4700babccfd28b654e7e8eac402ad6cd
+md_1 sha256:fc9f30369b978595ad685ba11ca9a17de0af16d79cd4b629975f4f1590033902
+
+md_2 sha1:103d9f89b50b9aad03054b579be5e7aa665f2d57
+md_2 sha256:fc78ec75275628762fe520479a6b2398dec295ce7aabcb1d15e5963c7b4e9317
+
+mm_1 sha1:b258508afb7ceb449981bd9d63d2d3e971bf8d34
+mm_1 sha256:a4b7847d228e900e3000285e240c20fd96f9dd41ce1445305f6eada126d4a04a
+
+mm_2 sha1:b431b272d829ff3aa4d1a5085f4394ab4d3305b6
+mm_2 sha256:3f8b83ea36aacf689bcf1a1290a9a8ed341564d32682ea6f76fea9a979186782
+
+mm_3 sha1:19989d4559aae417fedee240ccf2ba315ea4dc2b
+mm_3 sha256:71b3bfc5747ac033fff9ea0ab39ee453a3af2969890e75d6ef547b87544e2681
+
+mn_1 sha1:bd084b0c27c7b6cc34f11d6d0509a29be3caf970
+mn_1 sha256:47a67450583d7a329eb01a7c4ba644945af72c0ed2c7c95eb5a00d6e46d4d483
+
+mn_2 sha1:a716d58de4a570e0038f5c307bd8db34daea021f
+mn_2 sha256:f95104c1ebe27acb84bac25a7be98c71f6b8d3054b21f357a5be0c524ad97e08
+
+nm_1 sha1:c8f25781e8f1792e3e40b74225e20553041b5226
+nm_1 sha256:09baddc7afaa62e62e152c23c9c3ab94bf15a3894031e227e9be7fe68e1f4e49
+
+nm_2 sha1:cdb9a8c3da571502ac30225e9c17beccb8387983
+nm_2 sha256:58b5227956ac2d2a08d0efa513c0ae37430948b16791ea3869a1308dbf05536d
+
+na_1 sha1:15885881ea69115351c09b38371f0348a3fb8c67
+na_1 sha256:18e4fdd1670cd7968ee23d35bfd29e5418d56fb190c840094c1c57ceee0aad8f
+
+nd_1 sha1:a4e179e4291e5536a5e1c82e091052772d2c5a93
+nd_1 sha256:07dac9b01d00956ea0c65bd993d7de4864aeef2ed3cbb1255d9f1d949fcd6df6
+
+ss_1 sha1:40c959f984c8b89a2b02520d17f00d717f024397
+ss_1 sha256:50fc1b5df74d9910db2f9270993484235f15b69b75b01bcfb53e059289d14af9
+
+ss_2 sha1:2ac547ae9614a00d1b28275de608131f7a0e259f
+ss_2 sha256:a90f02e6044f1497d13db587d22ab12f90150a7d1e084afcf96065fab35ae2bc
+
+tt_1 sha1:4ac13458899ab908ef3b1128fa378daefc88d356
+tt_1 sha256:c53113c7dd5060e86b5b251428bd058f6726f66273c6a24bff1c61a04f498dd3
+
+tt_2 sha1:4c86f9a85fbc5e6804ee2e17a797538fbe785bca
+tt_2 sha256:0775f2a296129a7cf2862b46bc0e88c14d593f2773a3e3fb1c5193db6f5a7e77
+
+tt_3 sha1:c4e4a12231b9fa79a0053cb6077fcb21bb5b135a
+tt_3 sha256:47860f93cdd211f96443e0560f21c57ab6c2f4b0ac27ff03651a352e53fe8484
+
+z__1 sha1:7d670fdcdb9929f6c7dac196ff78689cd1c566a1
+z__1 sha256:44d0f37aff5e51cfcfdd1134c93a6419bcca7b9964f792ffcd5f9b4fcba1ee63
+
+z__2 sha1:5e5f22072bb39f6e12cf663a57cb634c76eefb49
+z__2 sha256:d29de162113190fed104eb5f010820cef4e315f89b9326e8497f7219fb737894
+
+z__3 sha1:1ba523955d5160681af65cb776411f574c1e8155
+z__3 sha256:07422d772b07794ab4369a5648e617719f89c2d2212cbeab05d97214b6471636
+
+zaa1 sha1:8acb8e9750e3f644bf323fcf3d338849db106c77
+zaa1 sha256:e79b029282c8abec2d9f3f7faceaf2a1405e02d1f368e66450ae66cf5b68d1f4
+
+zaa2 sha1:6c0b99286d0bce551ac4a7b3dff8b706edff3715
+zaa2 sha256:c82bd78c3e69ea1796e6b1a7a3ba45bb106c50e819296475b862123d3f5cc5a0
+
+zan1 sha1:087494262084cefee7ed484d20c8dc0580791272
+zan1 sha256:4b159eb3804d05599023dd074f771d06d02870f4ab24a7165add8ac3d703b8d3
+
+zdd1 sha1:879007efae624d2b1307214b24a956f0a8d686a8
+zdd1 sha256:eecfdd4d8092dd0363fb6d4548b54c6afc8982c3ed9b34e393f1d6a921d8eaa3
+
+zdm1 sha1:9b541b2275c06e3a7b13f28badf5294e2ae63df4
+zdm1 sha256:ab136e88e19a843c4bf7713d2090d5a2186ba16a6a80dacc12eeddd256a8e556
+
+zdm2 sha1:d77371d15817fcaa57eeec27f770c505ba974ec1
+zdm2 sha256:1c1a5f57363f46a15d95ce8527b3c2c158d88d16853b4acbf81bd20fd2c89a46
+
+zdn1 sha1:beb5d38c55283d280685ea21a0e50cfcc0ca064a
+zdn1 sha256:0f0eca66183617b0aa5ad74b256540329f841470922ca6760263c996d825eb18
+
+zmd1 sha1:d41fda41b7ec4de46b43cb7ea42a45001ae393d5
+zmd1 sha256:1ed32d481852eddf31a0ce12652a0ad14bf5b7a842667b5dbb0b50f35bf1c80a
+
+zmd2 sha1:a79ac3be9377639e1c7d1edf1ae1b3a5f0ccd8a9
+zmd2 sha256:b238da211b404f8917df2d9c6f7030535e904b2186131007a3c292ec6902f933
+
+zmm1 sha1:4ca22bae2527d3d9e1676498a0fba3b355bd1278
+zmm1 sha256:072b1d85b5f34fabc99dfa46008c5418df68302d3e317430006f49b32d244226
+
+zmm2 sha1:61422ba9c2c873416061a88cd40a59a35b576474
+zmm2 sha256:81dd5d2b3c5cda16fef552256aed4e2ea0802a8450a08f308a92142112ff6dda
+
+zmm3 sha1:697aad7715a1e7306ca76290a3dd4208fbaeddfa
+zmm3 sha256:8b10fab49e9be3414aa5e9a93d0e46f9569053440138a7c19a5eb5536d8e95bf
+
+zmn1 sha1:b16d7b25b869f2beb124efa53467d8a1550ad694
+zmn1 sha256:609e4f75d1295e844c826feeba213acb0b6cfc609adfe8ff705b19e3829ae3e9
+
+zmn2 sha1:a5c544c21cfcb07eb80a4d89a5b7d1570002edfd
+zmn2 sha256:d6d03edf2dc1a3b267a8205de5f41a2ff4b03def8c7ae02052b543fb09d589fc
+
+zna1 sha1:d12979c22fff69c59ca9409e7a8fe3ee25eaee80
+zna1 sha256:b37b80e789e8ea32aa323f004628f02013f632124b0282c7fe00a127d3c64c3c
+
+znd1 sha1:a18393c636b98e9bd7296b8b437ea4992b72440c
+znd1 sha256:af92a22eee8c38410a0c9d2b5135a10aeb052cbc7cf675541ed9a67bfcaf7cf9
+
+znm1 sha1:3fdbe17fd013303a2e981e1ca1c6cd6e72789087
+znm1 sha256:f75aeaa0c11e76918e381c105f0752932c6150e941fec565d24fa31098a13dc1
+
+znm2 sha1:7e09d6a3a14bd630913e8c75693cea32157b606d
+znm2 sha256:938d73cfbaa1c902a84fb5b3afd9736aa0590367fb9bd59c6c4d072ce70fcd6d
+EOF
+
+cat >.test-plain-OA <<EOF
+:000000 100644 $(test_oid zero) $(test_oid aa_1) A	AA
+:000000 100644 $(test_oid zero) $(test_oid an_1) A	AN
+:100644 000000 $(test_oid dd_1) $(test_oid zero) D	DD
+:000000 040000 $(test_oid zero) $(test_oid df_1) A	DF
+:100644 000000 $(test_oid dm_1) $(test_oid zero) D	DM
+:100644 000000 $(test_oid dn_1) $(test_oid zero) D	DN
+:000000 100644 $(test_oid zero) $(test_oid ll_2) A	LL
+:100644 100644 $(test_oid md_1) $(test_oid md_2) M	MD
+:100644 100644 $(test_oid mm_1) $(test_oid mm_2) M	MM
+:100644 100644 $(test_oid mn_1) $(test_oid mn_2) M	MN
+:100644 100644 $(test_oid ss_1) $(test_oid ss_2) M	SS
+:100644 100644 $(test_oid tt_1) $(test_oid tt_2) M	TT
+:040000 040000 $(test_oid z__1) $(test_oid z__2) M	Z
 EOF
 
-cat >.test-recursive-OA <<\EOF
-:000000 100644 0000000000000000000000000000000000000000 ccba72ad3888a3520b39efcf780b9ee64167535d A	AA
-:000000 100644 0000000000000000000000000000000000000000 7e426fb079479fd67f6d81f984e4ec649a44bc25 A	AN
-:100644 000000 bcc68ef997017466d5c9094bcf7692295f588c9a 0000000000000000000000000000000000000000 D	DD
-:000000 100644 0000000000000000000000000000000000000000 68a6d8b91da11045cf4aa3a5ab9f2a781c701249 A	DF/DF
-:100644 000000 141c1f1642328e4bc46a7d801a71da392e66791e 0000000000000000000000000000000000000000 D	DM
-:100644 000000 35abde1506ddf806572ff4d407bd06885d0f8ee9 0000000000000000000000000000000000000000 D	DN
-:000000 100644 0000000000000000000000000000000000000000 1d41122ebdd7a640f29d3c9cc4f9d70094374762 A	LL
-:100644 100644 03f24c8c4700babccfd28b654e7e8eac402ad6cd 103d9f89b50b9aad03054b579be5e7aa665f2d57 M	MD
-:100644 100644 b258508afb7ceb449981bd9d63d2d3e971bf8d34 b431b272d829ff3aa4d1a5085f4394ab4d3305b6 M	MM
-:100644 100644 bd084b0c27c7b6cc34f11d6d0509a29be3caf970 a716d58de4a570e0038f5c307bd8db34daea021f M	MN
-:100644 100644 40c959f984c8b89a2b02520d17f00d717f024397 2ac547ae9614a00d1b28275de608131f7a0e259f M	SS
-:100644 100644 4ac13458899ab908ef3b1128fa378daefc88d356 4c86f9a85fbc5e6804ee2e17a797538fbe785bca M	TT
-:000000 100644 0000000000000000000000000000000000000000 8acb8e9750e3f644bf323fcf3d338849db106c77 A	Z/AA
-:000000 100644 0000000000000000000000000000000000000000 087494262084cefee7ed484d20c8dc0580791272 A	Z/AN
-:100644 000000 879007efae624d2b1307214b24a956f0a8d686a8 0000000000000000000000000000000000000000 D	Z/DD
-:100644 000000 9b541b2275c06e3a7b13f28badf5294e2ae63df4 0000000000000000000000000000000000000000 D	Z/DM
-:100644 000000 beb5d38c55283d280685ea21a0e50cfcc0ca064a 0000000000000000000000000000000000000000 D	Z/DN
-:100644 100644 d41fda41b7ec4de46b43cb7ea42a45001ae393d5 a79ac3be9377639e1c7d1edf1ae1b3a5f0ccd8a9 M	Z/MD
-:100644 100644 4ca22bae2527d3d9e1676498a0fba3b355bd1278 61422ba9c2c873416061a88cd40a59a35b576474 M	Z/MM
-:100644 100644 b16d7b25b869f2beb124efa53467d8a1550ad694 a5c544c21cfcb07eb80a4d89a5b7d1570002edfd M	Z/MN
+cat >.test-recursive-OA <<EOF
+:000000 100644 $(test_oid zero) $(test_oid aa_1) A	AA
+:000000 100644 $(test_oid zero) $(test_oid an_1) A	AN
+:100644 000000 $(test_oid dd_1) $(test_oid zero) D	DD
+:000000 100644 $(test_oid zero) $(test_oid dfd1) A	DF/DF
+:100644 000000 $(test_oid dm_1) $(test_oid zero) D	DM
+:100644 000000 $(test_oid dn_1) $(test_oid zero) D	DN
+:000000 100644 $(test_oid zero) $(test_oid ll_2) A	LL
+:100644 100644 $(test_oid md_1) $(test_oid md_2) M	MD
+:100644 100644 $(test_oid mm_1) $(test_oid mm_2) M	MM
+:100644 100644 $(test_oid mn_1) $(test_oid mn_2) M	MN
+:100644 100644 $(test_oid ss_1) $(test_oid ss_2) M	SS
+:100644 100644 $(test_oid tt_1) $(test_oid tt_2) M	TT
+:000000 100644 $(test_oid zero) $(test_oid zaa1) A	Z/AA
+:000000 100644 $(test_oid zero) $(test_oid zan1) A	Z/AN
+:100644 000000 $(test_oid zdd1) $(test_oid zero) D	Z/DD
+:100644 000000 $(test_oid zdm1) $(test_oid zero) D	Z/DM
+:100644 000000 $(test_oid zdn1) $(test_oid zero) D	Z/DN
+:100644 100644 $(test_oid zmd1) $(test_oid zmd2) M	Z/MD
+:100644 100644 $(test_oid zmm1) $(test_oid zmm2) M	Z/MM
+:100644 100644 $(test_oid zmn1) $(test_oid zmn2) M	Z/MN
 EOF
-cat >.test-plain-OB <<\EOF
-:000000 100644 0000000000000000000000000000000000000000 6aa2b5335b16431a0ef71e5c0a28be69183cf6a2 A	AA
-:100644 000000 bcc68ef997017466d5c9094bcf7692295f588c9a 0000000000000000000000000000000000000000 D	DD
-:000000 100644 0000000000000000000000000000000000000000 71420ab81e254145d26d6fc0cddee64c1acd4787 A	DF
-:100644 100644 141c1f1642328e4bc46a7d801a71da392e66791e 3c4d8de5fbad08572bab8e10eef8dbb264cf0231 M	DM
-:000000 100644 0000000000000000000000000000000000000000 1d41122ebdd7a640f29d3c9cc4f9d70094374762 A	LL
-:100644 000000 03f24c8c4700babccfd28b654e7e8eac402ad6cd 0000000000000000000000000000000000000000 D	MD
-:100644 100644 b258508afb7ceb449981bd9d63d2d3e971bf8d34 19989d4559aae417fedee240ccf2ba315ea4dc2b M	MM
-:000000 100644 0000000000000000000000000000000000000000 15885881ea69115351c09b38371f0348a3fb8c67 A	NA
-:100644 000000 a4e179e4291e5536a5e1c82e091052772d2c5a93 0000000000000000000000000000000000000000 D	ND
-:100644 100644 c8f25781e8f1792e3e40b74225e20553041b5226 cdb9a8c3da571502ac30225e9c17beccb8387983 M	NM
-:100644 100644 40c959f984c8b89a2b02520d17f00d717f024397 2ac547ae9614a00d1b28275de608131f7a0e259f M	SS
-:100644 100644 4ac13458899ab908ef3b1128fa378daefc88d356 c4e4a12231b9fa79a0053cb6077fcb21bb5b135a M	TT
-:040000 040000 7d670fdcdb9929f6c7dac196ff78689cd1c566a1 1ba523955d5160681af65cb776411f574c1e8155 M	Z
+cat >.test-plain-OB <<EOF
+:000000 100644 $(test_oid zero) $(test_oid aa_2) A	AA
+:100644 000000 $(test_oid dd_1) $(test_oid zero) D	DD
+:000000 100644 $(test_oid zero) $(test_oid df_2) A	DF
+:100644 100644 $(test_oid dm_1) $(test_oid dm_2) M	DM
+:000000 100644 $(test_oid zero) $(test_oid ll_2) A	LL
+:100644 000000 $(test_oid md_1) $(test_oid zero) D	MD
+:100644 100644 $(test_oid mm_1) $(test_oid mm_3) M	MM
+:000000 100644 $(test_oid zero) $(test_oid na_1) A	NA
+:100644 000000 $(test_oid nd_1) $(test_oid zero) D	ND
+:100644 100644 $(test_oid nm_1) $(test_oid nm_2) M	NM
+:100644 100644 $(test_oid ss_1) $(test_oid ss_2) M	SS
+:100644 100644 $(test_oid tt_1) $(test_oid tt_3) M	TT
+:040000 040000 $(test_oid z__1) $(test_oid z__3) M	Z
 EOF
-cat >.test-recursive-OB <<\EOF
-:000000 100644 0000000000000000000000000000000000000000 6aa2b5335b16431a0ef71e5c0a28be69183cf6a2 A	AA
-:100644 000000 bcc68ef997017466d5c9094bcf7692295f588c9a 0000000000000000000000000000000000000000 D	DD
-:000000 100644 0000000000000000000000000000000000000000 71420ab81e254145d26d6fc0cddee64c1acd4787 A	DF
-:100644 100644 141c1f1642328e4bc46a7d801a71da392e66791e 3c4d8de5fbad08572bab8e10eef8dbb264cf0231 M	DM
-:000000 100644 0000000000000000000000000000000000000000 1d41122ebdd7a640f29d3c9cc4f9d70094374762 A	LL
-:100644 000000 03f24c8c4700babccfd28b654e7e8eac402ad6cd 0000000000000000000000000000000000000000 D	MD
-:100644 100644 b258508afb7ceb449981bd9d63d2d3e971bf8d34 19989d4559aae417fedee240ccf2ba315ea4dc2b M	MM
-:000000 100644 0000000000000000000000000000000000000000 15885881ea69115351c09b38371f0348a3fb8c67 A	NA
-:100644 000000 a4e179e4291e5536a5e1c82e091052772d2c5a93 0000000000000000000000000000000000000000 D	ND
-:100644 100644 c8f25781e8f1792e3e40b74225e20553041b5226 cdb9a8c3da571502ac30225e9c17beccb8387983 M	NM
-:100644 100644 40c959f984c8b89a2b02520d17f00d717f024397 2ac547ae9614a00d1b28275de608131f7a0e259f M	SS
-:100644 100644 4ac13458899ab908ef3b1128fa378daefc88d356 c4e4a12231b9fa79a0053cb6077fcb21bb5b135a M	TT
-:000000 100644 0000000000000000000000000000000000000000 6c0b99286d0bce551ac4a7b3dff8b706edff3715 A	Z/AA
-:100644 000000 879007efae624d2b1307214b24a956f0a8d686a8 0000000000000000000000000000000000000000 D	Z/DD
-:100644 100644 9b541b2275c06e3a7b13f28badf5294e2ae63df4 d77371d15817fcaa57eeec27f770c505ba974ec1 M	Z/DM
-:100644 000000 d41fda41b7ec4de46b43cb7ea42a45001ae393d5 0000000000000000000000000000000000000000 D	Z/MD
-:100644 100644 4ca22bae2527d3d9e1676498a0fba3b355bd1278 697aad7715a1e7306ca76290a3dd4208fbaeddfa M	Z/MM
-:000000 100644 0000000000000000000000000000000000000000 d12979c22fff69c59ca9409e7a8fe3ee25eaee80 A	Z/NA
-:100644 000000 a18393c636b98e9bd7296b8b437ea4992b72440c 0000000000000000000000000000000000000000 D	Z/ND
-:100644 100644 3fdbe17fd013303a2e981e1ca1c6cd6e72789087 7e09d6a3a14bd630913e8c75693cea32157b606d M	Z/NM
+cat >.test-recursive-OB <<EOF
+:000000 100644 $(test_oid zero) $(test_oid aa_2) A	AA
+:100644 000000 $(test_oid dd_1) $(test_oid zero) D	DD
+:000000 100644 $(test_oid zero) $(test_oid df_2) A	DF
+:100644 100644 $(test_oid dm_1) $(test_oid dm_2) M	DM
+:000000 100644 $(test_oid zero) $(test_oid ll_2) A	LL
+:100644 000000 $(test_oid md_1) $(test_oid zero) D	MD
+:100644 100644 $(test_oid mm_1) $(test_oid mm_3) M	MM
+:000000 100644 $(test_oid zero) $(test_oid na_1) A	NA
+:100644 000000 $(test_oid nd_1) $(test_oid zero) D	ND
+:100644 100644 $(test_oid nm_1) $(test_oid nm_2) M	NM
+:100644 100644 $(test_oid ss_1) $(test_oid ss_2) M	SS
+:100644 100644 $(test_oid tt_1) $(test_oid tt_3) M	TT
+:000000 100644 $(test_oid zero) $(test_oid zaa2) A	Z/AA
+:100644 000000 $(test_oid zdd1) $(test_oid zero) D	Z/DD
+:100644 100644 $(test_oid zdm1) $(test_oid zdm2) M	Z/DM
+:100644 000000 $(test_oid zmd1) $(test_oid zero) D	Z/MD
+:100644 100644 $(test_oid zmm1) $(test_oid zmm3) M	Z/MM
+:000000 100644 $(test_oid zero) $(test_oid zna1) A	Z/NA
+:100644 000000 $(test_oid znd1) $(test_oid zero) D	Z/ND
+:100644 100644 $(test_oid znm1) $(test_oid znm2) M	Z/NM
 EOF
-cat >.test-plain-AB <<\EOF
-:100644 100644 ccba72ad3888a3520b39efcf780b9ee64167535d 6aa2b5335b16431a0ef71e5c0a28be69183cf6a2 M	AA
-:100644 000000 7e426fb079479fd67f6d81f984e4ec649a44bc25 0000000000000000000000000000000000000000 D	AN
-:000000 100644 0000000000000000000000000000000000000000 71420ab81e254145d26d6fc0cddee64c1acd4787 A	DF
-:040000 000000 6d50f65d3bdab91c63444294d38f08aeff328e42 0000000000000000000000000000000000000000 D	DF
-:000000 100644 0000000000000000000000000000000000000000 3c4d8de5fbad08572bab8e10eef8dbb264cf0231 A	DM
-:000000 100644 0000000000000000000000000000000000000000 35abde1506ddf806572ff4d407bd06885d0f8ee9 A	DN
-:100644 000000 103d9f89b50b9aad03054b579be5e7aa665f2d57 0000000000000000000000000000000000000000 D	MD
-:100644 100644 b431b272d829ff3aa4d1a5085f4394ab4d3305b6 19989d4559aae417fedee240ccf2ba315ea4dc2b M	MM
-:100644 100644 a716d58de4a570e0038f5c307bd8db34daea021f bd084b0c27c7b6cc34f11d6d0509a29be3caf970 M	MN
-:000000 100644 0000000000000000000000000000000000000000 15885881ea69115351c09b38371f0348a3fb8c67 A	NA
-:100644 000000 a4e179e4291e5536a5e1c82e091052772d2c5a93 0000000000000000000000000000000000000000 D	ND
-:100644 100644 c8f25781e8f1792e3e40b74225e20553041b5226 cdb9a8c3da571502ac30225e9c17beccb8387983 M	NM
-:100644 100644 4c86f9a85fbc5e6804ee2e17a797538fbe785bca c4e4a12231b9fa79a0053cb6077fcb21bb5b135a M	TT
-:040000 040000 5e5f22072bb39f6e12cf663a57cb634c76eefb49 1ba523955d5160681af65cb776411f574c1e8155 M	Z
+cat >.test-plain-AB <<EOF
+:100644 100644 $(test_oid aa_1) $(test_oid aa_2) M	AA
+:100644 000000 $(test_oid an_1) $(test_oid zero) D	AN
+:000000 100644 $(test_oid zero) $(test_oid df_2) A	DF
+:040000 000000 $(test_oid df_1) $(test_oid zero) D	DF
+:000000 100644 $(test_oid zero) $(test_oid dm_2) A	DM
+:000000 100644 $(test_oid zero) $(test_oid dn_1) A	DN
+:100644 000000 $(test_oid md_2) $(test_oid zero) D	MD
+:100644 100644 $(test_oid mm_2) $(test_oid mm_3) M	MM
+:100644 100644 $(test_oid mn_2) $(test_oid mn_1) M	MN
+:000000 100644 $(test_oid zero) $(test_oid na_1) A	NA
+:100644 000000 $(test_oid nd_1) $(test_oid zero) D	ND
+:100644 100644 $(test_oid nm_1) $(test_oid nm_2) M	NM
+:100644 100644 $(test_oid tt_2) $(test_oid tt_3) M	TT
+:040000 040000 $(test_oid z__2) $(test_oid z__3) M	Z
 EOF
-cat >.test-recursive-AB <<\EOF
-:100644 100644 ccba72ad3888a3520b39efcf780b9ee64167535d 6aa2b5335b16431a0ef71e5c0a28be69183cf6a2 M	AA
-:100644 000000 7e426fb079479fd67f6d81f984e4ec649a44bc25 0000000000000000000000000000000000000000 D	AN
-:000000 100644 0000000000000000000000000000000000000000 71420ab81e254145d26d6fc0cddee64c1acd4787 A	DF
-:100644 000000 68a6d8b91da11045cf4aa3a5ab9f2a781c701249 0000000000000000000000000000000000000000 D	DF/DF
-:000000 100644 0000000000000000000000000000000000000000 3c4d8de5fbad08572bab8e10eef8dbb264cf0231 A	DM
-:000000 100644 0000000000000000000000000000000000000000 35abde1506ddf806572ff4d407bd06885d0f8ee9 A	DN
-:100644 000000 103d9f89b50b9aad03054b579be5e7aa665f2d57 0000000000000000000000000000000000000000 D	MD
-:100644 100644 b431b272d829ff3aa4d1a5085f4394ab4d3305b6 19989d4559aae417fedee240ccf2ba315ea4dc2b M	MM
-:100644 100644 a716d58de4a570e0038f5c307bd8db34daea021f bd084b0c27c7b6cc34f11d6d0509a29be3caf970 M	MN
-:000000 100644 0000000000000000000000000000000000000000 15885881ea69115351c09b38371f0348a3fb8c67 A	NA
-:100644 000000 a4e179e4291e5536a5e1c82e091052772d2c5a93 0000000000000000000000000000000000000000 D	ND
-:100644 100644 c8f25781e8f1792e3e40b74225e20553041b5226 cdb9a8c3da571502ac30225e9c17beccb8387983 M	NM
-:100644 100644 4c86f9a85fbc5e6804ee2e17a797538fbe785bca c4e4a12231b9fa79a0053cb6077fcb21bb5b135a M	TT
-:100644 100644 8acb8e9750e3f644bf323fcf3d338849db106c77 6c0b99286d0bce551ac4a7b3dff8b706edff3715 M	Z/AA
-:100644 000000 087494262084cefee7ed484d20c8dc0580791272 0000000000000000000000000000000000000000 D	Z/AN
-:000000 100644 0000000000000000000000000000000000000000 d77371d15817fcaa57eeec27f770c505ba974ec1 A	Z/DM
-:000000 100644 0000000000000000000000000000000000000000 beb5d38c55283d280685ea21a0e50cfcc0ca064a A	Z/DN
-:100644 000000 a79ac3be9377639e1c7d1edf1ae1b3a5f0ccd8a9 0000000000000000000000000000000000000000 D	Z/MD
-:100644 100644 61422ba9c2c873416061a88cd40a59a35b576474 697aad7715a1e7306ca76290a3dd4208fbaeddfa M	Z/MM
-:100644 100644 a5c544c21cfcb07eb80a4d89a5b7d1570002edfd b16d7b25b869f2beb124efa53467d8a1550ad694 M	Z/MN
-:000000 100644 0000000000000000000000000000000000000000 d12979c22fff69c59ca9409e7a8fe3ee25eaee80 A	Z/NA
-:100644 000000 a18393c636b98e9bd7296b8b437ea4992b72440c 0000000000000000000000000000000000000000 D	Z/ND
-:100644 100644 3fdbe17fd013303a2e981e1ca1c6cd6e72789087 7e09d6a3a14bd630913e8c75693cea32157b606d M	Z/NM
+cat >.test-recursive-AB <<EOF
+:100644 100644 $(test_oid aa_1) $(test_oid aa_2) M	AA
+:100644 000000 $(test_oid an_1) $(test_oid zero) D	AN
+:000000 100644 $(test_oid zero) $(test_oid df_2) A	DF
+:100644 000000 $(test_oid dfd1) $(test_oid zero) D	DF/DF
+:000000 100644 $(test_oid zero) $(test_oid dm_2) A	DM
+:000000 100644 $(test_oid zero) $(test_oid dn_1) A	DN
+:100644 000000 $(test_oid md_2) $(test_oid zero) D	MD
+:100644 100644 $(test_oid mm_2) $(test_oid mm_3) M	MM
+:100644 100644 $(test_oid mn_2) $(test_oid mn_1) M	MN
+:000000 100644 $(test_oid zero) $(test_oid na_1) A	NA
+:100644 000000 $(test_oid nd_1) $(test_oid zero) D	ND
+:100644 100644 $(test_oid nm_1) $(test_oid nm_2) M	NM
+:100644 100644 $(test_oid tt_2) $(test_oid tt_3) M	TT
+:100644 100644 $(test_oid zaa1) $(test_oid zaa2) M	Z/AA
+:100644 000000 $(test_oid zan1) $(test_oid zero) D	Z/AN
+:000000 100644 $(test_oid zero) $(test_oid zdm2) A	Z/DM
+:000000 100644 $(test_oid zero) $(test_oid zdn1) A	Z/DN
+:100644 000000 $(test_oid zmd2) $(test_oid zero) D	Z/MD
+:100644 100644 $(test_oid zmm2) $(test_oid zmm3) M	Z/MM
+:100644 100644 $(test_oid zmn2) $(test_oid zmn1) M	Z/MN
+:000000 100644 $(test_oid zero) $(test_oid zna1) A	Z/NA
+:100644 000000 $(test_oid znd1) $(test_oid zero) D	Z/ND
+:100644 100644 $(test_oid znm1) $(test_oid znm2) M	Z/NM
 EOF
 
 cmp_diff_files_output () {
diff --git a/third_party/git/t/t4005-diff-rename-2.sh b/third_party/git/t/t4005-diff-rename-2.sh
index f542d2929d..d18a80493c 100755
--- a/third_party/git/t/t4005-diff-rename-2.sh
+++ b/third_party/git/t/t4005-diff-rename-2.sh
@@ -14,8 +14,8 @@ test_expect_success 'setup reference tree' '
 	git update-index --add COPYING rezrov &&
 	tree=$(git write-tree) &&
 	echo $tree &&
-	sed -e 's/HOWEVER/However/' <COPYING >COPYING.1 &&
-	sed -e 's/GPL/G.P.L/g' <COPYING >COPYING.2 &&
+	sed -e "s/HOWEVER/However/" <COPYING >COPYING.1 &&
+	sed -e "s/GPL/G.P.L/g" <COPYING >COPYING.2 &&
 	origoid=$(git hash-object COPYING) &&
 	oid1=$(git hash-object COPYING.1) &&
 	oid2=$(git hash-object COPYING.2)
diff --git a/third_party/git/t/t4009-diff-rename-4.sh b/third_party/git/t/t4009-diff-rename-4.sh
index 3641fd84d6..b63bdf031f 100755
--- a/third_party/git/t/t4009-diff-rename-4.sh
+++ b/third_party/git/t/t4009-diff-rename-4.sh
@@ -14,6 +14,7 @@ test_expect_success \
     'cat "$TEST_DIRECTORY"/diff-lib/COPYING >COPYING &&
      echo frotz >rezrov &&
     git update-index --add COPYING rezrov &&
+    orig=$(git hash-object COPYING) &&
     tree=$(git write-tree) &&
     echo $tree'
 
@@ -22,6 +23,8 @@ test_expect_success \
     'sed -e 's/HOWEVER/However/' <COPYING >COPYING.1 &&
     sed -e 's/GPL/G.P.L/g' <COPYING >COPYING.2 &&
     rm -f COPYING &&
+    c1=$(git hash-object COPYING.1) &&
+    c2=$(git hash-object COPYING.2) &&
     git update-index --add --remove COPYING COPYING.?'
 
 # tree has COPYING and rezrov.  work tree has COPYING.1 and COPYING.2,
@@ -31,11 +34,11 @@ test_expect_success \
 
 git diff-index -z -C $tree >current
 
-cat >expected <<\EOF
-:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0603b3238a076dc6c8022aedc6648fa523a17178 C1234
+cat >expected <<EOF
+:100644 100644 $orig $c1 C1234
 COPYING
 COPYING.1
-:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 06c67961bbaed34a127f76d261f4c0bf73eda471 R1234
+:100644 100644 $orig $c2 R1234
 COPYING
 COPYING.2
 EOF
@@ -57,10 +60,10 @@ test_expect_success \
 # about rezrov.
 
 git diff-index -z -C $tree >current
-cat >expected <<\EOF
-:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 06c67961bbaed34a127f76d261f4c0bf73eda471 M
+cat >expected <<EOF
+:100644 100644 $orig $c2 M
 COPYING
-:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0603b3238a076dc6c8022aedc6648fa523a17178 C1234
+:100644 100644 $orig $c1 C1234
 COPYING
 COPYING.1
 EOF
@@ -82,8 +85,8 @@ test_expect_success \
      git update-index --add --remove COPYING COPYING.1'
 
 git diff-index -z -C --find-copies-harder $tree >current
-cat >expected <<\EOF
-:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0603b3238a076dc6c8022aedc6648fa523a17178 C1234
+cat >expected <<EOF
+:100644 100644 $orig $c1 C1234
 COPYING
 COPYING.1
 EOF
diff --git a/third_party/git/t/t4010-diff-pathspec.sh b/third_party/git/t/t4010-diff-pathspec.sh
index 281f8fad0c..65cc703c65 100755
--- a/third_party/git/t/t4010-diff-pathspec.sh
+++ b/third_party/git/t/t4010-diff-pathspec.sh
@@ -17,11 +17,15 @@ test_expect_success \
     'echo frotz >file0 &&
      mkdir path1 &&
      echo rezrov >path1/file1 &&
+     before0=$(git hash-object file0) &&
+     before1=$(git hash-object path1/file1) &&
      git update-index --add file0 path1/file1 &&
      tree=$(git write-tree) &&
      echo "$tree" &&
      echo nitfol >file0 &&
      echo yomin >path1/file1 &&
+     after0=$(git hash-object file0) &&
+     after1=$(git hash-object path1/file1) &&
      git update-index file0 path1/file1'
 
 cat >expected <<\EOF
@@ -31,32 +35,32 @@ test_expect_success \
     'git diff-index --cached $tree -- path >current &&
      compare_diff_raw current expected'
 
-cat >expected <<\EOF
-:100644 100644 766498d93a4b06057a8e49d23f4068f1170ff38f 0a41e115ab61be0328a19b29f18cdcb49338d516 M	path1/file1
+cat >expected <<EOF
+:100644 100644 $before1 $after1 M	path1/file1
 EOF
 test_expect_success \
     'limit to path1 should show path1/file1' \
     'git diff-index --cached $tree -- path1 >current &&
      compare_diff_raw current expected'
 
-cat >expected <<\EOF
-:100644 100644 766498d93a4b06057a8e49d23f4068f1170ff38f 0a41e115ab61be0328a19b29f18cdcb49338d516 M	path1/file1
+cat >expected <<EOF
+:100644 100644 $before1 $after1 M	path1/file1
 EOF
 test_expect_success \
     'limit to path1/ should show path1/file1' \
     'git diff-index --cached $tree -- path1/ >current &&
      compare_diff_raw current expected'
 
-cat >expected <<\EOF
-:100644 100644 766498d93a4b06057a8e49d23f4068f1170ff38f 0a41e115ab61be0328a19b29f18cdcb49338d516 M	path1/file1
+cat >expected <<EOF
+:100644 100644 $before1 $after1 M	path1/file1
 EOF
 test_expect_success \
     '"*file1" should show path1/file1' \
     'git diff-index --cached $tree -- "*file1" >current &&
      compare_diff_raw current expected'
 
-cat >expected <<\EOF
-:100644 100644 8e4020bb5a8d8c873b25de15933e75cc0fc275df dca6b92303befc93086aa025d90a5facd7eb2812 M	file0
+cat >expected <<EOF
+:100644 100644 $before0 $after0 M	file0
 EOF
 test_expect_success \
     'limit to file0 should show file0' \
@@ -121,7 +125,9 @@ test_expect_success 'setup submodules' '
 test_expect_success 'diff-tree ignores trailing slash on submodule path' '
 	git diff --name-only HEAD^ HEAD submod >expect &&
 	git diff --name-only HEAD^ HEAD submod/ >actual &&
-	test_cmp expect actual
+	test_cmp expect actual &&
+	git diff --name-only HEAD^ HEAD -- submod/whatever >actual &&
+	test_must_be_empty actual
 '
 
 test_expect_success 'diff multiple wildcard pathspecs' '
diff --git a/third_party/git/t/t4011-diff-symlink.sh b/third_party/git/t/t4011-diff-symlink.sh
index 5ae19b987d..717034bb50 100755
--- a/third_party/git/t/t4011-diff-symlink.sh
+++ b/third_party/git/t/t4011-diff-symlink.sh
@@ -9,11 +9,24 @@ test_description='Test diff of symlinks.
 . ./test-lib.sh
 . "$TEST_DIRECTORY"/diff-lib.sh
 
+# Print the short OID of a symlink with the given name.
+symlink_oid () {
+	local oid=$(printf "%s" "$1" | git hash-object --stdin) &&
+	git rev-parse --short "$oid"
+}
+
+# Print the short OID of the given file.
+short_oid () {
+	local oid=$(git hash-object "$1") &&
+	git rev-parse --short "$oid"
+}
+
 test_expect_success 'diff new symlink and file' '
-	cat >expected <<-\EOF &&
+	symlink=$(symlink_oid xyzzy) &&
+	cat >expected <<-EOF &&
 	diff --git a/frotz b/frotz
 	new file mode 120000
-	index 0000000..7c465af
+	index 0000000..$symlink
 	--- /dev/null
 	+++ b/frotz
 	@@ -0,0 +1 @@
@@ -21,7 +34,7 @@ test_expect_success 'diff new symlink and file' '
 	\ No newline at end of file
 	diff --git a/nitfol b/nitfol
 	new file mode 100644
-	index 0000000..7c465af
+	index 0000000..$symlink
 	--- /dev/null
 	+++ b/nitfol
 	@@ -0,0 +1 @@
@@ -46,10 +59,10 @@ test_expect_success 'diff unchanged symlink and file'  '
 '
 
 test_expect_success 'diff removed symlink and file' '
-	cat >expected <<-\EOF &&
+	cat >expected <<-EOF &&
 	diff --git a/frotz b/frotz
 	deleted file mode 120000
-	index 7c465af..0000000
+	index $symlink..0000000
 	--- a/frotz
 	+++ /dev/null
 	@@ -1 +0,0 @@
@@ -57,7 +70,7 @@ test_expect_success 'diff removed symlink and file' '
 	\ No newline at end of file
 	diff --git a/nitfol b/nitfol
 	deleted file mode 100644
-	index 7c465af..0000000
+	index $symlink..0000000
 	--- a/nitfol
 	+++ /dev/null
 	@@ -1 +0,0 @@
@@ -90,9 +103,10 @@ test_expect_success 'diff identical, but newly created symlink and file' '
 '
 
 test_expect_success 'diff different symlink and file' '
-	cat >expected <<-\EOF &&
+	new=$(symlink_oid yxyyz) &&
+	cat >expected <<-EOF &&
 	diff --git a/frotz b/frotz
-	index 7c465af..df1db54 120000
+	index $symlink..$new 120000
 	--- a/frotz
 	+++ b/frotz
 	@@ -1 +1 @@
@@ -101,7 +115,7 @@ test_expect_success 'diff different symlink and file' '
 	+yxyyz
 	\ No newline at end of file
 	diff --git a/nitfol b/nitfol
-	index 7c465af..df1db54 100644
+	index $symlink..$new 100644
 	--- a/nitfol
 	+++ b/nitfol
 	@@ -1 +1 @@
@@ -137,14 +151,16 @@ test_expect_success SYMLINKS 'setup symlinks with attributes' '
 '
 
 test_expect_success SYMLINKS 'symlinks do not respect userdiff config by path' '
-	cat >expect <<-\EOF &&
+	file=$(short_oid file.bin) &&
+	link=$(symlink_oid file.bin) &&
+	cat >expect <<-EOF &&
 	diff --git a/file.bin b/file.bin
 	new file mode 100644
-	index 0000000..d95f3ad
+	index 0000000..$file
 	Binary files /dev/null and b/file.bin differ
 	diff --git a/link.bin b/link.bin
 	new file mode 120000
-	index 0000000..dce41ec
+	index 0000000..$link
 	--- /dev/null
 	+++ b/link.bin
 	@@ -0,0 +1 @@
diff --git a/third_party/git/t/t4013-diff-various.sh b/third_party/git/t/t4013-diff-various.sh
index a9054d2db1..5c7b0122b4 100755
--- a/third_party/git/t/t4013-diff-various.sh
+++ b/third_party/git/t/t4013-diff-various.sh
@@ -7,9 +7,6 @@ test_description='Various diff formatting options'
 
 . ./test-lib.sh
 
-LF='
-'
-
 test_expect_success setup '
 
 	GIT_AUTHOR_DATE="2006-06-26 00:00:00 +0000" &&
@@ -98,6 +95,15 @@ test_expect_success setup '
 	git commit -m "update mode" &&
 	git checkout -f master &&
 
+	GIT_AUTHOR_DATE="2006-06-26 00:06:00 +0000" &&
+	GIT_COMMITTER_DATE="2006-06-26 00:06:00 +0000" &&
+	export GIT_AUTHOR_DATE GIT_COMMITTER_DATE &&
+	git checkout -b note initial &&
+	git update-index --chmod=+x file2 &&
+	git commit -m "update mode (file2)" &&
+	git notes add -m "note" &&
+	git checkout -f master &&
+
 	# Same merge as master, but with parents reversed. Hide it in a
 	# pseudo-ref to avoid impacting tests with --all.
 	commit=$(echo reverse |
@@ -123,6 +129,48 @@ test_expect_success setup '
 +*++ [initial] Initial
 EOF
 
+process_diffs () {
+	perl -e '
+		my $oid_length = length($ARGV[0]);
+		my $x40 = "[0-9a-f]{40}";
+		my $xab = "[0-9a-f]{4,16}";
+		my $orx = "[0-9a-f]" x $oid_length;
+
+		sub munge_oid {
+			my ($oid) = @_;
+			my $x;
+
+			return "" unless length $oid;
+
+			if ($oid =~ /^(100644|100755|120000)$/) {
+				return $oid;
+			}
+
+			if ($oid =~ /^0*$/) {
+				$x = "0";
+			} else {
+				$x = "f";
+			}
+
+			if (length($oid) == 40) {
+				return $x x $oid_length;
+			} else {
+				return $x x length($oid);
+			}
+		}
+
+		while (<STDIN>) {
+			s/($orx)/munge_oid($1)/ge;
+			s/From ($x40)( |\))/"From " . munge_oid($1) . $2/ge;
+			s/commit ($x40)($| \(from )($x40?)/"commit " .  munge_oid($1) . $2 . munge_oid($3)/ge;
+			s/\b($x40)( |\.\.|$)/munge_oid($1) . $2/ge;
+			s/^($x40)($| )/munge_oid($1) . $2/e;
+			s/($xab)(\.\.|,| |\.\.\.|$)/munge_oid($1) . $2/ge;
+			print;
+		}
+	' "$ZERO_OID" <"$1"
+}
+
 V=$(git version | sed -e 's/^git version //' -e 's/\./\\./g')
 while read magic cmd
 do
@@ -161,13 +209,15 @@ do
 		} >"$actual" &&
 		if test -f "$expect"
 		then
+			process_diffs "$actual" >actual &&
+			process_diffs "$expect" >expect &&
 			case $cmd in
 			*format-patch* | *-stat*)
-				test_i18ncmp "$expect" "$actual";;
+				test_i18ncmp expect actual;;
 			*)
-				test_cmp "$expect" "$actual";;
+				test_cmp expect actual;;
 			esac &&
-			rm -f "$actual"
+			rm -f "$actual" actual expect
 		else
 			# this is to help developing new tests.
 			cp "$actual" "$expect"
@@ -189,6 +239,9 @@ diff-tree --root -r --abbrev=4 initial
 :noellipses diff-tree --root -r --abbrev=4 initial
 diff-tree -p initial
 diff-tree --root -p initial
+diff-tree --root -p --abbrev=10 initial
+diff-tree --root -p --full-index initial
+diff-tree --root -p --full-index --abbrev=10 initial
 diff-tree --patch-with-stat initial
 diff-tree --root --patch-with-stat initial
 diff-tree --patch-with-raw initial
@@ -265,6 +318,9 @@ log --root --patch-with-stat --summary master
 log --root -c --patch-with-stat --summary master
 # improved by Timo's patch
 log --root --cc --patch-with-stat --summary master
+log --no-diff-merges -p --first-parent master
+log --diff-merges=off -p --first-parent master
+log --first-parent --diff-merges=off -p master
 log -p --first-parent master
 log -m -p --first-parent master
 log -m -p master
@@ -375,6 +431,9 @@ diff --no-index --raw --no-abbrev dir2 dir
 
 diff-tree --pretty --root --stat --compact-summary initial
 diff-tree --pretty -R --root --stat --compact-summary initial
+diff-tree --pretty note
+diff-tree --pretty --notes note
+diff-tree --format=%N note
 diff-tree --stat --compact-summary initial mode
 diff-tree -R --stat --compact-summary initial mode
 EOF
@@ -386,16 +445,22 @@ test_expect_success 'log -S requires an argument' '
 test_expect_success 'diff --cached on unborn branch' '
 	echo ref: refs/heads/unborn >.git/HEAD &&
 	git diff --cached >result &&
-	test_cmp "$TEST_DIRECTORY/t4013/diff.diff_--cached" result
+	process_diffs result >actual &&
+	process_diffs "$TEST_DIRECTORY/t4013/diff.diff_--cached" >expected &&
+	test_cmp expected actual
 '
 
 test_expect_success 'diff --cached -- file on unborn branch' '
 	git diff --cached -- file0 >result &&
-	test_cmp "$TEST_DIRECTORY/t4013/diff.diff_--cached_--_file0" result
+	process_diffs result >actual &&
+	process_diffs "$TEST_DIRECTORY/t4013/diff.diff_--cached_--_file0" >expected &&
+	test_cmp expected actual
 '
 test_expect_success 'diff --line-prefix with spaces' '
 	git diff --line-prefix="| | | " --cached -- file0 >result &&
-	test_cmp "$TEST_DIRECTORY/t4013/diff.diff_--line-prefix_--cached_--_file0" result
+	process_diffs result >actual &&
+	process_diffs "$TEST_DIRECTORY/t4013/diff.diff_--line-prefix_--cached_--_file0" >expected &&
+	test_cmp expected actual
 '
 
 test_expect_success 'diff-tree --stdin with log formatting' '
diff --git a/third_party/git/t/t4013/diff.diff-tree_--format=%N_note b/third_party/git/t/t4013/diff.diff-tree_--format=%N_note
new file mode 100644
index 0000000000..93042ed539
--- /dev/null
+++ b/third_party/git/t/t4013/diff.diff-tree_--format=%N_note
@@ -0,0 +1,6 @@
+$ git diff-tree --format=%N note
+note
+
+
+:100644 100755 01e79c32a8c99c557f0757da7cb6d65b3414466d 01e79c32a8c99c557f0757da7cb6d65b3414466d M	file2
+$
diff --git a/third_party/git/t/t4013/diff.diff-tree_--pretty_--notes_note b/third_party/git/t/t4013/diff.diff-tree_--pretty_--notes_note
new file mode 100644
index 0000000000..4d0bde601c
--- /dev/null
+++ b/third_party/git/t/t4013/diff.diff-tree_--pretty_--notes_note
@@ -0,0 +1,12 @@
+$ git diff-tree --pretty --notes note
+commit a6f364368ca320bc5a92e18912e16fa6b3dff598
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:06:00 2006 +0000
+
+    update mode (file2)
+
+Notes:
+    note
+
+:100644 100755 01e79c32a8c99c557f0757da7cb6d65b3414466d 01e79c32a8c99c557f0757da7cb6d65b3414466d M	file2
+$
diff --git a/third_party/git/t/t4013/diff.diff-tree_--pretty_note b/third_party/git/t/t4013/diff.diff-tree_--pretty_note
new file mode 100644
index 0000000000..1fa5967083
--- /dev/null
+++ b/third_party/git/t/t4013/diff.diff-tree_--pretty_note
@@ -0,0 +1,9 @@
+$ git diff-tree --pretty note
+commit a6f364368ca320bc5a92e18912e16fa6b3dff598
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:06:00 2006 +0000
+
+    update mode (file2)
+
+:100644 100755 01e79c32a8c99c557f0757da7cb6d65b3414466d 01e79c32a8c99c557f0757da7cb6d65b3414466d M	file2
+$
diff --git a/third_party/git/t/t4013/diff.diff-tree_--root_-p_--abbrev=10_initial b/third_party/git/t/t4013/diff.diff-tree_--root_-p_--abbrev=10_initial
new file mode 100644
index 0000000000..7518a9044e
--- /dev/null
+++ b/third_party/git/t/t4013/diff.diff-tree_--root_-p_--abbrev=10_initial
@@ -0,0 +1,29 @@
+$ git diff-tree --root -p --abbrev=10 initial
+444ac553ac7612cc88969031b02b3767fb8a353a
+diff --git a/dir/sub b/dir/sub
+new file mode 100644
+index 0000000000..35d242ba79
+--- /dev/null
++++ b/dir/sub
+@@ -0,0 +1,2 @@
++A
++B
+diff --git a/file0 b/file0
+new file mode 100644
+index 0000000000..01e79c32a8
+--- /dev/null
++++ b/file0
+@@ -0,0 +1,3 @@
++1
++2
++3
+diff --git a/file2 b/file2
+new file mode 100644
+index 0000000000..01e79c32a8
+--- /dev/null
++++ b/file2
+@@ -0,0 +1,3 @@
++1
++2
++3
+$
diff --git a/third_party/git/t/t4013/diff.diff-tree_--root_-p_--full-index_--abbrev=10_initial b/third_party/git/t/t4013/diff.diff-tree_--root_-p_--full-index_--abbrev=10_initial
new file mode 100644
index 0000000000..69f913fbe5
--- /dev/null
+++ b/third_party/git/t/t4013/diff.diff-tree_--root_-p_--full-index_--abbrev=10_initial
@@ -0,0 +1,29 @@
+$ git diff-tree --root -p --full-index --abbrev=10 initial
+444ac553ac7612cc88969031b02b3767fb8a353a
+diff --git a/dir/sub b/dir/sub
+new file mode 100644
+index 0000000000000000000000000000000000000000..35d242ba79ae89ac695e26b3d4c27a8e6f028f9e
+--- /dev/null
++++ b/dir/sub
+@@ -0,0 +1,2 @@
++A
++B
+diff --git a/file0 b/file0
+new file mode 100644
+index 0000000000000000000000000000000000000000..01e79c32a8c99c557f0757da7cb6d65b3414466d
+--- /dev/null
++++ b/file0
+@@ -0,0 +1,3 @@
++1
++2
++3
+diff --git a/file2 b/file2
+new file mode 100644
+index 0000000000000000000000000000000000000000..01e79c32a8c99c557f0757da7cb6d65b3414466d
+--- /dev/null
++++ b/file2
+@@ -0,0 +1,3 @@
++1
++2
++3
+$
diff --git a/third_party/git/t/t4013/diff.diff-tree_--root_-p_--full-index_initial b/third_party/git/t/t4013/diff.diff-tree_--root_-p_--full-index_initial
new file mode 100644
index 0000000000..1b0b6717fa
--- /dev/null
+++ b/third_party/git/t/t4013/diff.diff-tree_--root_-p_--full-index_initial
@@ -0,0 +1,29 @@
+$ git diff-tree --root -p --full-index initial
+444ac553ac7612cc88969031b02b3767fb8a353a
+diff --git a/dir/sub b/dir/sub
+new file mode 100644
+index 0000000000000000000000000000000000000000..35d242ba79ae89ac695e26b3d4c27a8e6f028f9e
+--- /dev/null
++++ b/dir/sub
+@@ -0,0 +1,2 @@
++A
++B
+diff --git a/file0 b/file0
+new file mode 100644
+index 0000000000000000000000000000000000000000..01e79c32a8c99c557f0757da7cb6d65b3414466d
+--- /dev/null
++++ b/file0
+@@ -0,0 +1,3 @@
++1
++2
++3
+diff --git a/file2 b/file2
+new file mode 100644
+index 0000000000000000000000000000000000000000..01e79c32a8c99c557f0757da7cb6d65b3414466d
+--- /dev/null
++++ b/file2
+@@ -0,0 +1,3 @@
++1
++2
++3
+$
diff --git a/third_party/git/t/t4013/diff.log_--decorate=full_--all b/third_party/git/t/t4013/diff.log_--decorate=full_--all
index 2afe91f116..3f9b872ece 100644
--- a/third_party/git/t/t4013/diff.log_--decorate=full_--all
+++ b/third_party/git/t/t4013/diff.log_--decorate=full_--all
@@ -5,12 +5,27 @@ Date:   Mon Jun 26 00:06:00 2006 +0000
 
     update mode
 
+commit a6f364368ca320bc5a92e18912e16fa6b3dff598 (refs/heads/note)
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:06:00 2006 +0000
+
+    update mode (file2)
+
+Notes:
+    note
+
 commit cd4e72fd96faed3f0ba949dc42967430374e2290 (refs/heads/rearrange)
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:06:00 2006 +0000
 
     Rearranged lines in dir/sub
 
+commit cbacedd14cb8b89255a2c02b59e77a2e9a8021a0 (refs/notes/commits)
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:06:00 2006 +0000
+
+    Notes added by 'git notes add'
+
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (HEAD -> refs/heads/master)
 Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
diff --git a/third_party/git/t/t4013/diff.log_--decorate_--all b/third_party/git/t/t4013/diff.log_--decorate_--all
index d0f308ab2b..f5e20e1e14 100644
--- a/third_party/git/t/t4013/diff.log_--decorate_--all
+++ b/third_party/git/t/t4013/diff.log_--decorate_--all
@@ -5,12 +5,27 @@ Date:   Mon Jun 26 00:06:00 2006 +0000
 
     update mode
 
+commit a6f364368ca320bc5a92e18912e16fa6b3dff598 (note)
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:06:00 2006 +0000
+
+    update mode (file2)
+
+Notes:
+    note
+
 commit cd4e72fd96faed3f0ba949dc42967430374e2290 (rearrange)
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:06:00 2006 +0000
 
     Rearranged lines in dir/sub
 
+commit cbacedd14cb8b89255a2c02b59e77a2e9a8021a0 (refs/notes/commits)
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:06:00 2006 +0000
+
+    Notes added by 'git notes add'
+
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (HEAD -> master)
 Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
diff --git a/third_party/git/t/t4013/diff.log_--diff-merges=off_-p_--first-parent_master b/third_party/git/t/t4013/diff.log_--diff-merges=off_-p_--first-parent_master
new file mode 100644
index 0000000000..194e893c94
--- /dev/null
+++ b/third_party/git/t/t4013/diff.log_--diff-merges=off_-p_--first-parent_master
@@ -0,0 +1,78 @@
+$ git log --diff-merges=off -p --first-parent master
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
+Merge: 9a6d494 c7a2ab9
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:04:00 2006 +0000
+
+    Merge branch 'side'
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:02:00 2006 +0000
+
+    Third
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:01:00 2006 +0000
+
+    Second
+    
+    This is the second commit.
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:00:00 2006 +0000
+
+    Initial
+$
diff --git a/third_party/git/t/t4013/diff.log_--first-parent_--diff-merges=off_-p_master b/third_party/git/t/t4013/diff.log_--first-parent_--diff-merges=off_-p_master
new file mode 100644
index 0000000000..5d7461a167
--- /dev/null
+++ b/third_party/git/t/t4013/diff.log_--first-parent_--diff-merges=off_-p_master
@@ -0,0 +1,78 @@
+$ git log --first-parent --diff-merges=off -p master
+commit 80e25ffa65bcdbe82ef654b4d06dbbde7945c37f
+Merge: 9a6d494 c7a2ab9
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:04:00 2006 +0000
+
+    Merge branch 'side'
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:02:00 2006 +0000
+
+    Third
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:01:00 2006 +0000
+
+    Second
+    
+    This is the second commit.
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:00:00 2006 +0000
+
+    Initial
+$
diff --git a/third_party/git/t/t4013/diff.log_--no-diff-merges_-p_--first-parent_master b/third_party/git/t/t4013/diff.log_--no-diff-merges_-p_--first-parent_master
new file mode 100644
index 0000000000..597002232e
--- /dev/null
+++ b/third_party/git/t/t4013/diff.log_--no-diff-merges_-p_--first-parent_master
@@ -0,0 +1,78 @@
+$ git log --no-diff-merges -p --first-parent master
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
+Merge: 9a6d494 c7a2ab9
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:04:00 2006 +0000
+
+    Merge branch 'side'
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:02:00 2006 +0000
+
+    Third
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:01:00 2006 +0000
+
+    Second
+    
+    This is the second commit.
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:00:00 2006 +0000
+
+    Initial
+$
diff --git a/third_party/git/t/t4013/diff.log_-p_--first-parent_master b/third_party/git/t/t4013/diff.log_-p_--first-parent_master
index 3fc896d424..28840ebea1 100644
--- a/third_party/git/t/t4013/diff.log_-p_--first-parent_master
+++ b/third_party/git/t/t4013/diff.log_-p_--first-parent_master
@@ -6,6 +6,28 @@ Date:   Mon Jun 26 00:04:00 2006 +0000
 
     Merge branch 'side'
 
+diff --git a/dir/sub b/dir/sub
+index cead32e..992913c 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -4,3 +4,5 @@ C
+ D
+ E
+ F
++1
++2
+diff --git a/file0 b/file0
+index b414108..10a8a9f 100644
+--- a/file0
++++ b/file0
+@@ -4,3 +4,6 @@
+ 4
+ 5
+ 6
++A
++B
++C
+
 commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:02:00 2006 +0000
diff --git a/third_party/git/t/t4014-format-patch.sh b/third_party/git/t/t4014-format-patch.sh
index ca7debf1d4..294e76c860 100755
--- a/third_party/git/t/t4014-format-patch.sh
+++ b/third_party/git/t/t4014-format-patch.sh
@@ -9,7 +9,6 @@ test_description='various format-patch tests'
 . "$TEST_DIRECTORY"/lib-terminal.sh
 
 test_expect_success setup '
-
 	for i in 1 2 3 4 5 6 7 8 9 10; do echo "$i"; done >file &&
 	cat file >elif &&
 	git add file elif &&
@@ -34,7 +33,8 @@ test_expect_success setup '
 	git commit -m "Side changes #3 with \\n backslash-n in it." &&
 
 	git checkout master &&
-	git diff-tree -p C2 | git apply --index &&
+	git diff-tree -p C2 >patch &&
+	git apply --index <patch &&
 	test_tick &&
 	git commit -m "Master accepts moral equivalent of #2" &&
 
@@ -59,185 +59,170 @@ test_expect_success setup '
 	git checkout master
 '
 
-test_expect_success "format-patch --ignore-if-in-upstream" '
-
+test_expect_success 'format-patch --ignore-if-in-upstream' '
 	git format-patch --stdout master..side >patch0 &&
-	cnt=$(grep "^From " patch0 | wc -l) &&
-	test $cnt = 3
-
+	grep "^From " patch0 >from0 &&
+	test_line_count = 3 from0
 '
 
-test_expect_success "format-patch --ignore-if-in-upstream" '
-
+test_expect_success 'format-patch --ignore-if-in-upstream' '
 	git format-patch --stdout \
 		--ignore-if-in-upstream master..side >patch1 &&
-	cnt=$(grep "^From " patch1 | wc -l) &&
-	test $cnt = 2
-
+	grep "^From " patch1 >from1 &&
+	test_line_count = 2 from1
 '
 
-test_expect_success "format-patch --ignore-if-in-upstream handles tags" '
+test_expect_success 'format-patch --ignore-if-in-upstream handles tags' '
 	git tag -a v1 -m tag side &&
 	git tag -a v2 -m tag master &&
 	git format-patch --stdout --ignore-if-in-upstream v2..v1 >patch1 &&
-	cnt=$(grep "^From " patch1 | wc -l) &&
-	test $cnt = 2
+	grep "^From " patch1 >from1 &&
+	test_line_count = 2 from1
 '
 
 test_expect_success "format-patch doesn't consider merge commits" '
-
-	git checkout -b slave master &&
+	git checkout -b feature master &&
 	echo "Another line" >>file &&
 	test_tick &&
-	git commit -am "Slave change #1" &&
+	git commit -am "Feature branch change #1" &&
 	echo "Yet another line" >>file &&
 	test_tick &&
-	git commit -am "Slave change #2" &&
+	git commit -am "Feature branch change #2" &&
 	git checkout -b merger master &&
 	test_tick &&
-	git merge --no-ff slave &&
-	cnt=$(git format-patch -3 --stdout | grep "^From " | wc -l) &&
-	test $cnt = 3
+	git merge --no-ff feature &&
+	git format-patch -3 --stdout >patch &&
+	grep "^From " patch >from &&
+	test_line_count = 3 from
 '
 
-test_expect_success "format-patch result applies" '
-
+test_expect_success 'format-patch result applies' '
 	git checkout -b rebuild-0 master &&
 	git am -3 patch0 &&
-	cnt=$(git rev-list master.. | wc -l) &&
-	test $cnt = 2
+	git rev-list master.. >list &&
+	test_line_count = 2 list
 '
 
-test_expect_success "format-patch --ignore-if-in-upstream result applies" '
-
+test_expect_success 'format-patch --ignore-if-in-upstream result applies' '
 	git checkout -b rebuild-1 master &&
 	git am -3 patch1 &&
-	cnt=$(git rev-list master.. | wc -l) &&
-	test $cnt = 2
+	git rev-list master.. >list &&
+	test_line_count = 2 list
 '
 
 test_expect_success 'commit did not screw up the log message' '
-
-	git cat-file commit side | grep "^Side .* with .* backslash-n"
-
+	git cat-file commit side >actual &&
+	grep "^Side .* with .* backslash-n" actual
 '
 
 test_expect_success 'format-patch did not screw up the log message' '
-
 	grep "^Subject: .*Side changes #3 with .* backslash-n" patch0 &&
 	grep "^Subject: .*Side changes #3 with .* backslash-n" patch1
-
 '
 
 test_expect_success 'replay did not screw up the log message' '
-
-	git cat-file commit rebuild-1 | grep "^Side .* with .* backslash-n"
-
+	git cat-file commit rebuild-1 >actual &&
+	grep "^Side .* with .* backslash-n" actual
 '
 
 test_expect_success 'extra headers' '
-
 	git config format.headers "To: R E Cipient <rcipient@example.com>
 " &&
 	git config --add format.headers "Cc: S E Cipient <scipient@example.com>
 " &&
-	git format-patch --stdout master..side > patch2 &&
-	sed -e "/^\$/q" patch2 > hdrs2 &&
+	git format-patch --stdout master..side >patch2 &&
+	sed -e "/^\$/q" patch2 >hdrs2 &&
 	grep "^To: R E Cipient <rcipient@example.com>\$" hdrs2 &&
 	grep "^Cc: S E Cipient <scipient@example.com>\$" hdrs2
-
 '
 
 test_expect_success 'extra headers without newlines' '
-
 	git config --replace-all format.headers "To: R E Cipient <rcipient@example.com>" &&
 	git config --add format.headers "Cc: S E Cipient <scipient@example.com>" &&
 	git format-patch --stdout master..side >patch3 &&
-	sed -e "/^\$/q" patch3 > hdrs3 &&
+	sed -e "/^\$/q" patch3 >hdrs3 &&
 	grep "^To: R E Cipient <rcipient@example.com>\$" hdrs3 &&
 	grep "^Cc: S E Cipient <scipient@example.com>\$" hdrs3
-
 '
 
 test_expect_success 'extra headers with multiple To:s' '
-
 	git config --replace-all format.headers "To: R E Cipient <rcipient@example.com>" &&
 	git config --add format.headers "To: S E Cipient <scipient@example.com>" &&
-	git format-patch --stdout master..side > patch4 &&
-	sed -e "/^\$/q" patch4 > hdrs4 &&
+	git format-patch --stdout master..side >patch4 &&
+	sed -e "/^\$/q" patch4 >hdrs4 &&
 	grep "^To: R E Cipient <rcipient@example.com>,\$" hdrs4 &&
 	grep "^ *S E Cipient <scipient@example.com>\$" hdrs4
 '
 
 test_expect_success 'additional command line cc (ascii)' '
-
 	git config --replace-all format.headers "Cc: R E Cipient <rcipient@example.com>" &&
-	git format-patch --cc="S E Cipient <scipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch5 &&
-	grep "^Cc: R E Cipient <rcipient@example.com>,\$" patch5 &&
-	grep "^ *S E Cipient <scipient@example.com>\$" patch5
+	git format-patch --cc="S E Cipient <scipient@example.com>" --stdout master..side >patch5 &&
+	sed -e "/^\$/q" patch5 >hdrs5 &&
+	grep "^Cc: R E Cipient <rcipient@example.com>,\$" hdrs5 &&
+	grep "^ *S E Cipient <scipient@example.com>\$" hdrs5
 '
 
 test_expect_failure 'additional command line cc (rfc822)' '
-
 	git config --replace-all format.headers "Cc: R E Cipient <rcipient@example.com>" &&
-	git format-patch --cc="S. E. Cipient <scipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch5 &&
-	grep "^Cc: R E Cipient <rcipient@example.com>,\$" patch5 &&
-	grep "^ *\"S. E. Cipient\" <scipient@example.com>\$" patch5
+	git format-patch --cc="S. E. Cipient <scipient@example.com>" --stdout master..side >patch5 &&
+	sed -e "/^\$/q" patch5 >hdrs5 &&
+	grep "^Cc: R E Cipient <rcipient@example.com>,\$" hdrs5 &&
+	grep "^ *\"S. E. Cipient\" <scipient@example.com>\$" hdrs5
 '
 
 test_expect_success 'command line headers' '
-
 	git config --unset-all format.headers &&
-	git format-patch --add-header="Cc: R E Cipient <rcipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch6 &&
-	grep "^Cc: R E Cipient <rcipient@example.com>\$" patch6
+	git format-patch --add-header="Cc: R E Cipient <rcipient@example.com>" --stdout master..side >patch6 &&
+	sed -e "/^\$/q" patch6 >hdrs6 &&
+	grep "^Cc: R E Cipient <rcipient@example.com>\$" hdrs6
 '
 
 test_expect_success 'configuration headers and command line headers' '
-
 	git config --replace-all format.headers "Cc: R E Cipient <rcipient@example.com>" &&
-	git format-patch --add-header="Cc: S E Cipient <scipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch7 &&
-	grep "^Cc: R E Cipient <rcipient@example.com>,\$" patch7 &&
-	grep "^ *S E Cipient <scipient@example.com>\$" patch7
+	git format-patch --add-header="Cc: S E Cipient <scipient@example.com>" --stdout master..side >patch7 &&
+	sed -e "/^\$/q" patch7 >hdrs7 &&
+	grep "^Cc: R E Cipient <rcipient@example.com>,\$" hdrs7 &&
+	grep "^ *S E Cipient <scipient@example.com>\$" hdrs7
 '
 
 test_expect_success 'command line To: header (ascii)' '
-
 	git config --unset-all format.headers &&
-	git format-patch --to="R E Cipient <rcipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch8 &&
-	grep "^To: R E Cipient <rcipient@example.com>\$" patch8
+	git format-patch --to="R E Cipient <rcipient@example.com>" --stdout master..side >patch8 &&
+	sed -e "/^\$/q" patch8 >hdrs8 &&
+	grep "^To: R E Cipient <rcipient@example.com>\$" hdrs8
 '
 
 test_expect_failure 'command line To: header (rfc822)' '
-
-	git format-patch --to="R. E. Cipient <rcipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch8 &&
-	grep "^To: \"R. E. Cipient\" <rcipient@example.com>\$" patch8
+	git format-patch --to="R. E. Cipient <rcipient@example.com>" --stdout master..side >patch8 &&
+	sed -e "/^\$/q" patch8 >hdrs8 &&
+	grep "^To: \"R. E. Cipient\" <rcipient@example.com>\$" hdrs8
 '
 
 test_expect_failure 'command line To: header (rfc2047)' '
-
-	git format-patch --to="R ร„ Cipient <rcipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch8 &&
-	grep "^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <rcipient@example.com>\$" patch8
+	git format-patch --to="R ร„ Cipient <rcipient@example.com>" --stdout master..side >patch8 &&
+	sed -e "/^\$/q" patch8 >hdrs8 &&
+	grep "^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <rcipient@example.com>\$" hdrs8
 '
 
 test_expect_success 'configuration To: header (ascii)' '
-
 	git config format.to "R E Cipient <rcipient@example.com>" &&
-	git format-patch --stdout master..side | sed -e "/^\$/q" >patch9 &&
-	grep "^To: R E Cipient <rcipient@example.com>\$" patch9
+	git format-patch --stdout master..side >patch9 &&
+	sed -e "/^\$/q" patch9 >hdrs9 &&
+	grep "^To: R E Cipient <rcipient@example.com>\$" hdrs9
 '
 
 test_expect_failure 'configuration To: header (rfc822)' '
-
 	git config format.to "R. E. Cipient <rcipient@example.com>" &&
-	git format-patch --stdout master..side | sed -e "/^\$/q" >patch9 &&
-	grep "^To: \"R. E. Cipient\" <rcipient@example.com>\$" patch9
+	git format-patch --stdout master..side >patch9 &&
+	sed -e "/^\$/q" patch9 >hdrs9 &&
+	grep "^To: \"R. E. Cipient\" <rcipient@example.com>\$" hdrs9
 '
 
 test_expect_failure 'configuration To: header (rfc2047)' '
-
 	git config format.to "R ร„ Cipient <rcipient@example.com>" &&
-	git format-patch --stdout master..side | sed -e "/^\$/q" >patch9 &&
-	grep "^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <rcipient@example.com>\$" patch9
+	git format-patch --stdout master..side >patch9 &&
+	sed -e "/^\$/q" patch9 >hdrs9 &&
+	grep "^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <rcipient@example.com>\$" hdrs9
 '
 
 # check_patch <patch>: Verify that <patch> looks like a half-sane
@@ -249,89 +234,79 @@ check_patch () {
 }
 
 test_expect_success 'format.from=false' '
-
-	git -c format.from=false format-patch --stdout master..side |
-	sed -e "/^\$/q" >patch &&
+	git -c format.from=false format-patch --stdout master..side >patch &&
+	sed -e "/^\$/q" patch >hdrs &&
 	check_patch patch &&
-	! grep "^From: C O Mitter <committer@example.com>\$" patch
+	! grep "^From: C O Mitter <committer@example.com>\$" hdrs
 '
 
 test_expect_success 'format.from=true' '
-
-	git -c format.from=true format-patch --stdout master..side |
-	sed -e "/^\$/q" >patch &&
-	check_patch patch &&
-	grep "^From: C O Mitter <committer@example.com>\$" patch
+	git -c format.from=true format-patch --stdout master..side >patch &&
+	sed -e "/^\$/q" patch >hdrs &&
+	check_patch hdrs &&
+	grep "^From: C O Mitter <committer@example.com>\$" hdrs
 '
 
 test_expect_success 'format.from with address' '
-
-	git -c format.from="F R Om <from@example.com>" format-patch --stdout master..side |
-	sed -e "/^\$/q" >patch &&
-	check_patch patch &&
-	grep "^From: F R Om <from@example.com>\$" patch
+	git -c format.from="F R Om <from@example.com>" format-patch --stdout master..side >patch &&
+	sed -e "/^\$/q" patch >hdrs &&
+	check_patch hdrs &&
+	grep "^From: F R Om <from@example.com>\$" hdrs
 '
 
 test_expect_success '--no-from overrides format.from' '
-
-	git -c format.from="F R Om <from@example.com>" format-patch --no-from --stdout master..side |
-	sed -e "/^\$/q" >patch &&
-	check_patch patch &&
-	! grep "^From: F R Om <from@example.com>\$" patch
+	git -c format.from="F R Om <from@example.com>" format-patch --no-from --stdout master..side >patch &&
+	sed -e "/^\$/q" patch >hdrs &&
+	check_patch hdrs &&
+	! grep "^From: F R Om <from@example.com>\$" hdrs
 '
 
 test_expect_success '--from overrides format.from' '
-
-	git -c format.from="F R Om <from@example.com>" format-patch --from --stdout master..side |
-	sed -e "/^\$/q" >patch &&
-	check_patch patch &&
-	! grep "^From: F R Om <from@example.com>\$" patch
+	git -c format.from="F R Om <from@example.com>" format-patch --from --stdout master..side >patch &&
+	sed -e "/^\$/q" patch >hdrs &&
+	check_patch hdrs &&
+	! grep "^From: F R Om <from@example.com>\$" hdrs
 '
 
 test_expect_success '--no-to overrides config.to' '
-
 	git config --replace-all format.to \
 		"R E Cipient <rcipient@example.com>" &&
-	git format-patch --no-to --stdout master..side |
-	sed -e "/^\$/q" >patch10 &&
-	check_patch patch10 &&
-	! grep "^To: R E Cipient <rcipient@example.com>\$" patch10
+	git format-patch --no-to --stdout master..side >patch10 &&
+	sed -e "/^\$/q" patch10 >hdrs10 &&
+	check_patch hdrs10 &&
+	! grep "^To: R E Cipient <rcipient@example.com>\$" hdrs10
 '
 
 test_expect_success '--no-to and --to replaces config.to' '
-
 	git config --replace-all format.to \
 		"Someone <someone@out.there>" &&
 	git format-patch --no-to --to="Someone Else <else@out.there>" \
-		--stdout master..side |
-	sed -e "/^\$/q" >patch11 &&
-	check_patch patch11 &&
-	! grep "^To: Someone <someone@out.there>\$" patch11 &&
-	grep "^To: Someone Else <else@out.there>\$" patch11
+		--stdout master..side >patch11 &&
+	sed -e "/^\$/q" patch11 >hdrs11 &&
+	check_patch hdrs11 &&
+	! grep "^To: Someone <someone@out.there>\$" hdrs11 &&
+	grep "^To: Someone Else <else@out.there>\$" hdrs11
 '
 
 test_expect_success '--no-cc overrides config.cc' '
-
 	git config --replace-all format.cc \
 		"C E Cipient <rcipient@example.com>" &&
-	git format-patch --no-cc --stdout master..side |
-	sed -e "/^\$/q" >patch12 &&
-	check_patch patch12 &&
-	! grep "^Cc: C E Cipient <rcipient@example.com>\$" patch12
+	git format-patch --no-cc --stdout master..side >patch12 &&
+	sed -e "/^\$/q" patch12 >hdrs12 &&
+	check_patch hdrs12 &&
+	! grep "^Cc: C E Cipient <rcipient@example.com>\$" hdrs12
 '
 
 test_expect_success '--no-add-header overrides config.headers' '
-
 	git config --replace-all format.headers \
 		"Header1: B E Cipient <rcipient@example.com>" &&
-	git format-patch --no-add-header --stdout master..side |
-	sed -e "/^\$/q" >patch13 &&
-	check_patch patch13 &&
-	! grep "^Header1: B E Cipient <rcipient@example.com>\$" patch13
+	git format-patch --no-add-header --stdout master..side >patch13 &&
+	sed -e "/^\$/q" patch13 >hdrs13 &&
+	check_patch hdrs13 &&
+	! grep "^Header1: B E Cipient <rcipient@example.com>\$" hdrs13
 '
 
 test_expect_success 'multiple files' '
-
 	rm -rf patches/ &&
 	git checkout side &&
 	git format-patch -o patches/ master &&
@@ -357,7 +332,7 @@ test_expect_success 'reroll count (-v)' '
 check_threading () {
 	expect="$1" &&
 	shift &&
-	(git format-patch --stdout "$@"; echo $? > status.out) |
+	git format-patch --stdout "$@" >patch &&
 	# Prints everything between the Message-ID and In-Reply-To,
 	# and replaces all Message-ID-lookalikes by a sequence number
 	perl -ne '
@@ -372,12 +347,11 @@ check_threading () {
 			print;
 		}
 		print "---\n" if /^From /i;
-	' > actual &&
-	test 0 = "$(cat status.out)" &&
+	' <patch >actual &&
 	test_cmp "$expect" actual
 }
 
-cat >> expect.no-threading <<EOF
+cat >>expect.no-threading <<EOF
 ---
 ---
 ---
@@ -388,7 +362,7 @@ test_expect_success 'no threading' '
 	check_threading expect.no-threading master
 '
 
-cat > expect.thread <<EOF
+cat >expect.thread <<EOF
 ---
 Message-Id: <0>
 ---
@@ -405,7 +379,7 @@ test_expect_success 'thread' '
 	check_threading expect.thread --thread master
 '
 
-cat > expect.in-reply-to <<EOF
+cat >expect.in-reply-to <<EOF
 ---
 Message-Id: <0>
 In-Reply-To: <1>
@@ -425,7 +399,7 @@ test_expect_success 'thread in-reply-to' '
 		--thread master
 '
 
-cat > expect.cover-letter <<EOF
+cat >expect.cover-letter <<EOF
 ---
 Message-Id: <0>
 ---
@@ -446,7 +420,7 @@ test_expect_success 'thread cover-letter' '
 	check_threading expect.cover-letter --cover-letter --thread master
 '
 
-cat > expect.cl-irt <<EOF
+cat >expect.cl-irt <<EOF
 ---
 Message-Id: <0>
 In-Reply-To: <1>
@@ -478,7 +452,7 @@ test_expect_success 'thread explicit shallow' '
 		--in-reply-to="<test.message>" --thread=shallow master
 '
 
-cat > expect.deep <<EOF
+cat >expect.deep <<EOF
 ---
 Message-Id: <0>
 ---
@@ -496,7 +470,7 @@ test_expect_success 'thread deep' '
 	check_threading expect.deep --thread=deep master
 '
 
-cat > expect.deep-irt <<EOF
+cat >expect.deep-irt <<EOF
 ---
 Message-Id: <0>
 In-Reply-To: <1>
@@ -519,7 +493,7 @@ test_expect_success 'thread deep in-reply-to' '
 		--in-reply-to="<test.message>" master
 '
 
-cat > expect.deep-cl <<EOF
+cat >expect.deep-cl <<EOF
 ---
 Message-Id: <0>
 ---
@@ -543,7 +517,7 @@ test_expect_success 'thread deep cover-letter' '
 	check_threading expect.deep-cl --cover-letter --thread=deep master
 '
 
-cat > expect.deep-cl-irt <<EOF
+cat >expect.deep-cl-irt <<EOF
 ---
 Message-Id: <0>
 In-Reply-To: <1>
@@ -594,7 +568,6 @@ test_expect_success 'thread config + --no-thread' '
 '
 
 test_expect_success 'excessive subject' '
-
 	rm -rf patches/ &&
 	git checkout side &&
 	before=$(git hash-object file) &&
@@ -622,10 +595,9 @@ test_expect_success 'cover-letter inherits diff options' '
 	! grep "file => foo .* 0 *\$" 0000-cover-letter.patch &&
 	git format-patch --cover-letter -1 -M &&
 	grep "file => foo .* 0 *\$" 0000-cover-letter.patch
-
 '
 
-cat > expect << EOF
+cat >expect <<EOF
   This is an excessively long subject line for a message due to the
     habit some projects have of not having a short, one-line subject at
     the start of the commit message, but rather sticking a whole
@@ -636,14 +608,12 @@ cat > expect << EOF
 EOF
 
 test_expect_success 'shortlog of cover-letter wraps overly-long onelines' '
-
 	git format-patch --cover-letter -2 &&
-	sed -e "1,/A U Thor/d" -e "/^\$/q" < 0000-cover-letter.patch > output &&
+	sed -e "1,/A U Thor/d" -e "/^\$/q" 0000-cover-letter.patch >output &&
 	test_cmp expect output
-
 '
 
-cat > expect << EOF
+cat >expect <<EOF
 index $before..$after 100644
 --- a/file
 +++ b/file
@@ -656,16 +626,14 @@ index $before..$after 100644
 EOF
 
 test_expect_success 'format-patch respects -U' '
-
 	git format-patch -U4 -2 &&
 	sed -e "1,/^diff/d" -e "/^+5/q" \
 		<0001-This-is-an-excessively-long-subject-line-for-a-messa.patch \
 		>output &&
 	test_cmp expect output
-
 '
 
-cat > expect << EOF
+cat >expect <<EOF
 
 diff --git a/file b/file
 index $before..$after 100644
@@ -679,11 +647,9 @@ index $before..$after 100644
 EOF
 
 test_expect_success 'format-patch -p suppresses stat' '
-
 	git format-patch -p -2 &&
-	sed -e "1,/^\$/d" -e "/^+5/q" < 0001-This-is-an-excessively-long-subject-line-for-a-messa.patch > output &&
+	sed -e "1,/^\$/d" -e "/^+5/q" 0001-This-is-an-excessively-long-subject-line-for-a-messa.patch >output &&
 	test_cmp expect output
-
 '
 
 test_expect_success 'format-patch from a subdirectory (1)' '
@@ -736,7 +702,7 @@ test_expect_success 'format-patch from a subdirectory (3)' '
 '
 
 test_expect_success 'format-patch --in-reply-to' '
-	git format-patch -1 --stdout --in-reply-to "baz@foo.bar" > patch8 &&
+	git format-patch -1 --stdout --in-reply-to "baz@foo.bar" >patch8 &&
 	grep "^In-Reply-To: <baz@foo.bar>" patch8 &&
 	grep "^References: <baz@foo.bar>" patch8
 '
@@ -827,21 +793,56 @@ test_expect_success 'format-patch with multiple notes refs' '
 	! grep "this is note 2" out
 '
 
-echo "fatal: --name-only does not make sense" > expect.name-only
-echo "fatal: --name-status does not make sense" > expect.name-status
-echo "fatal: --check does not make sense" > expect.check
+test_expect_success 'format-patch with multiple notes refs in config' '
+	test_when_finished "test_unconfig format.notes" &&
+
+	git notes --ref note1 add -m "this is note 1" HEAD &&
+	test_when_finished git notes --ref note1 remove HEAD &&
+	git notes --ref note2 add -m "this is note 2" HEAD &&
+	test_when_finished git notes --ref note2 remove HEAD &&
+
+	git config format.notes note1 &&
+	git format-patch -1 --stdout >out &&
+	grep "this is note 1" out &&
+	! grep "this is note 2" out &&
+	git config format.notes note2 &&
+	git format-patch -1 --stdout >out &&
+	! grep "this is note 1" out &&
+	grep "this is note 2" out &&
+	git config --add format.notes note1 &&
+	git format-patch -1 --stdout >out &&
+	grep "this is note 1" out &&
+	grep "this is note 2" out &&
+
+	git config --replace-all format.notes note1 &&
+	git config --add format.notes false &&
+	git format-patch -1 --stdout >out &&
+	! grep "this is note 1" out &&
+	! grep "this is note 2" out &&
+	git config --add format.notes note2 &&
+	git format-patch -1 --stdout >out &&
+	! grep "this is note 1" out &&
+	grep "this is note 2" out
+'
+
+echo "fatal: --name-only does not make sense" >expect.name-only
+echo "fatal: --name-status does not make sense" >expect.name-status
+echo "fatal: --check does not make sense" >expect.check
 
 test_expect_success 'options no longer allowed for format-patch' '
-	test_must_fail git format-patch --name-only 2> output &&
+	test_must_fail git format-patch --name-only 2>output &&
 	test_i18ncmp expect.name-only output &&
-	test_must_fail git format-patch --name-status 2> output &&
+	test_must_fail git format-patch --name-status 2>output &&
 	test_i18ncmp expect.name-status output &&
-	test_must_fail git format-patch --check 2> output &&
-	test_i18ncmp expect.check output'
+	test_must_fail git format-patch --check 2>output &&
+	test_i18ncmp expect.check output
+'
 
 test_expect_success 'format-patch --numstat should produce a patch' '
-	git format-patch --numstat --stdout master..side > output &&
-	test 5 = $(grep "^diff --git a/" output | wc -l)'
+	git format-patch --numstat --stdout master..side >output &&
+	grep "^diff --git a/" output >diff &&
+	test_line_count = 5 diff
+'
 
 test_expect_success 'format-patch -- <path>' '
 	git format-patch master..side -- file 2>error &&
@@ -852,20 +853,25 @@ test_expect_success 'format-patch --ignore-if-in-upstream HEAD' '
 	git format-patch --ignore-if-in-upstream HEAD
 '
 
-git_version="$(git --version | sed "s/.* //")"
+test_expect_success 'get git version' '
+	git_version=$(git --version) &&
+	git_version=${git_version##* }
+'
 
 signature() {
 	printf "%s\n%s\n\n" "-- " "${1:-$git_version}"
 }
 
 test_expect_success 'format-patch default signature' '
-	git format-patch --stdout -1 | tail -n 3 >output &&
+	git format-patch --stdout -1 >patch &&
+	tail -n 3 patch >output &&
 	signature >expect &&
 	test_cmp expect output
 '
 
 test_expect_success 'format-patch --signature' '
-	git format-patch --stdout --signature="my sig" -1 | tail -n 3 >output &&
+	git format-patch --stdout --signature="my sig" -1 >patch &&
+	tail -n 3 patch >output &&
 	signature "my sig" >expect &&
 	test_cmp expect output
 '
@@ -897,8 +903,8 @@ test_expect_success 'format-patch --signature --cover-letter' '
 	git config --unset-all format.signature &&
 	git format-patch --stdout --signature="my sig" --cover-letter \
 		-1 >output &&
-	grep "my sig" output &&
-	test 2 = $(grep "my sig" output | wc -l)
+	grep "my sig" output >sig &&
+	test_line_count = 2 sig
 '
 
 test_expect_success 'format.signature="" suppresses signatures' '
@@ -935,7 +941,7 @@ test_expect_success 'prepare mail-signature input' '
 test_expect_success '--signature-file=file works' '
 	git format-patch --stdout --signature-file=mail-signature -1 >output &&
 	check_patch output &&
-	sed -e "1,/^-- \$/d" <output >actual &&
+	sed -e "1,/^-- \$/d" output >actual &&
 	{
 		cat mail-signature && echo
 	} >expect &&
@@ -946,7 +952,7 @@ test_expect_success 'format.signaturefile works' '
 	test_config format.signaturefile mail-signature &&
 	git format-patch --stdout -1 >output &&
 	check_patch output &&
-	sed -e "1,/^-- \$/d" <output >actual &&
+	sed -e "1,/^-- \$/d" output >actual &&
 	{
 		cat mail-signature && echo
 	} >expect &&
@@ -968,7 +974,7 @@ test_expect_success '--signature-file overrides format.signaturefile' '
 	git format-patch --stdout \
 			--signature-file=other-mail-signature -1 >output &&
 	check_patch output &&
-	sed -e "1,/^-- \$/d" <output >actual &&
+	sed -e "1,/^-- \$/d" output >actual &&
 	{
 		cat other-mail-signature && echo
 	} >expect &&
@@ -1037,7 +1043,7 @@ test_expect_success 'format-patch wraps extremely long subject (ascii)' '
 	git add file &&
 	git commit -m "$M512" &&
 	git format-patch --stdout -1 >patch &&
-	sed -n "/^Subject/p; /^ /p; /^$/q" <patch >subject &&
+	sed -n "/^Subject/p; /^ /p; /^$/q" patch >subject &&
 	test_cmp expect subject
 '
 
@@ -1076,7 +1082,7 @@ test_expect_success 'format-patch wraps extremely long subject (rfc2047)' '
 	git add file &&
 	git commit -m "$M512" &&
 	git format-patch --stdout -1 >patch &&
-	sed -n "/^Subject/p; /^ /p; /^$/q" <patch >subject &&
+	sed -n "/^Subject/p; /^ /p; /^$/q" patch >subject &&
 	test_cmp expect subject
 '
 
@@ -1085,7 +1091,7 @@ check_author() {
 	git add file &&
 	GIT_AUTHOR_NAME=$1 git commit -m author-check &&
 	git format-patch --stdout -1 >patch &&
-	sed -n "/^From: /p; /^ /p; /^$/q" <patch >actual &&
+	sed -n "/^From: /p; /^ /p; /^$/q" patch >actual &&
 	test_cmp expect actual
 }
 
@@ -1155,6 +1161,59 @@ test_expect_success 'format-patch wraps extremely long from-header (rfc2047)' '
 '
 
 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
 test_expect_success 'subject lines do not have 822 atom-quoting' '
@@ -1205,7 +1264,7 @@ test_expect_success '--from=ident replaces author' '
 	From: A U Thor <author@example.com>
 
 	EOF
-	sed -ne "/^From:/p; /^$/p; /^---$/q" <patch >patch.head &&
+	sed -ne "/^From:/p; /^$/p; /^---$/q" patch >patch.head &&
 	test_cmp expect patch.head
 '
 
@@ -1217,7 +1276,7 @@ test_expect_success '--from uses committer ident' '
 	From: A U Thor <author@example.com>
 
 	EOF
-	sed -ne "/^From:/p; /^$/p; /^---$/q" <patch >patch.head &&
+	sed -ne "/^From:/p; /^$/p; /^---$/q" patch >patch.head &&
 	test_cmp expect patch.head
 '
 
@@ -1227,7 +1286,7 @@ test_expect_success '--from omits redundant in-body header' '
 	From: A U Thor <author@example.com>
 
 	EOF
-	sed -ne "/^From:/p; /^$/p; /^---$/q" <patch >patch.head &&
+	sed -ne "/^From:/p; /^$/p; /^---$/q" patch >patch.head &&
 	test_cmp expect patch.head
 '
 
@@ -1242,7 +1301,7 @@ test_expect_success 'in-body headers trigger content encoding' '
 	From: รฉxรถtรฌc <author@example.com>
 
 	EOF
-	sed -ne "/^From:/p; /^$/p; /^Content-Type/p; /^---$/q" <patch >patch.head &&
+	sed -ne "/^From:/p; /^$/p; /^Content-Type/p; /^---$/q" patch >patch.head &&
 	test_cmp expect patch.head
 '
 
@@ -1256,283 +1315,283 @@ append_signoff()
 
 test_expect_success 'signoff: commit with no body' '
 	append_signoff </dev/null >actual &&
-	cat <<\EOF | sed "s/EOL$//" >expected &&
-4:Subject: [PATCH] EOL
-8:
-9:Signed-off-by: C O Mitter <committer@example.com>
-EOF
-	test_cmp expected actual
+	cat <<-\EOF | sed "s/EOL$//" >expect &&
+	4:Subject: [PATCH] EOL
+	8:
+	9:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
+	test_cmp expect actual
 '
 
 test_expect_success 'signoff: commit with only subject' '
 	echo subject | append_signoff >actual &&
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-9:Signed-off-by: C O Mitter <committer@example.com>
-EOF
-	test_cmp expected actual
+	cat >expect <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	9:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
+	test_cmp expect actual
 '
 
 test_expect_success 'signoff: commit with only subject that does not end with NL' '
 	printf subject | append_signoff >actual &&
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-9:Signed-off-by: C O Mitter <committer@example.com>
-EOF
-	test_cmp expected actual
+	cat >expect <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	9:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
+	test_cmp expect actual
 '
 
 test_expect_success 'signoff: no existing signoffs' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-body
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-11:Signed-off-by: C O Mitter <committer@example.com>
-EOF
-	test_cmp expected actual
+	body
+	EOF
+	cat >expect <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	11:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
+	test_cmp expect actual
 '
 
 test_expect_success 'signoff: no existing signoffs and no trailing NL' '
 	printf "subject\n\nbody" | append_signoff >actual &&
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-11:Signed-off-by: C O Mitter <committer@example.com>
-EOF
-	test_cmp expected actual
+	cat >expect <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	11:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
+	test_cmp expect actual
 '
 
 test_expect_success 'signoff: some random signoff' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-body
+	body
 
-Signed-off-by: my@house
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-11:Signed-off-by: my@house
-12:Signed-off-by: C O Mitter <committer@example.com>
-EOF
-	test_cmp expected actual
+	Signed-off-by: my@house
+	EOF
+	cat >expect <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	11:Signed-off-by: my@house
+	12:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
+	test_cmp expect actual
 '
 
 test_expect_success 'signoff: misc conforming footer elements' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-body
+	body
 
-Signed-off-by: my@house
-(cherry picked from commit da39a3ee5e6b4b0d3255bfef95601890afd80709)
-Tested-by: Some One <someone@example.com>
-Bug: 1234
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-11:Signed-off-by: my@house
-15:Signed-off-by: C O Mitter <committer@example.com>
-EOF
-	test_cmp expected actual
+	Signed-off-by: my@house
+	(cherry picked from commit da39a3ee5e6b4b0d3255bfef95601890afd80709)
+	Tested-by: Some One <someone@example.com>
+	Bug: 1234
+	EOF
+	cat >expect <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	11:Signed-off-by: my@house
+	15:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
+	test_cmp expect actual
 '
 
 test_expect_success 'signoff: some random signoff-alike' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-body
-Fooled-by-me: my@house
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-11:
-12:Signed-off-by: C O Mitter <committer@example.com>
-EOF
-	test_cmp expected actual
+	body
+	Fooled-by-me: my@house
+	EOF
+	cat >expect <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	11:
+	12:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
+	test_cmp expect actual
 '
 
 test_expect_success 'signoff: not really a signoff' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-I want to mention about Signed-off-by: here.
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-9:I want to mention about Signed-off-by: here.
-10:
-11:Signed-off-by: C O Mitter <committer@example.com>
-EOF
-	test_cmp expected actual
+	I want to mention about Signed-off-by: here.
+	EOF
+	cat >expect <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	9:I want to mention about Signed-off-by: here.
+	10:
+	11:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
+	test_cmp expect actual
 '
 
 test_expect_success 'signoff: not really a signoff (2)' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-My unfortunate
-Signed-off-by: example happens to be wrapped here.
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:Signed-off-by: example happens to be wrapped here.
-11:Signed-off-by: C O Mitter <committer@example.com>
-EOF
-	test_cmp expected actual
+	My unfortunate
+	Signed-off-by: example happens to be wrapped here.
+	EOF
+	cat >expect <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:Signed-off-by: example happens to be wrapped here.
+	11:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
+	test_cmp expect actual
 '
 
 test_expect_success 'signoff: valid S-o-b paragraph in the middle' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-Signed-off-by: my@house
-Signed-off-by: your@house
+	Signed-off-by: my@house
+	Signed-off-by: your@house
 
-A lot of houses.
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-9:Signed-off-by: my@house
-10:Signed-off-by: your@house
-11:
-13:
-14:Signed-off-by: C O Mitter <committer@example.com>
-EOF
-	test_cmp expected actual
+	A lot of houses.
+	EOF
+	cat >expect <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	9:Signed-off-by: my@house
+	10:Signed-off-by: your@house
+	11:
+	13:
+	14:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
+	test_cmp expect actual
 '
 
 test_expect_success 'signoff: the same signoff at the end' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-body
+	body
 
-Signed-off-by: C O Mitter <committer@example.com>
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-11:Signed-off-by: C O Mitter <committer@example.com>
-EOF
-	test_cmp expected actual
+	Signed-off-by: C O Mitter <committer@example.com>
+	EOF
+	cat >expect <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	11:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
+	test_cmp expect actual
 '
 
 test_expect_success 'signoff: the same signoff at the end, no trailing NL' '
 	printf "subject\n\nSigned-off-by: C O Mitter <committer@example.com>" |
 		append_signoff >actual &&
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-9:Signed-off-by: C O Mitter <committer@example.com>
-EOF
-	test_cmp expected actual
+	cat >expect <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	9:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
+	test_cmp expect actual
 '
 
 test_expect_success 'signoff: the same signoff NOT at the end' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-body
+	body
 
-Signed-off-by: C O Mitter <committer@example.com>
-Signed-off-by: my@house
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-11:Signed-off-by: C O Mitter <committer@example.com>
-12:Signed-off-by: my@house
-EOF
-	test_cmp expected actual
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: my@house
+	EOF
+	cat >expect <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	11:Signed-off-by: C O Mitter <committer@example.com>
+	12:Signed-off-by: my@house
+	EOF
+	test_cmp expect actual
 '
 
 test_expect_success 'signoff: tolerate garbage in conforming footer' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-body
+	body
 
-Tested-by: my@house
-Some Trash
-Signed-off-by: C O Mitter <committer@example.com>
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-13:Signed-off-by: C O Mitter <committer@example.com>
-EOF
-	test_cmp expected actual
+	Tested-by: my@house
+	Some Trash
+	Signed-off-by: C O Mitter <committer@example.com>
+	EOF
+	cat >expect <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	13:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
+	test_cmp expect actual
 '
 
 test_expect_success 'signoff: respect trailer config' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-Myfooter: x
-Some Trash
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-11:
-12:Signed-off-by: C O Mitter <committer@example.com>
-EOF
-	test_cmp expected actual &&
+	Myfooter: x
+	Some Trash
+	EOF
+	cat >expect <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	11:
+	12:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
+	test_cmp expect actual &&
 
 	test_config trailer.Myfooter.ifexists add &&
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-Myfooter: x
-Some Trash
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-11:Signed-off-by: C O Mitter <committer@example.com>
-EOF
-	test_cmp expected actual
+	Myfooter: x
+	Some Trash
+	EOF
+	cat >expect <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	11:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
+	test_cmp expect actual
 '
 
 test_expect_success 'signoff: footer begins with non-signoff without @ sign' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-body
+	body
 
-Reviewed-id: Noone
-Tested-by: my@house
-Change-id: Ideadbeef
-Signed-off-by: C O Mitter <committer@example.com>
-Bug: 1234
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-14:Signed-off-by: C O Mitter <committer@example.com>
-EOF
-	test_cmp expected actual
+	Reviewed-id: Noone
+	Tested-by: my@house
+	Change-id: Ideadbeef
+	Signed-off-by: C O Mitter <committer@example.com>
+	Bug: 1234
+	EOF
+	cat >expect <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	14:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
+	test_cmp expect actual
 '
 
 test_expect_success 'format patch ignores color.ui' '
@@ -1543,46 +1602,231 @@ test_expect_success 'format patch ignores color.ui' '
 	test_cmp expect actual
 '
 
+test_expect_success 'format patch respects diff.relative' '
+	rm -rf subdir &&
+	mkdir subdir &&
+	echo other content >subdir/file2 &&
+	git add subdir/file2 &&
+	git commit -F msg &&
+	test_unconfig diff.relative &&
+	git format-patch --relative=subdir --stdout -1 >expect &&
+	test_config diff.relative true &&
+	git -C subdir format-patch --stdout -1 >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'cover letter with invalid --cover-from-description and config' '
+	test_config branch.rebuild-1.description "config subject
+
+body" &&
+	test_must_fail git format-patch --cover-letter --cover-from-description garbage master &&
+	test_config format.coverFromDescription garbage &&
+	test_must_fail git format-patch --cover-letter master
+'
+
+test_expect_success 'cover letter with format.coverFromDescription = default' '
+	test_config branch.rebuild-1.description "config subject
+
+body" &&
+	test_config format.coverFromDescription default &&
+	git checkout rebuild-1 &&
+	git format-patch --stdout --cover-letter master >actual &&
+	grep "^Subject: \[PATCH 0/2\] \*\*\* SUBJECT HERE \*\*\*$" actual &&
+	! grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
+	grep "^config subject$" actual &&
+	grep "^body$" actual
+'
+
+test_expect_success 'cover letter with --cover-from-description default' '
+	test_config branch.rebuild-1.description "config subject
+
+body" &&
+	git checkout rebuild-1 &&
+	git format-patch --stdout --cover-letter --cover-from-description default master >actual &&
+	grep "^Subject: \[PATCH 0/2\] \*\*\* SUBJECT HERE \*\*\*$" actual &&
+	! grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
+	grep "^config subject$" actual &&
+	grep "^body$" actual
+'
+
+test_expect_success 'cover letter with format.coverFromDescription = none' '
+	test_config branch.rebuild-1.description "config subject
+
+body" &&
+	test_config format.coverFromDescription none &&
+	git checkout rebuild-1 &&
+	git format-patch --stdout --cover-letter master >actual &&
+	grep "^Subject: \[PATCH 0/2\] \*\*\* SUBJECT HERE \*\*\*$" actual &&
+	grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
+	! grep "^config subject$" actual &&
+	! grep "^body$" actual
+'
+
+test_expect_success 'cover letter with --cover-from-description none' '
+	test_config branch.rebuild-1.description "config subject
+
+body" &&
+	git checkout rebuild-1 &&
+	git format-patch --stdout --cover-letter --cover-from-description none master >actual &&
+	grep "^Subject: \[PATCH 0/2\] \*\*\* SUBJECT HERE \*\*\*$" actual &&
+	grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
+	! grep "^config subject$" actual &&
+	! grep "^body$" actual
+'
+
+test_expect_success 'cover letter with format.coverFromDescription = message' '
+	test_config branch.rebuild-1.description "config subject
+
+body" &&
+	test_config format.coverFromDescription message &&
+	git checkout rebuild-1 &&
+	git format-patch --stdout --cover-letter master >actual &&
+	grep "^Subject: \[PATCH 0/2\] \*\*\* SUBJECT HERE \*\*\*$" actual &&
+	! grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
+	grep "^config subject$" actual &&
+	grep "^body$" actual
+'
+
+test_expect_success 'cover letter with --cover-from-description message' '
+	test_config branch.rebuild-1.description "config subject
+
+body" &&
+	git checkout rebuild-1 &&
+	git format-patch --stdout --cover-letter --cover-from-description message master >actual &&
+	grep "^Subject: \[PATCH 0/2\] \*\*\* SUBJECT HERE \*\*\*$" actual &&
+	! grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
+	grep "^config subject$" actual &&
+	grep "^body$" actual
+'
+
+test_expect_success 'cover letter with format.coverFromDescription = subject' '
+	test_config branch.rebuild-1.description "config subject
+
+body" &&
+	test_config format.coverFromDescription subject &&
+	git checkout rebuild-1 &&
+	git format-patch --stdout --cover-letter master >actual &&
+	grep "^Subject: \[PATCH 0/2\] config subject$" actual &&
+	! grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
+	! grep "^config subject$" actual &&
+	grep "^body$" actual
+'
+
+test_expect_success 'cover letter with --cover-from-description subject' '
+	test_config branch.rebuild-1.description "config subject
+
+body" &&
+	git checkout rebuild-1 &&
+	git format-patch --stdout --cover-letter --cover-from-description subject master >actual &&
+	grep "^Subject: \[PATCH 0/2\] config subject$" actual &&
+	! grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
+	! grep "^config subject$" actual &&
+	grep "^body$" actual
+'
+
+test_expect_success 'cover letter with format.coverFromDescription = auto (short subject line)' '
+	test_config branch.rebuild-1.description "config subject
+
+body" &&
+	test_config format.coverFromDescription auto &&
+	git checkout rebuild-1 &&
+	git format-patch --stdout --cover-letter master >actual &&
+	grep "^Subject: \[PATCH 0/2\] config subject$" actual &&
+	! grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
+	! grep "^config subject$" actual &&
+	grep "^body$" actual
+'
+
+test_expect_success 'cover letter with --cover-from-description auto (short subject line)' '
+	test_config branch.rebuild-1.description "config subject
+
+body" &&
+	git checkout rebuild-1 &&
+	git format-patch --stdout --cover-letter --cover-from-description auto master >actual &&
+	grep "^Subject: \[PATCH 0/2\] config subject$" actual &&
+	! grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
+	! grep "^config subject$" actual &&
+	grep "^body$" actual
+'
+
+test_expect_success 'cover letter with format.coverFromDescription = auto (long subject line)' '
+	test_config branch.rebuild-1.description "this is a really long first line and it is over 100 characters long which is the threshold for long subjects
+
+body" &&
+	test_config format.coverFromDescription auto &&
+	git checkout rebuild-1 &&
+	git format-patch --stdout --cover-letter master >actual &&
+	grep "^Subject: \[PATCH 0/2\] \*\*\* SUBJECT HERE \*\*\*$" actual &&
+	! grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
+	grep "^this is a really long first line and it is over 100 characters long which is the threshold for long subjects$" actual &&
+	grep "^body$" actual
+'
+
+test_expect_success 'cover letter with --cover-from-description auto (long subject line)' '
+	test_config branch.rebuild-1.description "this is a really long first line and it is over 100 characters long which is the threshold for long subjects
+
+body" &&
+	git checkout rebuild-1 &&
+	git format-patch --stdout --cover-letter --cover-from-description auto master >actual &&
+	grep "^Subject: \[PATCH 0/2\] \*\*\* SUBJECT HERE \*\*\*$" actual &&
+	! grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
+	grep "^this is a really long first line and it is over 100 characters long which is the threshold for long subjects$" actual &&
+	grep "^body$" actual
+'
+
+test_expect_success 'cover letter with command-line --cover-from-description overrides config' '
+	test_config branch.rebuild-1.description "config subject
+
+body" &&
+	test_config format.coverFromDescription none &&
+	git checkout rebuild-1 &&
+	git format-patch --stdout --cover-letter --cover-from-description subject master >actual &&
+	grep "^Subject: \[PATCH 0/2\] config subject$" actual &&
+	! grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
+	! grep "^config subject$" actual &&
+	grep "^body$" actual
+'
+
 test_expect_success 'cover letter using branch description (1)' '
 	git checkout rebuild-1 &&
 	test_config branch.rebuild-1.description hello &&
 	git format-patch --stdout --cover-letter master >actual &&
-	grep hello actual >/dev/null
+	grep hello actual
 '
 
 test_expect_success 'cover letter using branch description (2)' '
 	git checkout rebuild-1 &&
 	test_config branch.rebuild-1.description hello &&
 	git format-patch --stdout --cover-letter rebuild-1~2..rebuild-1 >actual &&
-	grep hello actual >/dev/null
+	grep hello actual
 '
 
 test_expect_success 'cover letter using branch description (3)' '
 	git checkout rebuild-1 &&
 	test_config branch.rebuild-1.description hello &&
 	git format-patch --stdout --cover-letter ^master rebuild-1 >actual &&
-	grep hello actual >/dev/null
+	grep hello actual
 '
 
 test_expect_success 'cover letter using branch description (4)' '
 	git checkout rebuild-1 &&
 	test_config branch.rebuild-1.description hello &&
 	git format-patch --stdout --cover-letter master.. >actual &&
-	grep hello actual >/dev/null
+	grep hello actual
 '
 
 test_expect_success 'cover letter using branch description (5)' '
 	git checkout rebuild-1 &&
 	test_config branch.rebuild-1.description hello &&
 	git format-patch --stdout --cover-letter -2 HEAD >actual &&
-	grep hello actual >/dev/null
+	grep hello actual
 '
 
 test_expect_success 'cover letter using branch description (6)' '
 	git checkout rebuild-1 &&
 	test_config branch.rebuild-1.description hello &&
 	git format-patch --stdout --cover-letter -2 >actual &&
-	grep hello actual >/dev/null
+	grep hello actual
 '
 
 test_expect_success 'cover letter with nothing' '
@@ -1632,11 +1876,39 @@ test_expect_success 'From line has expected format' '
 	test_cmp from filtered
 '
 
+test_expect_success 'format-patch -o with no leading directories' '
+	rm -fr patches &&
+	git format-patch -o patches master..side &&
+	count=$(git rev-list --count master..side) &&
+	ls patches >list &&
+	test_line_count = $count list
+'
+
+test_expect_success 'format-patch -o with leading existing directories' '
+	rm -rf existing-dir &&
+	mkdir existing-dir &&
+	git format-patch -o existing-dir/patches master..side &&
+	count=$(git rev-list --count master..side) &&
+	ls existing-dir/patches >list &&
+	test_line_count = $count list
+'
+
+test_expect_success 'format-patch -o with leading non-existing directories' '
+	rm -rf non-existing-dir &&
+	git format-patch -o non-existing-dir/patches master..side &&
+	count=$(git rev-list --count master..side) &&
+	test_path_is_dir non-existing-dir &&
+	ls non-existing-dir/patches >list &&
+	test_line_count = $count list
+'
+
 test_expect_success 'format-patch format.outputDirectory option' '
 	test_config format.outputDirectory patches &&
 	rm -fr patches &&
 	git format-patch master..side &&
-	test $(git rev-list master..side | wc -l) -eq $(ls patches | wc -l)
+	count=$(git rev-list --count master..side) &&
+	ls patches >list &&
+	test_line_count = $count list
 '
 
 test_expect_success 'format-patch -o overrides format.outputDirectory' '
@@ -1649,20 +1921,41 @@ test_expect_success 'format-patch -o overrides format.outputDirectory' '
 
 test_expect_success 'format-patch --base' '
 	git checkout patchid &&
-	git format-patch --stdout --base=HEAD~3 -1 | tail -n 7 >actual1 &&
-	git format-patch --stdout --base=HEAD~3 HEAD~.. | tail -n 7 >actual2 &&
-	echo >expected &&
-	echo "base-commit: $(git rev-parse HEAD~3)" >>expected &&
-	echo "prerequisite-patch-id: $(git show --patch HEAD~2 | git patch-id --stable | awk "{print \$1}")" >>expected &&
-	echo "prerequisite-patch-id: $(git show --patch HEAD~1 | git patch-id --stable | awk "{print \$1}")" >>expected &&
-	signature >> expected &&
-	test_cmp expected actual1 &&
-	test_cmp expected actual2 &&
+
+	git format-patch --stdout --base=HEAD~3 -1 >patch &&
+	tail -n 7 patch >actual1 &&
+
+	git format-patch --stdout --base=HEAD~3 HEAD~.. >patch &&
+	tail -n 7 patch >actual2 &&
+
+	echo >expect &&
+	git rev-parse HEAD~3 >commit-id-base &&
+	echo "base-commit: $(cat commit-id-base)" >>expect &&
+
+	git show --patch HEAD~2 >patch &&
+	git patch-id --stable <patch >patch.id.raw &&
+	awk "{print \"prerequisite-patch-id:\", \$1}" <patch.id.raw >>expect &&
+
+	git show --patch HEAD~1 >patch &&
+	git patch-id --stable <patch >patch.id.raw &&
+	awk "{print \"prerequisite-patch-id:\", \$1}" <patch.id.raw >>expect &&
+
+	signature >>expect &&
+	test_cmp expect actual1 &&
+	test_cmp expect actual2 &&
+
 	echo >fail &&
-	echo "base-commit: $(git rev-parse HEAD~3)" >>fail &&
-	echo "prerequisite-patch-id: $(git show --patch HEAD~2 | git patch-id --unstable | awk "{print \$1}")" >>fail &&
-	echo "prerequisite-patch-id: $(git show --patch HEAD~1 | git patch-id --unstable | awk "{print \$1}")" >>fail &&
-	signature >> fail &&
+	echo "base-commit: $(cat commit-id-base)" >>fail &&
+
+	git show --patch HEAD~2 >patch &&
+	git patch-id --unstable <patch >patch.id.raw &&
+	awk "{print \"prerequisite-patch-id:\", \$1}" <patch.id.raw >>fail &&
+
+	git show --patch HEAD~1 >patch &&
+	git patch-id --unstable <patch >patch.id.raw &&
+	awk "{print \"prerequisite-patch-id:\", \$1}" <patch.id.raw >>fail &&
+
+	signature >>fail &&
 	! test_cmp fail actual1 &&
 	! test_cmp fail actual2
 '
@@ -1672,8 +1965,9 @@ test_expect_success 'format-patch --base errors out when base commit is in revis
 	test_must_fail git format-patch --base=HEAD~1 -2 &&
 	git format-patch --stdout --base=HEAD~2 -2 >patch &&
 	grep "^base-commit:" patch >actual &&
-	echo "base-commit: $(git rev-parse HEAD~2)" >expected &&
-	test_cmp expected actual
+	git rev-parse HEAD~2 >commit-id-base &&
+	echo "base-commit: $(cat commit-id-base)" >expect &&
+	test_cmp expect actual
 '
 
 test_expect_success 'format-patch --base errors out when base commit is not ancestor of revision list' '
@@ -1699,8 +1993,8 @@ test_expect_success 'format-patch --base errors out when base commit is not ance
 	test_must_fail git format-patch --base=$(cat commit-id-Z) -3 &&
 	git format-patch --stdout --base=$(cat commit-id-base) -3 >patch &&
 	grep "^base-commit:" patch >actual &&
-	echo "base-commit: $(cat commit-id-base)" >expected &&
-	test_cmp expected actual
+	echo "base-commit: $(cat commit-id-base)" >expect &&
+	test_cmp expect actual
 '
 
 test_expect_success 'format-patch --base=auto' '
@@ -1711,8 +2005,9 @@ test_expect_success 'format-patch --base=auto' '
 	test_commit N2 &&
 	git format-patch --stdout --base=auto -2 >patch &&
 	grep "^base-commit:" patch >actual &&
-	echo "base-commit: $(git rev-parse upstream)" >expected &&
-	test_cmp expected actual
+	git rev-parse upstream >commit-id-base &&
+	echo "base-commit: $(cat commit-id-base)" >expect &&
+	test_cmp expect actual
 '
 
 test_expect_success 'format-patch errors out when history involves criss-cross' '
@@ -1742,23 +2037,51 @@ test_expect_success 'format-patch errors out when history involves criss-cross'
 	test_must_fail 	git format-patch --base=auto -1
 '
 
-test_expect_success 'format-patch format.useAutoBaseoption' '
-	test_when_finished "git config --unset format.useAutoBase" &&
+test_expect_success 'format-patch format.useAutoBase whenAble history involves criss-cross' '
+	test_config format.useAutoBase whenAble &&
+	git format-patch -1 >patch &&
+	! grep "^base-commit:" patch
+'
+
+test_expect_success 'format-patch format.useAutoBase option' '
 	git checkout local &&
-	git config format.useAutoBase true &&
+	test_config format.useAutoBase true &&
 	git format-patch --stdout -1 >patch &&
 	grep "^base-commit:" patch >actual &&
-	echo "base-commit: $(git rev-parse upstream)" >expected &&
-	test_cmp expected actual
+	git rev-parse upstream >commit-id-base &&
+	echo "base-commit: $(cat commit-id-base)" >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success 'format-patch format.useAutoBase option with whenAble' '
+	git checkout local &&
+	test_config format.useAutoBase whenAble &&
+	git format-patch --stdout -1 >patch &&
+	grep "^base-commit:" patch >actual &&
+	git rev-parse upstream >commit-id-base &&
+	echo "base-commit: $(cat commit-id-base)" >expect &&
+	test_cmp expect actual
 '
 
 test_expect_success 'format-patch --base overrides format.useAutoBase' '
-	test_when_finished "git config --unset format.useAutoBase" &&
-	git config format.useAutoBase true &&
+	test_config format.useAutoBase true &&
 	git format-patch --stdout --base=HEAD~1 -1 >patch &&
 	grep "^base-commit:" patch >actual &&
-	echo "base-commit: $(git rev-parse HEAD~1)" >expected &&
-	test_cmp expected actual
+	git rev-parse HEAD~1 >commit-id-base &&
+	echo "base-commit: $(cat commit-id-base)" >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success 'format-patch --no-base overrides format.useAutoBase' '
+	test_config format.useAutoBase true &&
+	git format-patch --stdout --no-base -1 >patch &&
+	! grep "^base-commit:" patch
+'
+
+test_expect_success 'format-patch --no-base overrides format.useAutoBase whenAble' '
+	test_config format.useAutoBase whenAble &&
+	git format-patch --stdout --no-base -1 >patch &&
+	! grep "^base-commit:" patch
 '
 
 test_expect_success 'format-patch --base with --attach' '
@@ -1833,7 +2156,7 @@ test_expect_success 'interdiff: cover-letter' '
 	git format-patch --cover-letter --interdiff=boop~2 -1 boop &&
 	test_i18ngrep "^Interdiff:$" 0000-cover-letter.patch &&
 	test_i18ngrep ! "^Interdiff:$" 0001-fleep.patch &&
-	sed "1,/^@@ /d; /^-- $/q" <0000-cover-letter.patch >actual &&
+	sed "1,/^@@ /d; /^-- $/q" 0000-cover-letter.patch >actual &&
 	test_cmp expect actual
 '
 
@@ -1849,7 +2172,7 @@ test_expect_success 'interdiff: solo-patch' '
 	EOF
 	git format-patch --interdiff=boop~2 -1 boop &&
 	test_i18ngrep "^Interdiff:$" 0001-fleep.patch &&
-	sed "1,/^  @@ /d; /^$/q" <0001-fleep.patch >actual &&
+	sed "1,/^  @@ /d; /^$/q" 0001-fleep.patch >actual &&
 	test_cmp expect actual
 '
 
diff --git a/third_party/git/t/t4015-diff-whitespace.sh b/third_party/git/t/t4015-diff-whitespace.sh
index 6b087df3dc..8bdaa0a693 100755
--- a/third_party/git/t/t4015-diff-whitespace.sh
+++ b/third_party/git/t/t4015-diff-whitespace.sh
@@ -16,6 +16,8 @@ test_expect_success "Ray Lehtiniemi's example" '
 	} while (0);
 	EOF
 	git update-index --add x &&
+	old_hash_x=$(git hash-object x) &&
+	before=$(git rev-parse --short "$old_hash_x") &&
 
 	cat <<-\EOF >x &&
 	do
@@ -24,10 +26,12 @@ test_expect_success "Ray Lehtiniemi's example" '
 	}
 	while (0);
 	EOF
+	new_hash_x=$(git hash-object x) &&
+	after=$(git rev-parse --short "$new_hash_x") &&
 
-	cat <<-\EOF >expect &&
+	cat <<-EOF >expect &&
 	diff --git a/x b/x
-	index adf3937..6edc172 100644
+	index $before..$after 100644
 	--- a/x
 	+++ b/x
 	@@ -1,3 +1,5 @@
@@ -61,6 +65,8 @@ test_expect_success 'another test, without options' '
 	EOF
 
 	git update-index x &&
+	old_hash_x=$(git hash-object x) &&
+	before=$(git rev-parse --short "$old_hash_x") &&
 
 	tr "_" " " <<-\EOF >x &&
 	_	whitespace at beginning
@@ -70,10 +76,12 @@ test_expect_success 'another test, without options' '
 	unchanged line
 	CR at end
 	EOF
+	new_hash_x=$(git hash-object x) &&
+	after=$(git rev-parse --short "$new_hash_x") &&
 
-	tr "Q_" "\015 " <<-\EOF >expect &&
+	tr "Q_" "\015 " <<-EOF >expect &&
 	diff --git a/x b/x
-	index d99af23..22d9f73 100644
+	index $before..$after 100644
 	--- a/x
 	+++ b/x
 	@@ -1,6 +1,6 @@
@@ -108,9 +116,9 @@ test_expect_success 'another test, without options' '
 	git diff -w --ignore-cr-at-eol >out &&
 	test_must_be_empty out &&
 
-	tr "Q_" "\015 " <<-\EOF >expect &&
+	tr "Q_" "\015 " <<-EOF >expect &&
 	diff --git a/x b/x
-	index d99af23..22d9f73 100644
+	index $before..$after 100644
 	--- a/x
 	+++ b/x
 	@@ -1,6 +1,6 @@
@@ -132,9 +140,9 @@ test_expect_success 'another test, without options' '
 	git diff -b --ignore-cr-at-eol >out &&
 	test_cmp expect out &&
 
-	tr "Q_" "\015 " <<-\EOF >expect &&
+	tr "Q_" "\015 " <<-EOF >expect &&
 	diff --git a/x b/x
-	index d99af23..22d9f73 100644
+	index $before..$after 100644
 	--- a/x
 	+++ b/x
 	@@ -1,6 +1,6 @@
@@ -154,9 +162,9 @@ test_expect_success 'another test, without options' '
 	git diff --ignore-space-at-eol --ignore-cr-at-eol >out &&
 	test_cmp expect out &&
 
-	tr "Q_" "\015 " <<-\EOF >expect &&
+	tr "Q_" "\015 " <<-EOF >expect &&
 	diff --git a/x b/x
-	index_d99af23..22d9f73 100644
+	index_$before..$after 100644
 	--- a/x
 	+++ b/x
 	@@ -1,6 +1,6 @@
@@ -522,13 +530,15 @@ test_expect_success 'ignore-blank-lines: mix changes and blank lines' '
 test_expect_success 'check mixed spaces and tabs in indent' '
 	# This is indented with SP HT SP.
 	echo " 	 foo();" >x &&
-	git diff --check | grep "space before tab in indent"
+	test_must_fail git diff --check >check &&
+	grep "space before tab in indent" check
 '
 
 test_expect_success 'check mixed tabs and spaces in indent' '
 	# This is indented with HT SP HT.
 	echo "	 	foo();" >x &&
-	git diff --check | grep "space before tab in indent"
+	test_must_fail git diff --check >check &&
+	grep "space before tab in indent" check
 '
 
 test_expect_success 'check with no whitespace errors' '
@@ -749,20 +759,23 @@ test_expect_success 'check tab-in-indent excluded from wildcard whitespace attri
 test_expect_success 'line numbers in --check output are correct' '
 	echo "" >x &&
 	echo "foo(); " >>x &&
-	git diff --check | grep "x:2:"
+	test_must_fail git diff --check >check &&
+	grep "x:2:" check
 '
 
 test_expect_success 'checkdiff detects new trailing blank lines (1)' '
 	echo "foo();" >x &&
 	echo "" >>x &&
-	git diff --check | grep "new blank line"
+	test_must_fail git diff --check >check &&
+	grep "new blank line" check
 '
 
 test_expect_success 'checkdiff detects new trailing blank lines (2)' '
-	{ echo a; echo b; echo; echo; } >x &&
+	test_write_lines a b "" "" >x &&
 	git add x &&
-	{ echo a; echo; echo; echo; echo; } >x &&
-	git diff --check | grep "new blank line"
+	test_write_lines a "" "" "" "" >x &&
+	test_must_fail git diff --check >check &&
+	grep "new blank line" check
 '
 
 test_expect_success 'checkdiff allows new blank lines' '
@@ -776,7 +789,7 @@ test_expect_success 'checkdiff allows new blank lines' '
 	git diff --check
 '
 
-test_expect_success 'whitespace-only changes not reported' '
+test_expect_success 'whitespace-only changes not reported (diff)' '
 	git reset --hard &&
 	echo >x "hello world" &&
 	git add x &&
@@ -786,14 +799,25 @@ test_expect_success 'whitespace-only changes not reported' '
 	test_must_be_empty actual
 '
 
-cat <<EOF >expect
-diff --git a/x b/z
-similarity index NUM%
-rename from x
-rename to z
-index 380c32a..a97b785 100644
-EOF
-test_expect_success 'whitespace-only changes reported across renames' '
+test_expect_success 'whitespace-only changes not reported (diffstat)' '
+	# reuse state from previous test
+	git diff --stat -b >actual &&
+	test_must_be_empty actual
+'
+
+test_expect_success 'whitespace changes with modification reported (diffstat)' '
+	git reset --hard &&
+	echo >x "hello  world" &&
+	git update-index --chmod=+x x &&
+	git diff --stat --cached -b >actual &&
+	cat <<-EOF >expect &&
+	 x | 0
+	 1 file changed, 0 insertions(+), 0 deletions(-)
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'whitespace-only changes reported across renames (diffstat)' '
 	git reset --hard &&
 	for i in 1 2 3 4 5 6 7 8 9; do echo "$i$i$i$i$i$i"; done >x &&
 	git add x &&
@@ -801,8 +825,35 @@ test_expect_success 'whitespace-only changes reported across renames' '
 	sed -e "5s/^/ /" x >z &&
 	git rm x &&
 	git add z &&
-	git diff -w -M --cached |
-	sed -e "/^similarity index /s/[0-9][0-9]*/NUM/" >actual &&
+	git diff -w -M --cached --stat >actual &&
+	cat <<-EOF >expect &&
+	 x => z | 0
+	 1 file changed, 0 insertions(+), 0 deletions(-)
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'whitespace-only changes reported across renames' '
+	git reset --hard HEAD~1 &&
+	for i in 1 2 3 4 5 6 7 8 9; do echo "$i$i$i$i$i$i"; done >x &&
+	git add x &&
+	hash_x=$(git hash-object x) &&
+	before=$(git rev-parse --short "$hash_x") &&
+	git commit -m "base" &&
+	sed -e "5s/^/ /" x >z &&
+	git rm x &&
+	git add z &&
+	hash_z=$(git hash-object z) &&
+	after=$(git rev-parse --short "$hash_z") &&
+	git diff -w -M --cached >actual.raw &&
+	sed -e "/^similarity index /s/[0-9][0-9]*/NUM/" actual.raw >actual &&
+	cat <<-EOF >expect &&
+	diff --git a/x b/z
+	similarity index NUM%
+	rename from x
+	rename to z
+	index $before..$after 100644
+	EOF
 	test_cmp expect actual
 '
 
@@ -834,7 +885,8 @@ test_expect_success 'combined diff with autocrlf conversion' '
 	git config core.autocrlf true &&
 	test_must_fail git merge master &&
 
-	git diff | sed -e "1,/^@@@/d" >actual &&
+	git diff >actual.raw &&
+	sed -e "1,/^@@@/d" actual.raw >actual &&
 	! grep "^-" actual
 
 '
@@ -858,13 +910,18 @@ test_expect_success 'diff that introduces a line with only tabs' '
 	git config core.whitespace blank-at-eol &&
 	git reset --hard &&
 	echo "test" >x &&
+	old_hash_x=$(git hash-object x) &&
+	before=$(git rev-parse --short "$old_hash_x") &&
 	git commit -m "initial" x &&
 	echo "{NTN}" | tr "NT" "\n\t" >>x &&
-	git diff --color | test_decode_color >current &&
+	new_hash_x=$(git hash-object x) &&
+	after=$(git rev-parse --short "$new_hash_x") &&
+	git diff --color >current.raw &&
+	test_decode_color <current.raw >current &&
 
-	cat >expected <<-\EOF &&
+	cat >expected <<-EOF &&
 	<BOLD>diff --git a/x b/x<RESET>
-	<BOLD>index 9daeafb..2874b91 100644<RESET>
+	<BOLD>index $before..$after 100644<RESET>
 	<BOLD>--- a/x<RESET>
 	<BOLD>+++ b/x<RESET>
 	<CYAN>@@ -1 +1,4 @@<RESET>
@@ -883,19 +940,23 @@ test_expect_success 'diff that introduces and removes ws breakages' '
 		echo "0. blank-at-eol " &&
 		echo "1. blank-at-eol "
 	} >x &&
+	old_hash_x=$(git hash-object x) &&
+	before=$(git rev-parse --short "$old_hash_x") &&
 	git commit -a --allow-empty -m preimage &&
 	{
 		echo "0. blank-at-eol " &&
 		echo "1. still-blank-at-eol " &&
 		echo "2. and a new line "
 	} >x &&
+	new_hash_x=$(git hash-object x) &&
+	after=$(git rev-parse --short "$new_hash_x") &&
 
-	git diff --color |
-	test_decode_color >current &&
+	git diff --color >current.raw &&
+	test_decode_color <current.raw >current &&
 
-	cat >expected <<-\EOF &&
+	cat >expected <<-EOF &&
 	<BOLD>diff --git a/x b/x<RESET>
-	<BOLD>index d0233a2..700886e 100644<RESET>
+	<BOLD>index $before..$after 100644<RESET>
 	<BOLD>--- a/x<RESET>
 	<BOLD>+++ b/x<RESET>
 	<CYAN>@@ -1,2 +1,3 @@<RESET>
@@ -915,16 +976,20 @@ test_expect_success 'ws-error-highlight test setup' '
 		echo "0. blank-at-eol " &&
 		echo "1. blank-at-eol "
 	} >x &&
+	old_hash_x=$(git hash-object x) &&
+	before=$(git rev-parse --short "$old_hash_x") &&
 	git commit -a --allow-empty -m preimage &&
 	{
 		echo "0. blank-at-eol " &&
 		echo "1. still-blank-at-eol " &&
 		echo "2. and a new line "
 	} >x &&
+	new_hash_x=$(git hash-object x) &&
+	after=$(git rev-parse --short "$new_hash_x") &&
 
-	cat >expect.default-old <<-\EOF &&
+	cat >expect.default-old <<-EOF &&
 	<BOLD>diff --git a/x b/x<RESET>
-	<BOLD>index d0233a2..700886e 100644<RESET>
+	<BOLD>index $before..$after 100644<RESET>
 	<BOLD>--- a/x<RESET>
 	<BOLD>+++ b/x<RESET>
 	<CYAN>@@ -1,2 +1,3 @@<RESET>
@@ -934,9 +999,9 @@ test_expect_success 'ws-error-highlight test setup' '
 	<GREEN>+<RESET><GREEN>2. and a new line<RESET><BLUE> <RESET>
 	EOF
 
-	cat >expect.all <<-\EOF &&
+	cat >expect.all <<-EOF &&
 	<BOLD>diff --git a/x b/x<RESET>
-	<BOLD>index d0233a2..700886e 100644<RESET>
+	<BOLD>index $before..$after 100644<RESET>
 	<BOLD>--- a/x<RESET>
 	<BOLD>+++ b/x<RESET>
 	<CYAN>@@ -1,2 +1,3 @@<RESET>
@@ -946,9 +1011,9 @@ test_expect_success 'ws-error-highlight test setup' '
 	<GREEN>+<RESET><GREEN>2. and a new line<RESET><BLUE> <RESET>
 	EOF
 
-	cat >expect.none <<-\EOF
+	cat >expect.none <<-EOF
 	<BOLD>diff --git a/x b/x<RESET>
-	<BOLD>index d0233a2..700886e 100644<RESET>
+	<BOLD>index $before..$after 100644<RESET>
 	<BOLD>--- a/x<RESET>
 	<BOLD>+++ b/x<RESET>
 	<CYAN>@@ -1,2 +1,3 @@<RESET>
@@ -962,32 +1027,32 @@ test_expect_success 'ws-error-highlight test setup' '
 
 test_expect_success 'test --ws-error-highlight option' '
 
-	git diff --color --ws-error-highlight=default,old |
-	test_decode_color >current &&
+	git diff --color --ws-error-highlight=default,old >current.raw &&
+	test_decode_color <current.raw >current &&
 	test_cmp expect.default-old current &&
 
-	git diff --color --ws-error-highlight=all |
-	test_decode_color >current &&
+	git diff --color --ws-error-highlight=all >current.raw &&
+	test_decode_color <current.raw >current &&
 	test_cmp expect.all current &&
 
-	git diff --color --ws-error-highlight=none |
-	test_decode_color >current &&
+	git diff --color --ws-error-highlight=none >current.raw &&
+	test_decode_color <current.raw >current &&
 	test_cmp expect.none current
 
 '
 
 test_expect_success 'test diff.wsErrorHighlight config' '
 
-	git -c diff.wsErrorHighlight=default,old diff --color |
-	test_decode_color >current &&
+	git -c diff.wsErrorHighlight=default,old diff --color >current.raw &&
+	test_decode_color <current.raw >current &&
 	test_cmp expect.default-old current &&
 
-	git -c diff.wsErrorHighlight=all diff --color |
-	test_decode_color >current &&
+	git -c diff.wsErrorHighlight=all diff --color >current.raw &&
+	test_decode_color <current.raw >current &&
 	test_cmp expect.all current &&
 
-	git -c diff.wsErrorHighlight=none diff --color |
-	test_decode_color >current &&
+	git -c diff.wsErrorHighlight=none diff --color >current.raw &&
+	test_decode_color <current.raw >current &&
 	test_cmp expect.none current
 
 '
@@ -995,18 +1060,18 @@ test_expect_success 'test diff.wsErrorHighlight config' '
 test_expect_success 'option overrides diff.wsErrorHighlight' '
 
 	git -c diff.wsErrorHighlight=none \
-		diff --color --ws-error-highlight=default,old |
-	test_decode_color >current &&
+		diff --color --ws-error-highlight=default,old >current.raw &&
+	test_decode_color <current.raw >current &&
 	test_cmp expect.default-old current &&
 
 	git -c diff.wsErrorHighlight=default \
-		diff --color --ws-error-highlight=all |
-	test_decode_color >current &&
+		diff --color --ws-error-highlight=all >current.raw &&
+	test_decode_color <current.raw >current &&
 	test_cmp expect.all current &&
 
 	git -c diff.wsErrorHighlight=all \
-		diff --color --ws-error-highlight=none |
-	test_decode_color >current &&
+		diff --color --ws-error-highlight=none >current.raw &&
+	test_decode_color <current.raw >current &&
 	test_cmp expect.none current
 
 '
@@ -1022,14 +1087,16 @@ test_expect_success 'detect moved code, complete file' '
 	EOF
 	git add test.c &&
 	git commit -m "add main function" &&
+	file=$(git rev-parse --short HEAD:test.c) &&
 	git mv test.c main.c &&
 	test_config color.diff.oldMoved "normal red" &&
 	test_config color.diff.newMoved "normal green" &&
-	git diff HEAD --color-moved=zebra --color --no-renames | test_decode_color >actual &&
-	cat >expected <<-\EOF &&
+	git diff HEAD --color-moved=zebra --color --no-renames >actual.raw &&
+	test_decode_color <actual.raw >actual &&
+	cat >expected <<-EOF &&
 	<BOLD>diff --git a/main.c b/main.c<RESET>
 	<BOLD>new file mode 100644<RESET>
-	<BOLD>index 0000000..a986c57<RESET>
+	<BOLD>index 0000000..$file<RESET>
 	<BOLD>--- /dev/null<RESET>
 	<BOLD>+++ b/main.c<RESET>
 	<CYAN>@@ -0,0 +1,5 @@<RESET>
@@ -1040,7 +1107,7 @@ test_expect_success 'detect moved code, complete file' '
 	<BGREEN>+<RESET><BGREEN>}<RESET>
 	<BOLD>diff --git a/test.c b/test.c<RESET>
 	<BOLD>deleted file mode 100644<RESET>
-	<BOLD>index a986c57..0000000<RESET>
+	<BOLD>index $file..0000000<RESET>
 	<BOLD>--- a/test.c<RESET>
 	<BOLD>+++ /dev/null<RESET>
 	<CYAN>@@ -1,5 +0,0 @@<RESET>
@@ -1094,6 +1161,8 @@ test_expect_success 'detect malicious moved code, inside file' '
 	EOF
 	git add main.c test.c &&
 	git commit -m "add main and test file" &&
+	before_main=$(git rev-parse --short HEAD:main.c) &&
+	before_test=$(git rev-parse --short HEAD:test.c) &&
 	cat <<-\EOF >main.c &&
 		#include<stdio.h>
 		int stuff()
@@ -1126,10 +1195,15 @@ test_expect_success 'detect malicious moved code, inside file' '
 			bar();
 		}
 	EOF
-	git diff HEAD --no-renames --color-moved=zebra --color | test_decode_color >actual &&
-	cat <<-\EOF >expected &&
+	hash_main=$(git hash-object main.c) &&
+	after_main=$(git rev-parse --short "$hash_main") &&
+	hash_test=$(git hash-object test.c) &&
+	after_test=$(git rev-parse --short "$hash_test") &&
+	git diff HEAD --no-renames --color-moved=zebra --color >actual.raw &&
+	test_decode_color <actual.raw >actual &&
+	cat <<-EOF >expected &&
 	<BOLD>diff --git a/main.c b/main.c<RESET>
-	<BOLD>index 27a619c..7cf9336 100644<RESET>
+	<BOLD>index $before_main..$after_main 100644<RESET>
 	<BOLD>--- a/main.c<RESET>
 	<BOLD>+++ b/main.c<RESET>
 	<CYAN>@@ -5,13 +5,6 @@<RESET> <RESET>printf("Hello ");<RESET>
@@ -1147,7 +1221,7 @@ test_expect_success 'detect malicious moved code, inside file' '
 	 {<RESET>
 	 foo();<RESET>
 	<BOLD>diff --git a/test.c b/test.c<RESET>
-	<BOLD>index 1dc1d85..2bedec9 100644<RESET>
+	<BOLD>index $before_test..$after_test 100644<RESET>
 	<BOLD>--- a/test.c<RESET>
 	<BOLD>+++ b/test.c<RESET>
 	<CYAN>@@ -4,6 +4,13 @@<RESET> <RESET>int bar()<RESET>
@@ -1175,10 +1249,11 @@ test_expect_success 'plain moved code, inside file' '
 	test_config color.diff.oldMovedAlternative "blue" &&
 	test_config color.diff.newMovedAlternative "yellow" &&
 	# needs previous test as setup
-	git diff HEAD --no-renames --color-moved=plain --color | test_decode_color >actual &&
-	cat <<-\EOF >expected &&
+	git diff HEAD --no-renames --color-moved=plain --color >actual.raw &&
+	test_decode_color <actual.raw >actual &&
+	cat <<-EOF >expected &&
 	<BOLD>diff --git a/main.c b/main.c<RESET>
-	<BOLD>index 27a619c..7cf9336 100644<RESET>
+	<BOLD>index $before_main..$after_main 100644<RESET>
 	<BOLD>--- a/main.c<RESET>
 	<BOLD>+++ b/main.c<RESET>
 	<CYAN>@@ -5,13 +5,6 @@<RESET> <RESET>printf("Hello ");<RESET>
@@ -1196,7 +1271,7 @@ test_expect_success 'plain moved code, inside file' '
 	 {<RESET>
 	 foo();<RESET>
 	<BOLD>diff --git a/test.c b/test.c<RESET>
-	<BOLD>index 1dc1d85..2bedec9 100644<RESET>
+	<BOLD>index $before_test..$after_test 100644<RESET>
 	<BOLD>--- a/test.c<RESET>
 	<BOLD>+++ b/test.c<RESET>
 	<CYAN>@@ -4,6 +4,13 @@<RESET> <RESET>int bar()<RESET>
@@ -1754,7 +1829,8 @@ test_expect_success 'move detection with submodules' '
 	! grep BRED decoded_actual &&
 
 	# nor did we mess with it another way
-	git diff --submodule=diff --color | test_decode_color >expect &&
+	git diff --submodule=diff --color >expect.raw &&
+	test_decode_color <expect.raw >expect &&
 	test_cmp expect decoded_actual &&
 	rm -rf bananas &&
 	git submodule deinit bananas
@@ -2008,11 +2084,6 @@ test_expect_success 'compare mixed whitespace delta across moved blocks' '
 	test_cmp expected actual
 '
 
-# Note that the "6" in the expected hunk header below is funny, since we only
-# show 5 lines (the missing one was blank and thus ignored). This is how
-# --ignore-blank-lines behaves even without --function-context, and this test
-# is just checking the interaction of the two features. Don't take it as an
-# endorsement of that output.
 test_expect_success 'combine --ignore-blank-lines with --function-context' '
 	test_write_lines 1 "" 2 3 4 5 >a &&
 	test_write_lines 1    2 3 4   >b &&
@@ -2022,6 +2093,7 @@ test_expect_success 'combine --ignore-blank-lines with --function-context' '
 	cat <<-\EOF >expect &&
 	@@ -1,6 +1,4 @@
 	 1
+	-
 	 2
 	 3
 	 4
@@ -2030,4 +2102,27 @@ test_expect_success 'combine --ignore-blank-lines with --function-context' '
 	test_cmp expect actual
 '
 
+test_expect_success 'combine --ignore-blank-lines with --function-context 2' '
+	test_write_lines    a b c "" function 1 2 3 4 5 "" 6 7 8 9 >a &&
+	test_write_lines "" a b c "" function 1 2 3 4 5    6 7 8   >b &&
+	test_must_fail git diff --no-index \
+		--ignore-blank-lines --function-context a b >actual.raw &&
+	sed -n "/@@/,\$p" <actual.raw >actual &&
+	cat <<-\EOF >expect &&
+	@@ -5,11 +6,9 @@ c
+	 function
+	 1
+	 2
+	 3
+	 4
+	 5
+	-
+	 6
+	 7
+	 8
+	-9
+	EOF
+	test_cmp expect actual
+'
+
 test_done
diff --git a/third_party/git/t/t4018-diff-funcname.sh b/third_party/git/t/t4018-diff-funcname.sh
index 9261d6d3a0..9d07797579 100755
--- a/third_party/git/t/t4018-diff-funcname.sh
+++ b/third_party/git/t/t4018-diff-funcname.sh
@@ -31,11 +31,14 @@ diffpatterns="
 	cpp
 	csharp
 	css
+	dts
+	elixir
 	fortran
 	fountain
 	golang
 	html
 	java
+	markdown
 	matlab
 	objc
 	pascal
@@ -104,7 +107,6 @@ do
 		result=success
 	fi
 	test_expect_$result "hunk header: $i" "
-		test_when_finished 'cat actual' &&	# for debugging only
 		git diff -U1 $i >actual &&
 		grep '@@ .* @@.*RIGHT' actual
 	"
diff --git a/third_party/git/t/t4018/dts-labels b/third_party/git/t/t4018/dts-labels
new file mode 100644
index 0000000000..b21ef8737b
--- /dev/null
+++ b/third_party/git/t/t4018/dts-labels
@@ -0,0 +1,9 @@
+/ {
+	label_1: node1@ff00 {
+		label2: RIGHT {
+			vendor,some-property;
+
+			ChangeMe = <0x45-30>;
+		};
+	};
+};
diff --git a/third_party/git/t/t4018/dts-node-unitless b/third_party/git/t/t4018/dts-node-unitless
new file mode 100644
index 0000000000..c5287d9141
--- /dev/null
+++ b/third_party/git/t/t4018/dts-node-unitless
@@ -0,0 +1,8 @@
+/ {
+	label_1: node1 {
+		RIGHT {
+			prop-array = <1>, <4>;
+			ChangeMe = <0xffeedd00>;
+		};
+	};
+};
diff --git a/third_party/git/t/t4018/dts-nodes b/third_party/git/t/t4018/dts-nodes
new file mode 100644
index 0000000000..5a4334bb16
--- /dev/null
+++ b/third_party/git/t/t4018/dts-nodes
@@ -0,0 +1,8 @@
+/ {
+	label_1: node1@ff00 {
+		RIGHT@deadf00,4000 {
+			#size-cells = <1>;
+			ChangeMe = <0xffeedd00>;
+		};
+	};
+};
diff --git a/third_party/git/t/t4018/dts-nodes-boolean-prop b/third_party/git/t/t4018/dts-nodes-boolean-prop
new file mode 100644
index 0000000000..afc6b5b404
--- /dev/null
+++ b/third_party/git/t/t4018/dts-nodes-boolean-prop
@@ -0,0 +1,9 @@
+/ {
+	label_1: node1@ff00 {
+		RIGHT@deadf00,4000 {
+			boolean-prop1;
+
+			ChangeMe;
+		};
+	};
+};
diff --git a/third_party/git/t/t4018/dts-nodes-comment1 b/third_party/git/t/t4018/dts-nodes-comment1
new file mode 100644
index 0000000000..559dfce9b3
--- /dev/null
+++ b/third_party/git/t/t4018/dts-nodes-comment1
@@ -0,0 +1,8 @@
+/ {
+	label_1: node1@ff00 {
+		RIGHT@deadf00,4000 /* &a comment */ {
+			#size-cells = <1>;
+			ChangeMe = <0xffeedd00>;
+		};
+	};
+};
diff --git a/third_party/git/t/t4018/dts-nodes-comment2 b/third_party/git/t/t4018/dts-nodes-comment2
new file mode 100644
index 0000000000..27e9718b31
--- /dev/null
+++ b/third_party/git/t/t4018/dts-nodes-comment2
@@ -0,0 +1,8 @@
+/ {
+	label_1: node1@ff00 {
+		RIGHT@deadf00,4000 { /* a trailing comment */ 
+			#size-cells = <1>;
+			ChangeMe = <0xffeedd00>;
+		};
+	};
+};
diff --git a/third_party/git/t/t4018/dts-nodes-multiline-prop b/third_party/git/t/t4018/dts-nodes-multiline-prop
new file mode 100644
index 0000000000..072d58b69d
--- /dev/null
+++ b/third_party/git/t/t4018/dts-nodes-multiline-prop
@@ -0,0 +1,13 @@
+/ {
+	label_1: node1@ff00 {
+		RIGHT@deadf00,4000 {
+			multilineprop = <3>,
+					<4>,
+					<5>,
+					<6>,
+					<7>;
+
+			ChangeMe = <0xffeedd00>;
+		};
+	};
+};
diff --git a/third_party/git/t/t4018/dts-reference b/third_party/git/t/t4018/dts-reference
new file mode 100644
index 0000000000..8f0c87d863
--- /dev/null
+++ b/third_party/git/t/t4018/dts-reference
@@ -0,0 +1,9 @@
+&label_1 {
+	TEST = <455>;
+};
+
+&RIGHT {
+	vendor,some-property;
+
+	ChangeMe = <0x45-30>;
+};
diff --git a/third_party/git/t/t4018/dts-root b/third_party/git/t/t4018/dts-root
new file mode 100644
index 0000000000..4353b8220c
--- /dev/null
+++ b/third_party/git/t/t4018/dts-root
@@ -0,0 +1,5 @@
+/ { RIGHT /* Technically just supposed to be a slash and brace */
+	#size-cells = <1>;
+
+	ChangeMe = <0xffeedd00>;
+};
diff --git a/third_party/git/t/t4018/dts-root-comment b/third_party/git/t/t4018/dts-root-comment
new file mode 100644
index 0000000000..333a625c70
--- /dev/null
+++ b/third_party/git/t/t4018/dts-root-comment
@@ -0,0 +1,8 @@
+/ { RIGHT /* Technically just supposed to be a slash and brace */
+	#size-cells = <1>;
+
+	/* This comment should be ignored */
+
+	some-property = <40+2>;
+	ChangeMe = <0xffeedd00>;
+};
diff --git a/third_party/git/t/t4018/elixir-do-not-pick-end b/third_party/git/t/t4018/elixir-do-not-pick-end
new file mode 100644
index 0000000000..fae08ba7e8
--- /dev/null
+++ b/third_party/git/t/t4018/elixir-do-not-pick-end
@@ -0,0 +1,5 @@
+defmodule RIGHT do
+end
+#
+#
+# ChangeMe; do not pick up 'end' line
diff --git a/third_party/git/t/t4018/elixir-ex-unit-test b/third_party/git/t/t4018/elixir-ex-unit-test
new file mode 100644
index 0000000000..0560a2b697
--- /dev/null
+++ b/third_party/git/t/t4018/elixir-ex-unit-test
@@ -0,0 +1,6 @@
+defmodule Test do
+  test "RIGHT" do
+    assert true == true
+    assert ChangeMe
+  end
+end
diff --git a/third_party/git/t/t4018/elixir-function b/third_party/git/t/t4018/elixir-function
new file mode 100644
index 0000000000..d452f495a7
--- /dev/null
+++ b/third_party/git/t/t4018/elixir-function
@@ -0,0 +1,5 @@
+def function(RIGHT, arg) do
+  # comment
+  # comment
+  ChangeMe
+end
diff --git a/third_party/git/t/t4018/elixir-macro b/third_party/git/t/t4018/elixir-macro
new file mode 100644
index 0000000000..4f925e9ad4
--- /dev/null
+++ b/third_party/git/t/t4018/elixir-macro
@@ -0,0 +1,5 @@
+defmacro foo(RIGHT) do
+  # Code
+  # Code
+  ChangeMe
+end
diff --git a/third_party/git/t/t4018/elixir-module b/third_party/git/t/t4018/elixir-module
new file mode 100644
index 0000000000..91a4e7aa20
--- /dev/null
+++ b/third_party/git/t/t4018/elixir-module
@@ -0,0 +1,9 @@
+defmodule RIGHT do
+  @moduledoc """
+  Foo bar
+  """
+
+  def ChangeMe(a) where is_map(a) do
+    a
+  end
+end
diff --git a/third_party/git/t/t4018/elixir-module-func b/third_party/git/t/t4018/elixir-module-func
new file mode 100644
index 0000000000..c9910d0675
--- /dev/null
+++ b/third_party/git/t/t4018/elixir-module-func
@@ -0,0 +1,8 @@
+defmodule Foo do
+  def fun(RIGHT) do
+     # Code
+     # Code
+     # Code
+     ChangeMe
+  end
+end
diff --git a/third_party/git/t/t4018/elixir-nested-module b/third_party/git/t/t4018/elixir-nested-module
new file mode 100644
index 0000000000..771ebc5c42
--- /dev/null
+++ b/third_party/git/t/t4018/elixir-nested-module
@@ -0,0 +1,9 @@
+defmodule MyApp.RIGHT do
+  @moduledoc """
+  Foo bar
+  """
+
+  def ChangeMe(a) where is_map(a) do
+    a
+  end
+end
diff --git a/third_party/git/t/t4018/elixir-private-function b/third_party/git/t/t4018/elixir-private-function
new file mode 100644
index 0000000000..1aabe33b7a
--- /dev/null
+++ b/third_party/git/t/t4018/elixir-private-function
@@ -0,0 +1,5 @@
+defp function(RIGHT, arg) do
+  # comment
+  # comment
+  ChangeMe
+end
diff --git a/third_party/git/t/t4018/elixir-protocol b/third_party/git/t/t4018/elixir-protocol
new file mode 100644
index 0000000000..7d9173691e
--- /dev/null
+++ b/third_party/git/t/t4018/elixir-protocol
@@ -0,0 +1,6 @@
+defprotocol RIGHT do
+  @doc """
+  Calculates the size (and not the length!) of a data structure
+  """
+  def size(data, ChangeMe)
+end
diff --git a/third_party/git/t/t4018/elixir-protocol-implementation b/third_party/git/t/t4018/elixir-protocol-implementation
new file mode 100644
index 0000000000..f9234bbfc4
--- /dev/null
+++ b/third_party/git/t/t4018/elixir-protocol-implementation
@@ -0,0 +1,5 @@
+defimpl RIGHT do
+  # Docs
+  # Docs
+  def foo(ChangeMe), do: :ok
+end
diff --git a/third_party/git/t/t4018/fortran-block-data b/third_party/git/t/t4018/fortran-block-data
new file mode 100644
index 0000000000..63d4e21d0a
--- /dev/null
+++ b/third_party/git/t/t4018/fortran-block-data
@@ -0,0 +1,5 @@
+       BLOCK DATA RIGHT
+       
+       COMMON /B/ C, ChangeMe
+       DATA C, ChangeMe  / 2.0, 6.0 / 
+       END 
diff --git a/third_party/git/t/t4018/fortran-comment b/third_party/git/t/t4018/fortran-comment
new file mode 100644
index 0000000000..7b10d17658
--- /dev/null
+++ b/third_party/git/t/t4018/fortran-comment
@@ -0,0 +1,13 @@
+      module a
+
+      contains
+
+      ! subroutine wrong
+      subroutine RIGHT
+      ! subroutine wrong
+
+      real ChangeMe
+
+      end subroutine RIGHT
+
+      end module a
diff --git a/third_party/git/t/t4018/fortran-comment-keyword b/third_party/git/t/t4018/fortran-comment-keyword
new file mode 100644
index 0000000000..e9206a5379
--- /dev/null
+++ b/third_party/git/t/t4018/fortran-comment-keyword
@@ -0,0 +1,14 @@
+      module a
+
+      contains
+
+      subroutine RIGHT (funcA, funcB)
+
+      real funcA  ! grid function a
+      real funcB  ! grid function b
+
+      real ChangeMe
+
+      end subroutine RIGHT
+
+      end module a
diff --git a/third_party/git/t/t4018/fortran-comment-legacy b/third_party/git/t/t4018/fortran-comment-legacy
new file mode 100644
index 0000000000..53cd062c1e
--- /dev/null
+++ b/third_party/git/t/t4018/fortran-comment-legacy
@@ -0,0 +1,13 @@
+      module a
+
+      contains
+
+C subroutine wrong
+      subroutine RIGHT
+C subroutine wrong
+
+      real ChangeMe
+
+      end subroutine RIGHT
+
+      end module a
diff --git a/third_party/git/t/t4018/fortran-comment-legacy-star b/third_party/git/t/t4018/fortran-comment-legacy-star
new file mode 100644
index 0000000000..2cbcdc3d8a
--- /dev/null
+++ b/third_party/git/t/t4018/fortran-comment-legacy-star
@@ -0,0 +1,13 @@
+      module a
+
+      contains
+
+* subroutine wrong
+      subroutine RIGHT
+* subroutine wrong
+
+      real ChangeMe
+
+      end subroutine RIGHT
+
+      end module a
diff --git a/third_party/git/t/t4018/fortran-external-function b/third_party/git/t/t4018/fortran-external-function
new file mode 100644
index 0000000000..5a2d85d3aa
--- /dev/null
+++ b/third_party/git/t/t4018/fortran-external-function
@@ -0,0 +1,9 @@
+function RIGHT(a, b) result(c)
+
+integer, intent(in) :: ChangeMe
+integer, intent(in) :: b
+integer, intent(out) :: c
+
+c = a+b
+
+end function RIGHT
diff --git a/third_party/git/t/t4018/fortran-external-subroutine b/third_party/git/t/t4018/fortran-external-subroutine
new file mode 100644
index 0000000000..4ce85fea13
--- /dev/null
+++ b/third_party/git/t/t4018/fortran-external-subroutine
@@ -0,0 +1,5 @@
+subroutine RIGHT
+
+real ChangeMe
+
+end subroutine RIGHT
diff --git a/third_party/git/t/t4018/fortran-module b/third_party/git/t/t4018/fortran-module
new file mode 100644
index 0000000000..c4b737dac3
--- /dev/null
+++ b/third_party/git/t/t4018/fortran-module
@@ -0,0 +1,5 @@
+module RIGHT
+
+use ChangeMe
+
+end module RIGHT
diff --git a/third_party/git/t/t4018/fortran-module-procedure b/third_party/git/t/t4018/fortran-module-procedure
new file mode 100644
index 0000000000..1ce6d854c2
--- /dev/null
+++ b/third_party/git/t/t4018/fortran-module-procedure
@@ -0,0 +1,13 @@
+ module RIGHT
+
+   implicit none
+   private
+
+   interface letters  ! generic interface
+      module procedure aaaa, &
+                       bbbb, &
+                       ChangeMe, &
+                       dddd
+   end interface
+   
+end module RIGHT
diff --git a/third_party/git/t/t4018/fortran-program b/third_party/git/t/t4018/fortran-program
new file mode 100644
index 0000000000..4616895e4b
--- /dev/null
+++ b/third_party/git/t/t4018/fortran-program
@@ -0,0 +1,5 @@
+program RIGHT
+
+call ChangeMe
+
+end program RIGHT
diff --git a/third_party/git/t/t4018/markdown-heading-indented b/third_party/git/t/t4018/markdown-heading-indented
new file mode 100644
index 0000000000..1991c2bd45
--- /dev/null
+++ b/third_party/git/t/t4018/markdown-heading-indented
@@ -0,0 +1,6 @@
+Indented headings are allowed, as long as the indent is no more than 3 spaces.
+
+   ### RIGHT
+
+- something
+- ChangeMe
diff --git a/third_party/git/t/t4018/markdown-heading-non-headings b/third_party/git/t/t4018/markdown-heading-non-headings
new file mode 100644
index 0000000000..c479c1a3f1
--- /dev/null
+++ b/third_party/git/t/t4018/markdown-heading-non-headings
@@ -0,0 +1,17 @@
+Headings can be right next to other lines of the file:
+# RIGHT
+Indents of four or more spaces make a code block:
+
+    # code comment, not heading
+
+If there's no space after the final hash, it's not a heading:
+
+#hashtag
+
+Sequences of more than 6 hashes don't make a heading:
+
+####### over-enthusiastic heading
+
+So the detected heading should be right up at the start of this file.
+
+ChangeMe
diff --git a/third_party/git/t/t4018/python-async-def b/third_party/git/t/t4018/python-async-def
new file mode 100644
index 0000000000..87640e03d2
--- /dev/null
+++ b/third_party/git/t/t4018/python-async-def
@@ -0,0 +1,4 @@
+async def RIGHT(pi: int = 3.14):
+    while True:
+        break
+    return ChangeMe()
diff --git a/third_party/git/t/t4018/python-class b/third_party/git/t/t4018/python-class
new file mode 100644
index 0000000000..ba9e741430
--- /dev/null
+++ b/third_party/git/t/t4018/python-class
@@ -0,0 +1,4 @@
+class RIGHT(int, str):
+    # comment
+    # another comment
+    # ChangeMe
diff --git a/third_party/git/t/t4018/python-def b/third_party/git/t/t4018/python-def
new file mode 100644
index 0000000000..e50b31b0ad
--- /dev/null
+++ b/third_party/git/t/t4018/python-def
@@ -0,0 +1,4 @@
+def RIGHT(pi: int = 3.14):
+    while True:
+        break
+    return ChangeMe()
diff --git a/third_party/git/t/t4018/python-indented-async-def b/third_party/git/t/t4018/python-indented-async-def
new file mode 100644
index 0000000000..f5d03258af
--- /dev/null
+++ b/third_party/git/t/t4018/python-indented-async-def
@@ -0,0 +1,7 @@
+class Foo:
+    async def RIGHT(self, x: int):
+        return [
+            1,
+            2,
+            ChangeMe,
+        ]
diff --git a/third_party/git/t/t4018/python-indented-class b/third_party/git/t/t4018/python-indented-class
new file mode 100644
index 0000000000..19b4f35c4c
--- /dev/null
+++ b/third_party/git/t/t4018/python-indented-class
@@ -0,0 +1,5 @@
+if TYPE_CHECKING:
+    class RIGHT:
+        # comment
+        # another comment
+        # ChangeMe
diff --git a/third_party/git/t/t4018/python-indented-def b/third_party/git/t/t4018/python-indented-def
new file mode 100644
index 0000000000..208fbadd2b
--- /dev/null
+++ b/third_party/git/t/t4018/python-indented-def
@@ -0,0 +1,7 @@
+class Foo:
+    def RIGHT(self, x: int):
+        return [
+            1,
+            2,
+            ChangeMe,
+        ]
diff --git a/third_party/git/t/t4026-color.sh b/third_party/git/t/t4026-color.sh
index 671e951ee5..c0b642c1ab 100755
--- a/third_party/git/t/t4026-color.sh
+++ b/third_party/git/t/t4026-color.sh
@@ -30,6 +30,14 @@ test_expect_success 'attribute before color name' '
 	color "bold red" "[1;31m"
 '
 
+test_expect_success 'aixterm bright fg color' '
+	color "brightred" "[91m"
+'
+
+test_expect_success 'aixterm bright bg color' '
+	color "green brightblue" "[32;104m"
+'
+
 test_expect_success 'color name before attribute' '
 	color "red bold" "[1;31m"
 '
@@ -74,6 +82,10 @@ test_expect_success '0-7 are aliases for basic ANSI color names' '
 	color "0 7" "[30;47m"
 '
 
+test_expect_success '8-15 are aliases for aixterm color names' '
+	color "12 13" "[94;105m"
+'
+
 test_expect_success '256 colors' '
 	color "254 bold 255" "[1;38;5;254;48;5;255m"
 '
diff --git a/third_party/git/t/t4027-diff-submodule.sh b/third_party/git/t/t4027-diff-submodule.sh
index 9aa8e2b39b..d7145ccca4 100755
--- a/third_party/git/t/t4027-diff-submodule.sh
+++ b/third_party/git/t/t4027-diff-submodule.sh
@@ -36,7 +36,8 @@ test_expect_success setup '
 '
 
 test_expect_success 'git diff --raw HEAD' '
-	git diff --raw --abbrev=40 HEAD >actual &&
+	hexsz=$(test_oid hexsz) &&
+	git diff --raw --abbrev=$hexsz HEAD >actual &&
 	test_cmp expect actual
 '
 
@@ -245,23 +246,21 @@ test_expect_success 'git diff (empty submodule dir)' '
 '
 
 test_expect_success 'conflicted submodule setup' '
-
-	# 39 efs
-	c=fffffffffffffffffffffffffffffffffffffff &&
+	c=$(test_oid ff_1) &&
 	(
 		echo "000000 $ZERO_OID 0	sub" &&
 		echo "160000 1$c 1	sub" &&
 		echo "160000 2$c 2	sub" &&
 		echo "160000 3$c 3	sub"
 	) | git update-index --index-info &&
-	echo >expect.nosub '\''diff --cc sub
+	echo >expect.nosub "diff --cc sub
 index 2ffffff,3ffffff..0000000
 --- a/sub
 +++ b/sub
 @@@ -1,1 -1,1 +1,1 @@@
-- Subproject commit 2fffffffffffffffffffffffffffffffffffffff
- -Subproject commit 3fffffffffffffffffffffffffffffffffffffff
-++Subproject commit 0000000000000000000000000000000000000000'\'' &&
+- Subproject commit 2$c
+ -Subproject commit 3$c
+++Subproject commit $ZERO_OID" &&
 
 	hh=$(git rev-parse HEAD) &&
 	sed -e "s/$ZERO_OID/$hh/" expect.nosub >expect.withsub
diff --git a/third_party/git/t/t4034-diff-words.sh b/third_party/git/t/t4034-diff-words.sh
index 912df91226..0c8fb39ced 100755
--- a/third_party/git/t/t4034-diff-words.sh
+++ b/third_party/git/t/t4034-diff-words.sh
@@ -19,9 +19,11 @@ cat >post.simple <<-\EOF
 
 	aeff = aeff * ( aaa )
 EOF
-cat >expect.letter-runs-are-words <<-\EOF
+pre=$(git rev-parse --short $(git hash-object pre.simple))
+post=$(git rev-parse --short $(git hash-object post.simple))
+cat >expect.letter-runs-are-words <<-EOF
 	<BOLD>diff --git a/pre b/post<RESET>
-	<BOLD>index 330b04f..5ed8eff 100644<RESET>
+	<BOLD>index $pre..$post 100644<RESET>
 	<BOLD>--- a/pre<RESET>
 	<BOLD>+++ b/post<RESET>
 	<CYAN>@@ -1,3 +1,7 @@<RESET>
@@ -33,9 +35,9 @@ cat >expect.letter-runs-are-words <<-\EOF
 
 	<GREEN>aeff = aeff * ( aaa<RESET> )
 EOF
-cat >expect.non-whitespace-is-word <<-\EOF
+cat >expect.non-whitespace-is-word <<-EOF
 	<BOLD>diff --git a/pre b/post<RESET>
-	<BOLD>index 330b04f..5ed8eff 100644<RESET>
+	<BOLD>index $pre..$post 100644<RESET>
 	<BOLD>--- a/pre<RESET>
 	<BOLD>+++ b/post<RESET>
 	<CYAN>@@ -1,3 +1,7 @@<RESET>
@@ -49,9 +51,12 @@ cat >expect.non-whitespace-is-word <<-\EOF
 EOF
 
 word_diff () {
+	pre=$(git rev-parse --short $(git hash-object pre)) &&
+	post=$(git rev-parse --short $(git hash-object post)) &&
 	test_must_fail git diff --no-index "$@" pre post >output &&
 	test_decode_color <output >output.decrypted &&
-	test_cmp expect output.decrypted
+	sed -e "2s/index [^ ]*/index $pre..$post/" expect >expected
+	test_cmp expected output.decrypted
 }
 
 test_language_driver () {
@@ -77,9 +82,9 @@ test_expect_success 'set up pre and post with runs of whitespace' '
 '
 
 test_expect_success 'word diff with runs of whitespace' '
-	cat >expect <<-\EOF &&
+	cat >expect <<-EOF &&
 		<BOLD>diff --git a/pre b/post<RESET>
-		<BOLD>index 330b04f..5ed8eff 100644<RESET>
+		<BOLD>index $pre..$post 100644<RESET>
 		<BOLD>--- a/pre<RESET>
 		<BOLD>+++ b/post<RESET>
 		<CYAN>@@ -1,3 +1,7 @@<RESET>
@@ -97,9 +102,9 @@ test_expect_success 'word diff with runs of whitespace' '
 '
 
 test_expect_success '--word-diff=porcelain' '
-	sed 's/#.*$//' >expect <<-\EOF &&
+	sed "s/#.*$//" >expect <<-EOF &&
 		diff --git a/pre b/post
-		index 330b04f..5ed8eff 100644
+		index $pre..$post 100644
 		--- a/pre
 		+++ b/post
 		@@ -1,3 +1,7 @@
@@ -121,9 +126,9 @@ test_expect_success '--word-diff=porcelain' '
 '
 
 test_expect_success '--word-diff=plain' '
-	cat >expect <<-\EOF &&
+	cat >expect <<-EOF &&
 		diff --git a/pre b/post
-		index 330b04f..5ed8eff 100644
+		index $pre..$post 100644
 		--- a/pre
 		+++ b/post
 		@@ -1,3 +1,7 @@
@@ -140,9 +145,9 @@ test_expect_success '--word-diff=plain' '
 '
 
 test_expect_success '--word-diff=plain --color' '
-	cat >expect <<-\EOF &&
+	cat >expect <<-EOF &&
 		<BOLD>diff --git a/pre b/post<RESET>
-		<BOLD>index 330b04f..5ed8eff 100644<RESET>
+		<BOLD>index $pre..$post 100644<RESET>
 		<BOLD>--- a/pre<RESET>
 		<BOLD>+++ b/post<RESET>
 		<CYAN>@@ -1,3 +1,7 @@<RESET>
@@ -158,9 +163,9 @@ test_expect_success '--word-diff=plain --color' '
 '
 
 test_expect_success 'word diff without context' '
-	cat >expect <<-\EOF &&
+	cat >expect <<-EOF &&
 		<BOLD>diff --git a/pre b/post<RESET>
-		<BOLD>index 330b04f..5ed8eff 100644<RESET>
+		<BOLD>index $pre..$post 100644<RESET>
 		<BOLD>--- a/pre<RESET>
 		<BOLD>+++ b/post<RESET>
 		<CYAN>@@ -1 +1 @@<RESET>
@@ -207,9 +212,9 @@ test_expect_success 'command-line overrides config' '
 '
 
 test_expect_success 'command-line overrides config: --word-diff-regex' '
-	cat >expect <<-\EOF &&
+	cat >expect <<-EOF &&
 		<BOLD>diff --git a/pre b/post<RESET>
-		<BOLD>index 330b04f..5ed8eff 100644<RESET>
+		<BOLD>index $pre..$post 100644<RESET>
 		<BOLD>--- a/pre<RESET>
 		<BOLD>+++ b/post<RESET>
 		<CYAN>@@ -1,3 +1,7 @@<RESET>
@@ -234,9 +239,9 @@ test_expect_success 'setup: remove diff driver regex' '
 '
 
 test_expect_success 'use configured regex' '
-	cat >expect <<-\EOF &&
+	cat >expect <<-EOF &&
 		<BOLD>diff --git a/pre b/post<RESET>
-		<BOLD>index 330b04f..5ed8eff 100644<RESET>
+		<BOLD>index $pre..$post 100644<RESET>
 		<BOLD>--- a/pre<RESET>
 		<BOLD>+++ b/post<RESET>
 		<CYAN>@@ -1,3 +1,7 @@<RESET>
@@ -254,9 +259,11 @@ test_expect_success 'use configured regex' '
 test_expect_success 'test parsing words for newline' '
 	echo "aaa (aaa)" >pre &&
 	echo "aaa (aaa) aaa" >post &&
-	cat >expect <<-\EOF &&
+	pre=$(git rev-parse --short $(git hash-object pre)) &&
+	post=$(git rev-parse --short $(git hash-object post)) &&
+	cat >expect <<-EOF &&
 		<BOLD>diff --git a/pre b/post<RESET>
-		<BOLD>index c29453b..be22f37 100644<RESET>
+		<BOLD>index $pre..$post 100644<RESET>
 		<BOLD>--- a/pre<RESET>
 		<BOLD>+++ b/post<RESET>
 		<CYAN>@@ -1 +1 @@<RESET>
@@ -268,9 +275,11 @@ test_expect_success 'test parsing words for newline' '
 test_expect_success 'test when words are only removed at the end' '
 	echo "(:" >pre &&
 	echo "(" >post &&
-	cat >expect <<-\EOF &&
+	pre=$(git rev-parse --short $(git hash-object pre)) &&
+	post=$(git rev-parse --short $(git hash-object post)) &&
+	cat >expect <<-EOF &&
 		<BOLD>diff --git a/pre b/post<RESET>
-		<BOLD>index 289cb9d..2d06f37 100644<RESET>
+		<BOLD>index $pre..$post 100644<RESET>
 		<BOLD>--- a/pre<RESET>
 		<BOLD>+++ b/post<RESET>
 		<CYAN>@@ -1 +1 @@<RESET>
@@ -282,9 +291,11 @@ test_expect_success 'test when words are only removed at the end' '
 test_expect_success '--word-diff=none' '
 	echo "(:" >pre &&
 	echo "(" >post &&
-	cat >expect <<-\EOF &&
+	pre=$(git rev-parse --short $(git hash-object pre)) &&
+	post=$(git rev-parse --short $(git hash-object post)) &&
+	cat >expect <<-EOF &&
 		diff --git a/pre b/post
-		index 289cb9d..2d06f37 100644
+		index $pre..$post 100644
 		--- a/pre
 		+++ b/post
 		@@ -1 +1 @@
@@ -303,6 +314,7 @@ test_language_driver bibtex
 test_language_driver cpp
 test_language_driver csharp
 test_language_driver css
+test_language_driver dts
 test_language_driver fortran
 test_language_driver html
 test_language_driver java
@@ -316,16 +328,6 @@ test_language_driver ruby
 test_language_driver tex
 
 test_expect_success 'word-diff with diff.sbe' '
-	cat >expect <<-\EOF &&
-	diff --git a/pre b/post
-	index a1a53b5..bc8fe6d 100644
-	--- a/pre
-	+++ b/post
-	@@ -1,3 +1,3 @@
-	a
-
-	[-b-]{+c+}
-	EOF
 	cat >pre <<-\EOF &&
 	a
 
@@ -336,21 +338,35 @@ test_expect_success 'word-diff with diff.sbe' '
 
 	c
 	EOF
+	pre=$(git rev-parse --short $(git hash-object pre)) &&
+	post=$(git rev-parse --short $(git hash-object post)) &&
+	cat >expect <<-EOF &&
+	diff --git a/pre b/post
+	index $pre..$post 100644
+	--- a/pre
+	+++ b/post
+	@@ -1,3 +1,3 @@
+	a
+
+	[-b-]{+c+}
+	EOF
 	test_config diff.suppress-blank-empty true &&
 	word_diff --word-diff=plain
 '
 
 test_expect_success 'word-diff with no newline at EOF' '
-	cat >expect <<-\EOF &&
+	printf "%s" "a a a a a" >pre &&
+	printf "%s" "a a ab a a" >post &&
+	pre=$(git rev-parse --short $(git hash-object pre)) &&
+	post=$(git rev-parse --short $(git hash-object post)) &&
+	cat >expect <<-EOF &&
 	diff --git a/pre b/post
-	index 7bf316e..3dd0303 100644
+	index $pre..$post 100644
 	--- a/pre
 	+++ b/post
 	@@ -1 +1 @@
 	a a [-a-]{+ab+} a a
 	EOF
-	printf "%s" "a a a a a" >pre &&
-	printf "%s" "a a ab a a" >post &&
 	word_diff --word-diff=plain
 '
 
diff --git a/third_party/git/t/t4034/dts/expect b/third_party/git/t/t4034/dts/expect
new file mode 100644
index 0000000000..560fc99184
--- /dev/null
+++ b/third_party/git/t/t4034/dts/expect
@@ -0,0 +1,37 @@
+<BOLD>diff --git a/pre b/post<RESET>
+<BOLD>index b6a9051..7803aee 100644<RESET>
+<BOLD>--- a/pre<RESET>
+<BOLD>+++ b/post<RESET>
+<CYAN>@@ -1,32 +1,32 @@<RESET>
+/ {<RESET>
+	<RED>this_handle<RESET><GREEN>HANDLE_2<RESET>: <RED>node<RESET><GREEN>new-node<RESET>@<RED>f00<RESET><GREEN>eeda<RESET> {
+		compatible = "<RED>mydev<RESET><GREEN>vendor,compat<RESET>";
+		string-prop = <RED>start<RESET><GREEN>end<RESET>: "hello <RED>world!<RESET><GREEN>world?<RESET>" <RED>end<RESET><GREEN>start<RESET>: ;
+		<RED>#size-cells<RESET><GREEN>#address-cells<RESET> = <<RED>0+0<RESET><GREEN>0+40<RESET>>;
+		reg = <<RED>0xf00<RESET><GREEN>0xeeda<RESET>>;
+		prop = <<GREEN>(<RESET>1<GREEN>)<RESET>>;
+		prop = <<GREEN>(<RESET>-1e10<GREEN>)<RESET>>;
+		prop = <(!<RED>3<RESET><GREEN>1<RESET>)>;
+		prop = <(~<RED>3<RESET><GREEN>1<RESET>)>;
+		prop = <(<RED>3<RESET><GREEN>1<RESET>*<RED>4<RESET><GREEN>2<RESET>)>;
+		prop = <(<RED>3<RESET><GREEN>1<RESET>&<RED>4<RESET><GREEN>2<RESET>)>;
+		prop = <(<RED>3<RESET><GREEN>1<RESET>*<RED>4<RESET><GREEN>2<RESET>)>;
+		prop = <(<RED>3<RESET><GREEN>1<RESET>/<RED>4<RESET><GREEN>2<RESET>)>;
+		prop = <(<RED>3<RESET><GREEN>1<RESET>%<RED>4<RESET><GREEN>2<RESET>)>;
+		prop = <(<RED>3+4<RESET><GREEN>1+2<RESET>)>;
+		prop = <(<RED>3-4<RESET><GREEN>1-2<RESET>)>;
+		prop = /bits/ <RED>64<RESET><GREEN>32<RESET> <(<RED>3<RESET><GREEN>1<RESET><<<RED>4<RESET><GREEN>2<RESET>)>;
+		prop = <(<RED>3<RESET><GREEN>1<RESET>>><RED>4<RESET><GREEN>2<RESET>)>;
+		prop = <(<RED>3<RESET><GREEN>1<RESET>&<RED>4<RESET><GREEN>2<RESET>)>;
+		prop = <(<RED>3<RESET><GREEN>1<RESET>^<RED>4<RESET><GREEN>2<RESET>)>;
+		prop = <(<RED>3<RESET><GREEN>1<RESET>|<RED>4<RESET><GREEN>2<RESET>)>;
+		prop = <(<RED>3<RESET><GREEN>1<RESET>&&<RED>4<RESET><GREEN>2<RESET>)>;
+		prop = <(<RED>3<RESET><GREEN>1<RESET>||<RED>4<RESET><GREEN>2<RESET>)>;
+		prop = <(<RED>4?5<RESET><GREEN>1?2<RESET>:3)>;
+		list = <&<RED>this_handle<RESET><GREEN>HANDLE_2<RESET>>, <0 0 0 <RED>0<RESET><GREEN>1<RESET>>;
+	};<RESET>
+
+	&<RED>phandle<RESET><GREEN>phandle2<RESET> {
+		<RED>pre-phandle<RESET><GREEN>prop_handle<RESET> = <&<RED>this_handle<RESET><GREEN>HANDLE_2<RESET>>;
+	};<RESET>
+};<RESET>
diff --git a/third_party/git/t/t4034/dts/post b/third_party/git/t/t4034/dts/post
new file mode 100644
index 0000000000..7803aee280
--- /dev/null
+++ b/third_party/git/t/t4034/dts/post
@@ -0,0 +1,32 @@
+/ {
+	HANDLE_2: new-node@eeda {
+		compatible = "vendor,compat";
+		string-prop = end: "hello world?" start: ;
+		#address-cells = <0+40>;
+		reg = <0xeeda>;
+		prop = <(1)>;
+		prop = <(-1e10)>;
+		prop = <(!1)>;
+		prop = <(~1)>;
+		prop = <(1*2)>;
+		prop = <(1&2)>;
+		prop = <(1*2)>;
+		prop = <(1/2)>;
+		prop = <(1%2)>;
+		prop = <(1+2)>;
+		prop = <(1-2)>;
+		prop = /bits/ 32 <(1<<2)>;
+		prop = <(1>>2)>;
+		prop = <(1&2)>;
+		prop = <(1^2)>;
+		prop = <(1|2)>;
+		prop = <(1&&2)>;
+		prop = <(1||2)>;
+		prop = <(1?2:3)>;
+		list = <&HANDLE_2>, <0 0 0 1>;
+	};
+
+	&phandle2 {
+		prop_handle = <&HANDLE_2>;
+	};
+};
diff --git a/third_party/git/t/t4034/dts/pre b/third_party/git/t/t4034/dts/pre
new file mode 100644
index 0000000000..b6a905113c
--- /dev/null
+++ b/third_party/git/t/t4034/dts/pre
@@ -0,0 +1,32 @@
+/ {
+	this_handle: node@f00 {
+		compatible = "mydev";
+		string-prop = start: "hello world!" end: ;
+		#size-cells = <0+0>;
+		reg = <0xf00>;
+		prop = <1>;
+		prop = <-1e10>;
+		prop = <(!3)>;
+		prop = <(~3)>;
+		prop = <(3*4)>;
+		prop = <(3&4)>;
+		prop = <(3*4)>;
+		prop = <(3/4)>;
+		prop = <(3%4)>;
+		prop = <(3+4)>;
+		prop = <(3-4)>;
+		prop = /bits/ 64 <(3<<4)>;
+		prop = <(3>>4)>;
+		prop = <(3&4)>;
+		prop = <(3^4)>;
+		prop = <(3|4)>;
+		prop = <(3&&4)>;
+		prop = <(3||4)>;
+		prop = <(4?5:3)>;
+		list = <&this_handle>, <0 0 0 0>;
+	};
+
+	&phandle {
+		pre-phandle = <&this_handle>;
+	};
+};
diff --git a/third_party/git/t/t4038-diff-combined.sh b/third_party/git/t/t4038-diff-combined.sh
index d4afe12554..94680836ce 100755
--- a/third_party/git/t/t4038-diff-combined.sh
+++ b/third_party/git/t/t4038-diff-combined.sh
@@ -354,7 +354,7 @@ test_expect_failure 'combine diff coalesce three parents' '
 '
 
 # Test for a bug reported at
-# https://public-inbox.org/git/20130515143508.GO25742@login.drsnuggles.stderr.nl/
+# https://lore.kernel.org/git/20130515143508.GO25742@login.drsnuggles.stderr.nl/
 # where a delete lines were missing from combined diff output when they
 # occurred exactly before the context lines of a later change.
 test_expect_success 'combine diff missing delete bug' '
@@ -440,11 +440,13 @@ test_expect_success 'setup for --combined-all-paths' '
 	git branch side2c &&
 	git checkout side1c &&
 	test_seq 1 10 >filename-side1c &&
+	side1cf=$(git hash-object filename-side1c) &&
 	git add filename-side1c &&
 	git commit -m with &&
 	git checkout side2c &&
 	test_seq 1 9 >filename-side2c &&
 	echo ten >>filename-side2c &&
+	side2cf=$(git hash-object filename-side2c) &&
 	git add filename-side2c &&
 	git commit -m iam &&
 	git checkout -b mergery side1c &&
@@ -452,13 +454,14 @@ test_expect_success 'setup for --combined-all-paths' '
 	git rm filename-side1c &&
 	echo eleven >>filename-side2c &&
 	git mv filename-side2c filename-merged &&
+	mergedf=$(git hash-object filename-merged) &&
 	git add filename-merged &&
 	git commit
 '
 
 test_expect_success '--combined-all-paths and --raw' '
-	cat <<-\EOF >expect &&
-	::100644 100644 100644 f00c965d8307308469e537302baa73048488f162 088bd5d92c2a8e0203ca8e7e4c2a5c692f6ae3f7 333b9c62519f285e1854830ade0fe1ef1d40ee1b RR	filename-side1c	filename-side2c	filename-merged
+	cat <<-EOF >expect &&
+	::100644 100644 100644 $side1cf $side2cf $mergedf RR	filename-side1c	filename-side2c	filename-merged
 	EOF
 	git diff-tree -c -M --raw --combined-all-paths HEAD >actual.tmp &&
 	sed 1d <actual.tmp >actual &&
@@ -482,11 +485,13 @@ test_expect_success FUNNYNAMES 'setup for --combined-all-paths with funny names'
 	git checkout side1d &&
 	test_seq 1 10 >"$(printf "file\twith\ttabs")" &&
 	git add file* &&
+	side1df=$(git hash-object *tabs) &&
 	git commit -m with &&
 	git checkout side2d &&
 	test_seq 1 9 >"$(printf "i\tam\ttabbed")" &&
 	echo ten >>"$(printf "i\tam\ttabbed")" &&
 	git add *tabbed &&
+	side2df=$(git hash-object *tabbed) &&
 	git commit -m iam &&
 	git checkout -b funny-names-mergery side1d &&
 	git merge --no-commit side2d &&
@@ -494,12 +499,14 @@ test_expect_success FUNNYNAMES 'setup for --combined-all-paths with funny names'
 	echo eleven >>"$(printf "i\tam\ttabbed")" &&
 	git mv "$(printf "i\tam\ttabbed")" "$(printf "fickle\tnaming")" &&
 	git add fickle* &&
-	git commit
+	headf=$(git hash-object fickle*) &&
+	git commit &&
+	head=$(git rev-parse HEAD)
 '
 
 test_expect_success FUNNYNAMES '--combined-all-paths and --raw and funny names' '
-	cat <<-\EOF >expect &&
-	::100644 100644 100644 f00c965d8307308469e537302baa73048488f162 088bd5d92c2a8e0203ca8e7e4c2a5c692f6ae3f7 333b9c62519f285e1854830ade0fe1ef1d40ee1b RR	"file\twith\ttabs"	"i\tam\ttabbed"	"fickle\tnaming"
+	cat <<-EOF >expect &&
+	::100644 100644 100644 $side1df $side2df $headf RR	"file\twith\ttabs"	"i\tam\ttabbed"	"fickle\tnaming"
 	EOF
 	git diff-tree -c -M --raw --combined-all-paths HEAD >actual.tmp &&
 	sed 1d <actual.tmp >actual &&
@@ -507,9 +514,9 @@ test_expect_success FUNNYNAMES '--combined-all-paths and --raw and funny names'
 '
 
 test_expect_success FUNNYNAMES '--combined-all-paths and --raw -and -z and funny names' '
-	printf "aaf8087c3cbd4db8e185a2d074cf27c53cfb75d7\0::100644 100644 100644 f00c965d8307308469e537302baa73048488f162 088bd5d92c2a8e0203ca8e7e4c2a5c692f6ae3f7 333b9c62519f285e1854830ade0fe1ef1d40ee1b RR\0file\twith\ttabs\0i\tam\ttabbed\0fickle\tnaming\0" >expect &&
+	printf "$head\0::100644 100644 100644 $side1df $side2df $headf RR\0file\twith\ttabs\0i\tam\ttabbed\0fickle\tnaming\0" >expect &&
 	git diff-tree -c -M --raw --combined-all-paths -z HEAD >actual &&
-	test_cmp -a expect actual
+	test_cmp expect actual
 '
 
 test_expect_success FUNNYNAMES '--combined-all-paths and --cc and funny names' '
diff --git a/third_party/git/t/t4039-diff-assume-unchanged.sh b/third_party/git/t/t4039-diff-assume-unchanged.sh
index 53ac44b0f0..0eb0314a8b 100755
--- a/third_party/git/t/t4039-diff-assume-unchanged.sh
+++ b/third_party/git/t/t4039-diff-assume-unchanged.sh
@@ -12,6 +12,7 @@ test_expect_success 'setup' '
 	git commit -m zero &&
 	echo one > one &&
 	echo two > two &&
+	blob=$(git hash-object one) &&
 	git add one two &&
 	git commit -m onetwo &&
 	git update-index --assume-unchanged one &&
@@ -20,7 +21,7 @@ test_expect_success 'setup' '
 '
 
 test_expect_success 'diff-index does not examine assume-unchanged entries' '
-	git diff-index HEAD^ -- one | grep -q 5626abf0f72e58d7a153368ba57db4c673c0e171
+	git diff-index HEAD^ -- one | grep -q $blob
 '
 
 test_expect_success 'diff-files does not examine assume-unchanged entries' '
diff --git a/third_party/git/t/t4041-diff-submodule-option.sh b/third_party/git/t/t4041-diff-submodule-option.sh
index 619bf97098..f852136585 100755
--- a/third_party/git/t/t4041-diff-submodule-option.sh
+++ b/third_party/git/t/t4041-diff-submodule-option.sh
@@ -284,7 +284,7 @@ test_expect_success 'submodule contains untracked content (all ignored)' '
 	test_must_be_empty actual
 '
 
-test_expect_success 'submodule contains untracked and modifed content' '
+test_expect_success 'submodule contains untracked and modified content' '
 	echo new > sm1/foo6 &&
 	git diff-index -p --submodule=log HEAD >actual &&
 	cat >expected <<-EOF &&
@@ -294,7 +294,7 @@ test_expect_success 'submodule contains untracked and modifed content' '
 	test_cmp expected actual
 '
 
-test_expect_success 'submodule contains untracked and modifed content (untracked ignored)' '
+test_expect_success 'submodule contains untracked and modified content (untracked ignored)' '
 	echo new > sm1/foo6 &&
 	git diff-index -p --ignore-submodules=untracked --submodule=log HEAD >actual &&
 	cat >expected <<-EOF &&
@@ -303,19 +303,19 @@ test_expect_success 'submodule contains untracked and modifed content (untracked
 	test_cmp expected actual
 '
 
-test_expect_success 'submodule contains untracked and modifed content (dirty ignored)' '
+test_expect_success 'submodule contains untracked and modified content (dirty ignored)' '
 	echo new > sm1/foo6 &&
 	git diff-index -p --ignore-submodules=dirty --submodule=log HEAD >actual &&
 	test_must_be_empty actual
 '
 
-test_expect_success 'submodule contains untracked and modifed content (all ignored)' '
+test_expect_success 'submodule contains untracked and modified content (all ignored)' '
 	echo new > sm1/foo6 &&
 	git diff-index -p --ignore-submodules --submodule=log HEAD >actual &&
 	test_must_be_empty actual
 '
 
-test_expect_success 'submodule contains modifed content' '
+test_expect_success 'submodule contains modified content' '
 	rm -f sm1/new-file &&
 	git diff-index -p --submodule=log HEAD >actual &&
 	cat >expected <<-EOF &&
@@ -369,7 +369,7 @@ test_expect_success 'modified submodule contains untracked content (all ignored)
 	test_must_be_empty actual
 '
 
-test_expect_success 'modified submodule contains untracked and modifed content' '
+test_expect_success 'modified submodule contains untracked and modified content' '
 	echo modification >> sm1/foo6 &&
 	git diff-index -p --submodule=log HEAD >actual &&
 	cat >expected <<-EOF &&
@@ -381,7 +381,7 @@ test_expect_success 'modified submodule contains untracked and modifed content'
 	test_cmp expected actual
 '
 
-test_expect_success 'modified submodule contains untracked and modifed content (untracked ignored)' '
+test_expect_success 'modified submodule contains untracked and modified content (untracked ignored)' '
 	echo modification >> sm1/foo6 &&
 	git diff-index -p --ignore-submodules=untracked --submodule=log HEAD >actual &&
 	cat >expected <<-EOF &&
@@ -392,7 +392,7 @@ test_expect_success 'modified submodule contains untracked and modifed content (
 	test_cmp expected actual
 '
 
-test_expect_success 'modified submodule contains untracked and modifed content (dirty ignored)' '
+test_expect_success 'modified submodule contains untracked and modified content (dirty ignored)' '
 	echo modification >> sm1/foo6 &&
 	git diff-index -p --ignore-submodules=dirty --submodule=log HEAD >actual &&
 	cat >expected <<-EOF &&
@@ -402,13 +402,13 @@ test_expect_success 'modified submodule contains untracked and modifed content (
 	test_cmp expected actual
 '
 
-test_expect_success 'modified submodule contains untracked and modifed content (all ignored)' '
+test_expect_success 'modified submodule contains untracked and modified content (all ignored)' '
 	echo modification >> sm1/foo6 &&
 	git diff-index -p --ignore-submodules --submodule=log HEAD >actual &&
 	test_must_be_empty actual
 '
 
-test_expect_success 'modified submodule contains modifed content' '
+test_expect_success 'modified submodule contains modified content' '
 	rm -f sm1/new-file &&
 	git diff-index -p --submodule=log HEAD >actual &&
 	cat >expected <<-EOF &&
diff --git a/third_party/git/t/t4044-diff-index-unique-abbrev.sh b/third_party/git/t/t4044-diff-index-unique-abbrev.sh
index 647905e01f..4701796d10 100755
--- a/third_party/git/t/t4044-diff-index-unique-abbrev.sh
+++ b/third_party/git/t/t4044-diff-index-unique-abbrev.sh
@@ -3,34 +3,48 @@
 test_description='test unique sha1 abbreviation on "index from..to" line'
 . ./test-lib.sh
 
-if ! test_have_prereq SHA1
-then
-       skip_all='not using SHA-1 for objects'
-       test_done
-fi
-
-cat >expect_initial <<EOF
-100644 blob 51d2738463ea4ca66f8691c91e33ce64b7d41bb1	foo
-EOF
+test_expect_success 'setup' '
+	test_oid_cache <<-EOF &&
+	val1 sha1:4827
+	val1 sha256:5664
 
-cat >expect_update <<EOF
-100644 blob 51d2738efb4ad8a1e40bed839ab8e116f0a15e47	foo
-EOF
+	val2 sha1:11742
+	val2 sha256:10625
 
-test_expect_success 'setup' '
-	echo 4827 > foo &&
+	hash1 sha1:51d2738463ea4ca66f8691c91e33ce64b7d41bb1
+	hash1 sha256:ae31dfff0af93b2c62b0098a039b38569c43b0a7e97b873000ca42d128f27350
+
+	hasht1 sha1:51d27384
+	hasht1 sha256:ae31dfff
+
+	hash2 sha1:51d2738efb4ad8a1e40bed839ab8e116f0a15e47
+	hash2 sha256:ae31dffada88a46fd5f53c7ed5aa25a7a8951f1d5e88456c317c8d5484d263e5
+
+	hasht2 sha1:51d2738e
+	hasht2 sha256:ae31dffa
+	EOF
+
+	cat >expect_initial <<-EOF &&
+	100644 blob $(test_oid hash1)	foo
+	EOF
+
+	cat >expect_update <<-EOF &&
+	100644 blob $(test_oid hash2)	foo
+	EOF
+
+	echo "$(test_oid val1)" > foo &&
 	git add foo &&
 	git commit -m "initial" &&
 	git cat-file -p HEAD: > actual &&
 	test_cmp expect_initial actual &&
-	echo 11742 > foo &&
+	echo "$(test_oid val2)" > foo &&
 	git commit -a -m "update" &&
 	git cat-file -p HEAD: > actual &&
 	test_cmp expect_update actual
 '
 
 cat >expect <<EOF
-index 51d27384..51d2738e 100644
+index $(test_oid hasht1)..$(test_oid hasht2) 100644
 EOF
 
 test_expect_success 'diff does not produce ambiguous index line' '
diff --git a/third_party/git/t/t4045-diff-relative.sh b/third_party/git/t/t4045-diff-relative.sh
index 36f8ed8a81..7be1de736d 100755
--- a/third_party/git/t/t4045-diff-relative.sh
+++ b/third_party/git/t/t4045-diff-relative.sh
@@ -8,7 +8,8 @@ test_expect_success 'setup' '
 	echo content >file1 &&
 	mkdir subdir &&
 	echo other content >subdir/file2 &&
-	blob=$(git hash-object subdir/file2) &&
+	blob_file1=$(git hash-object file1) &&
+	blob_file2=$(git hash-object subdir/file2) &&
 	git add . &&
 	git commit -m one
 '
@@ -18,7 +19,7 @@ check_diff () {
 	shift
 	expect=$1
 	shift
-	short_blob=$(git rev-parse --short $blob)
+	short_blob=$(git rev-parse --short $blob_file2)
 	cat >expected <<-EOF
 	diff --git a/$expect b/$expect
 	new file mode 100644
@@ -70,7 +71,7 @@ check_raw () {
 	expect=$1
 	shift
 	cat >expected <<-EOF
-	:000000 100644 0000000000000000000000000000000000000000 $blob A	$expect
+	:000000 100644 $ZERO_OID $blob_file2 A	$expect
 	EOF
 	test_expect_success "--raw $*" "
 		git -C '$dir' diff --no-abbrev --raw $* HEAD^ >actual &&
@@ -86,4 +87,79 @@ do
 	check_$type . dir/file2 --relative=sub
 done
 
+check_diff_relative_option () {
+	dir=$1
+	shift
+	expect=$1
+	shift
+	relative_opt=$1
+	shift
+	test_expect_success "config diff.relative $relative_opt -p $*" "
+		short_blob=\$(git rev-parse --short $blob_file2) &&
+		cat >expected <<-EOF &&
+		diff --git a/$expect b/$expect
+		new file mode 100644
+		index 0000000..\$short_blob
+		--- /dev/null
+		+++ b/$expect
+		@@ -0,0 +1 @@
+		+other content
+		EOF
+		test_config -C $dir diff.relative $relative_opt &&
+		git -C '$dir' diff -p $* HEAD^ >actual &&
+		test_cmp expected actual
+	"
+}
+
+check_diff_no_relative_option () {
+	dir=$1
+	shift
+	expect=$1
+	shift
+	relative_opt=$1
+	shift
+	test_expect_success "config diff.relative $relative_opt -p $*" "
+		short_blob_file1=\$(git rev-parse --short $blob_file1) &&
+		short_blob_file2=\$(git rev-parse --short $blob_file2) &&
+		cat >expected <<-EOF &&
+		diff --git a/file1 b/file1
+		new file mode 100644
+		index 0000000..\$short_blob_file1
+		--- /dev/null
+		+++ b/file1
+		@@ -0,0 +1 @@
+		+content
+		diff --git a/$expect b/$expect
+		new file mode 100644
+		index 0000000..\$short_blob_file2
+		--- /dev/null
+		+++ b/$expect
+		@@ -0,0 +1 @@
+		+other content
+		EOF
+		test_config -C $dir diff.relative $relative_opt &&
+		git -C '$dir' diff -p $* HEAD^ >actual &&
+		test_cmp expected actual
+	"
+}
+
+check_diff_no_relative_option . subdir/file2 false
+check_diff_no_relative_option . subdir/file2 true --no-relative
+check_diff_no_relative_option . subdir/file2 false --no-relative
+check_diff_no_relative_option subdir subdir/file2 false
+check_diff_no_relative_option subdir subdir/file2 true --no-relative
+check_diff_no_relative_option subdir subdir/file2 false --no-relative
+
+check_diff_relative_option . file2 false --relative=subdir/
+check_diff_relative_option . file2 false --relative=subdir
+check_diff_relative_option . file2 true --relative=subdir/
+check_diff_relative_option . file2 true --relative=subdir
+check_diff_relative_option subdir file2 false --relative
+check_diff_relative_option subdir file2 true --relative
+check_diff_relative_option subdir file2 true
+check_diff_relative_option subdir file2 false --no-relative --relative
+check_diff_relative_option subdir file2 true --no-relative --relative
+check_diff_relative_option . file2 false --no-relative --relative=subdir
+check_diff_relative_option . file2 true --no-relative --relative=subdir
+
 test_done
diff --git a/third_party/git/t/t4048-diff-combined-binary.sh b/third_party/git/t/t4048-diff-combined-binary.sh
index 87a8949500..7f9ad9fa3d 100755
--- a/third_party/git/t/t4048-diff-combined-binary.sh
+++ b/third_party/git/t/t4048-diff-combined-binary.sh
@@ -9,24 +9,27 @@ test_expect_success 'setup binary merge conflict' '
 	git commit -m one &&
 	echo twoQ2 | q_to_nul >binary &&
 	git commit -a -m two &&
+	two=$(git rev-parse --short HEAD:binary) &&
 	git checkout -b branch-binary HEAD^ &&
 	echo threeQ3 | q_to_nul >binary &&
 	git commit -a -m three &&
+	three=$(git rev-parse --short HEAD:binary) &&
 	test_must_fail git merge master &&
 	echo resolvedQhooray | q_to_nul >binary &&
-	git commit -a -m resolved
+	git commit -a -m resolved &&
+	res=$(git rev-parse --short HEAD:binary)
 '
 
-cat >expect <<'EOF'
+cat >expect <<EOF
 resolved
 
 diff --git a/binary b/binary
-index 7ea6ded..9563691 100644
+index $three..$res 100644
 Binary files a/binary and b/binary differ
 resolved
 
 diff --git a/binary b/binary
-index 6197570..9563691 100644
+index $two..$res 100644
 Binary files a/binary and b/binary differ
 EOF
 test_expect_success 'diff -m indicates binary-ness' '
@@ -34,11 +37,11 @@ test_expect_success 'diff -m indicates binary-ness' '
 	test_cmp expect actual
 '
 
-cat >expect <<'EOF'
+cat >expect <<EOF
 resolved
 
 diff --combined binary
-index 7ea6ded,6197570..9563691
+index $three,$two..$res
 Binary files differ
 EOF
 test_expect_success 'diff -c indicates binary-ness' '
@@ -46,11 +49,11 @@ test_expect_success 'diff -c indicates binary-ness' '
 	test_cmp expect actual
 '
 
-cat >expect <<'EOF'
+cat >expect <<EOF
 resolved
 
 diff --cc binary
-index 7ea6ded,6197570..9563691
+index $three,$two..$res
 Binary files differ
 EOF
 test_expect_success 'diff --cc indicates binary-ness' '
@@ -62,23 +65,26 @@ test_expect_success 'setup non-binary with binary attribute' '
 	git checkout master &&
 	test_commit one text &&
 	test_commit two text &&
+	two=$(git rev-parse --short HEAD:text) &&
 	git checkout -b branch-text HEAD^ &&
 	test_commit three text &&
+	three=$(git rev-parse --short HEAD:text) &&
 	test_must_fail git merge master &&
 	test_commit resolved text &&
+	res=$(git rev-parse --short HEAD:text) &&
 	echo text -diff >.gitattributes
 '
 
-cat >expect <<'EOF'
+cat >expect <<EOF
 resolved
 
 diff --git a/text b/text
-index 2bdf67a..2ab19ae 100644
+index $three..$res 100644
 Binary files a/text and b/text differ
 resolved
 
 diff --git a/text b/text
-index f719efd..2ab19ae 100644
+index $two..$res 100644
 Binary files a/text and b/text differ
 EOF
 test_expect_success 'diff -m respects binary attribute' '
@@ -86,11 +92,11 @@ test_expect_success 'diff -m respects binary attribute' '
 	test_cmp expect actual
 '
 
-cat >expect <<'EOF'
+cat >expect <<EOF
 resolved
 
 diff --combined text
-index 2bdf67a,f719efd..2ab19ae
+index $three,$two..$res
 Binary files differ
 EOF
 test_expect_success 'diff -c respects binary attribute' '
@@ -98,11 +104,11 @@ test_expect_success 'diff -c respects binary attribute' '
 	test_cmp expect actual
 '
 
-cat >expect <<'EOF'
+cat >expect <<EOF
 resolved
 
 diff --cc text
-index 2bdf67a,f719efd..2ab19ae
+index $three,$two..$res
 Binary files differ
 EOF
 test_expect_success 'diff --cc respects binary attribute' '
@@ -115,11 +121,11 @@ test_expect_success 'setup textconv attribute' '
 	git config diff.upcase.textconv "tr a-z A-Z <"
 '
 
-cat >expect <<'EOF'
+cat >expect <<EOF
 resolved
 
 diff --git a/text b/text
-index 2bdf67a..2ab19ae 100644
+index $three..$res 100644
 --- a/text
 +++ b/text
 @@ -1 +1 @@
@@ -128,7 +134,7 @@ index 2bdf67a..2ab19ae 100644
 resolved
 
 diff --git a/text b/text
-index f719efd..2ab19ae 100644
+index $two..$res 100644
 --- a/text
 +++ b/text
 @@ -1 +1 @@
@@ -140,11 +146,11 @@ test_expect_success 'diff -m respects textconv attribute' '
 	test_cmp expect actual
 '
 
-cat >expect <<'EOF'
+cat >expect <<EOF
 resolved
 
 diff --combined text
-index 2bdf67a,f719efd..2ab19ae
+index $three,$two..$res
 --- a/text
 +++ b/text
 @@@ -1,1 -1,1 +1,1 @@@
@@ -157,11 +163,11 @@ test_expect_success 'diff -c respects textconv attribute' '
 	test_cmp expect actual
 '
 
-cat >expect <<'EOF'
+cat >expect <<EOF
 resolved
 
 diff --cc text
-index 2bdf67a,f719efd..2ab19ae
+index $three,$two..$res
 --- a/text
 +++ b/text
 @@@ -1,1 -1,1 +1,1 @@@
@@ -174,9 +180,9 @@ test_expect_success 'diff --cc respects textconv attribute' '
 	test_cmp expect actual
 '
 
-cat >expect <<'EOF'
+cat >expect <<EOF
 diff --combined text
-index 2bdf67a,f719efd..2ab19ae
+index $three,$two..$res
 --- a/text
 +++ b/text
 @@@ -1,1 -1,1 +1,1 @@@
@@ -190,9 +196,9 @@ test_expect_success 'diff-tree plumbing does not respect textconv' '
 	test_cmp expect actual
 '
 
-cat >expect <<'EOF'
+cat >expect <<EOF
 diff --cc text
-index 2bdf67a,f719efd..0000000
+index $three,$two..0000000
 --- a/text
 +++ b/text
 @@@ -1,1 -1,1 +1,5 @@@
diff --git a/third_party/git/t/t4054-diff-bogus-tree.sh b/third_party/git/t/t4054-diff-bogus-tree.sh
index fcae82fffa..8c95f152b2 100755
--- a/third_party/git/t/t4054-diff-bogus-tree.sh
+++ b/third_party/git/t/t4054-diff-bogus-tree.sh
@@ -4,8 +4,9 @@ test_description='test diff with a bogus tree containing the null sha1'
 . ./test-lib.sh
 
 test_expect_success 'create bogus tree' '
+	name=$(echo $ZERO_OID | sed -e "s/00/Q/g") &&
 	bogus_tree=$(
-		printf "100644 fooQQQQQQQQQQQQQQQQQQQQQ" |
+		printf "100644 fooQ$name" |
 		q_to_nul |
 		git hash-object -w --stdin -t tree
 	)
diff --git a/third_party/git/t/t4057-diff-combined-paths.sh b/third_party/git/t/t4057-diff-combined-paths.sh
index dff36b77ec..0b78573733 100755
--- a/third_party/git/t/t4057-diff-combined-paths.sh
+++ b/third_party/git/t/t4057-diff-combined-paths.sh
@@ -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" &&
@@ -33,7 +33,7 @@ test_expect_success 'trivial merge - combine-diff empty' '
 '
 
 
-test_expect_success 'only one trully conflicting path' '
+test_expect_success 'only one truly conflicting path' '
 	git checkout side &&
 	for i in $(test_seq 2 9)
 	do
diff --git a/third_party/git/t/t4060-diff-submodule-option-diff-format.sh b/third_party/git/t/t4060-diff-submodule-option-diff-format.sh
index 9dcb69df5c..fc8229c726 100755
--- a/third_party/git/t/t4060-diff-submodule-option-diff-format.sh
+++ b/third_party/git/t/t4060-diff-submodule-option-diff-format.sh
@@ -42,6 +42,17 @@ commit_file () {
 	git commit "$@" -m "Commit $*" >/dev/null
 }
 
+diff_cmp () {
+       for i in "$1" "$2"
+       do
+		sed -e 's/^index 0000000\.\.[0-9a-f]*/index 0000000..1234567/' \
+		-e 's/^index [0-9a-f]*\.\.[0-9a-f]*/index 1234567..89abcde/' \
+		"$i" >"$i.compare" || return 1
+       done &&
+       test_cmp "$1.compare" "$2.compare" &&
+       rm -f "$1.compare" "$2.compare"
+}
+
 test_expect_success 'setup repository' '
 	test_create_repo sm1 &&
 	add_file . foo &&
@@ -69,7 +80,7 @@ test_expect_success 'added submodule' '
 	@@ -0,0 +1 @@
 	+foo2
 	EOF
-	test_cmp expected actual
+	diff_cmp expected actual
 '
 
 test_expect_success 'added submodule, set diff.submodule' '
@@ -93,7 +104,7 @@ test_expect_success 'added submodule, set diff.submodule' '
 	@@ -0,0 +1 @@
 	+foo2
 	EOF
-	test_cmp expected actual
+	diff_cmp expected actual
 '
 
 test_expect_success '--submodule=short overrides diff.submodule' '
@@ -109,7 +120,7 @@ test_expect_success '--submodule=short overrides diff.submodule' '
 	@@ -0,0 +1 @@
 	+Subproject commit $fullhead1
 	EOF
-	test_cmp expected actual
+	diff_cmp expected actual
 '
 
 test_expect_success 'diff.submodule does not affect plumbing' '
@@ -124,7 +135,7 @@ test_expect_success 'diff.submodule does not affect plumbing' '
 	@@ -0,0 +1 @@
 	+Subproject commit $fullhead1
 	EOF
-	test_cmp expected actual
+	diff_cmp expected actual
 '
 
 commit_file sm1 &&
@@ -142,7 +153,7 @@ test_expect_success 'modified submodule(forward)' '
 	@@ -0,0 +1 @@
 	+foo3
 	EOF
-	test_cmp expected actual
+	diff_cmp expected actual
 '
 
 test_expect_success 'modified submodule(forward)' '
@@ -157,7 +168,7 @@ test_expect_success 'modified submodule(forward)' '
 	@@ -0,0 +1 @@
 	+foo3
 	EOF
-	test_cmp expected actual
+	diff_cmp expected actual
 '
 
 test_expect_success 'modified submodule(forward) --submodule' '
@@ -166,7 +177,7 @@ test_expect_success 'modified submodule(forward) --submodule' '
 	Submodule sm1 $head1..$head2:
 	  > Add foo3 ($added foo3)
 	EOF
-	test_cmp expected actual
+	diff_cmp expected actual
 '
 
 fullhead2=$(cd sm1; git rev-parse --verify HEAD)
@@ -181,7 +192,7 @@ test_expect_success 'modified submodule(forward) --submodule=short' '
 	-Subproject commit $fullhead1
 	+Subproject commit $fullhead2
 	EOF
-	test_cmp expected actual
+	diff_cmp expected actual
 '
 
 commit_file sm1 &&
@@ -210,7 +221,7 @@ test_expect_success 'modified submodule(backward)' '
 	@@ -1 +0,0 @@
 	-foo3
 	EOF
-	test_cmp expected actual
+	diff_cmp expected actual
 '
 
 head4=$(add_file sm1 foo4 foo5)
@@ -247,7 +258,7 @@ test_expect_success 'modified submodule(backward and forward)' '
 	@@ -0,0 +1 @@
 	+foo5
 	EOF
-	test_cmp expected actual
+	diff_cmp expected actual
 '
 
 commit_file sm1 &&
@@ -291,7 +302,7 @@ test_expect_success 'typechanged submodule(submodule->blob), --cached' '
 	@@ -0,0 +1 @@
 	+sm1
 	EOF
-	test_cmp expected actual
+	diff_cmp expected actual
 '
 
 test_expect_success 'typechanged submodule(submodule->blob)' '
@@ -327,7 +338,7 @@ test_expect_success 'typechanged submodule(submodule->blob)' '
 	@@ -0,0 +1 @@
 	+foo5
 	EOF
-	test_cmp expected actual
+	diff_cmp expected actual
 '
 
 rm -rf sm1 &&
@@ -344,7 +355,7 @@ test_expect_success 'typechanged submodule(submodule->blob)' '
 	@@ -0,0 +1 @@
 	+sm1
 	EOF
-	test_cmp expected actual
+	diff_cmp expected actual
 '
 
 rm -f sm1 &&
@@ -356,7 +367,7 @@ test_expect_success 'nonexistent commit' '
 	cat >expected <<-EOF &&
 	Submodule sm1 $head4...$head6 (commits not present)
 	EOF
-	test_cmp expected actual
+	diff_cmp expected actual
 '
 
 commit_file
@@ -386,11 +397,12 @@ test_expect_success 'typechanged submodule(blob->submodule)' '
 	@@ -0,0 +1 @@
 	+foo7
 	EOF
-	test_cmp expected actual
+	diff_cmp expected actual
 '
 
 commit_file sm1 &&
 test_expect_success 'submodule is up to date' '
+	head7=$(git -C sm1 rev-parse --short --verify HEAD) &&
 	git diff-index -p --submodule=diff HEAD >actual &&
 	test_must_be_empty actual
 '
@@ -401,7 +413,7 @@ test_expect_success 'submodule contains untracked content' '
 	cat >expected <<-EOF &&
 	Submodule sm1 contains untracked content
 	EOF
-	test_cmp expected actual
+	diff_cmp expected actual
 '
 
 test_expect_success 'submodule contains untracked content (untracked ignored)' '
@@ -433,7 +445,7 @@ test_expect_success 'submodule contains untracked and modified content' '
 	-foo6
 	+new
 	EOF
-	test_cmp expected actual
+	diff_cmp expected actual
 '
 
 # NOT OK
@@ -450,7 +462,7 @@ test_expect_success 'submodule contains untracked and modified content (untracke
 	-foo6
 	+new
 	EOF
-	test_cmp expected actual
+	diff_cmp expected actual
 '
 
 test_expect_success 'submodule contains untracked and modified content (dirty ignored)' '
@@ -478,7 +490,7 @@ test_expect_success 'submodule contains modified content' '
 	-foo6
 	+new
 	EOF
-	test_cmp expected actual
+	diff_cmp expected actual
 '
 
 (cd sm1; git commit -mchange foo6 >/dev/null) &&
@@ -486,7 +498,7 @@ head8=$(cd sm1; git rev-parse --short --verify HEAD) &&
 test_expect_success 'submodule is modified' '
 	git diff-index -p --submodule=diff HEAD >actual &&
 	cat >expected <<-EOF &&
-	Submodule sm1 17243c9..$head8:
+	Submodule sm1 $head7..$head8:
 	diff --git a/sm1/foo6 b/sm1/foo6
 	index 462398b..3e75765 100644
 	--- a/sm1/foo6
@@ -495,7 +507,7 @@ test_expect_success 'submodule is modified' '
 	-foo6
 	+new
 	EOF
-	test_cmp expected actual
+	diff_cmp expected actual
 '
 
 test_expect_success 'modified submodule contains untracked content' '
@@ -503,7 +515,7 @@ test_expect_success 'modified submodule contains untracked content' '
 	git diff-index -p --submodule=diff HEAD >actual &&
 	cat >expected <<-EOF &&
 	Submodule sm1 contains untracked content
-	Submodule sm1 17243c9..$head8:
+	Submodule sm1 $head7..$head8:
 	diff --git a/sm1/foo6 b/sm1/foo6
 	index 462398b..3e75765 100644
 	--- a/sm1/foo6
@@ -512,13 +524,13 @@ test_expect_success 'modified submodule contains untracked content' '
 	-foo6
 	+new
 	EOF
-	test_cmp expected actual
+	diff_cmp expected actual
 '
 
 test_expect_success 'modified submodule contains untracked content (untracked ignored)' '
 	git diff-index -p --ignore-submodules=untracked --submodule=diff HEAD >actual &&
 	cat >expected <<-EOF &&
-	Submodule sm1 17243c9..$head8:
+	Submodule sm1 $head7..$head8:
 	diff --git a/sm1/foo6 b/sm1/foo6
 	index 462398b..3e75765 100644
 	--- a/sm1/foo6
@@ -527,13 +539,13 @@ test_expect_success 'modified submodule contains untracked content (untracked ig
 	-foo6
 	+new
 	EOF
-	test_cmp expected actual
+	diff_cmp expected actual
 '
 
 test_expect_success 'modified submodule contains untracked content (dirty ignored)' '
 	git diff-index -p --ignore-submodules=dirty --submodule=diff HEAD >actual &&
 	cat >expected <<-EOF &&
-	Submodule sm1 17243c9..cfce562:
+	Submodule sm1 $head7..$head8:
 	diff --git a/sm1/foo6 b/sm1/foo6
 	index 462398b..3e75765 100644
 	--- a/sm1/foo6
@@ -542,7 +554,7 @@ test_expect_success 'modified submodule contains untracked content (dirty ignore
 	-foo6
 	+new
 	EOF
-	test_cmp expected actual
+	diff_cmp expected actual
 '
 
 test_expect_success 'modified submodule contains untracked content (all ignored)' '
@@ -556,7 +568,7 @@ test_expect_success 'modified submodule contains untracked and modified content'
 	cat >expected <<-EOF &&
 	Submodule sm1 contains untracked content
 	Submodule sm1 contains modified content
-	Submodule sm1 17243c9..cfce562:
+	Submodule sm1 $head7..$head8:
 	diff --git a/sm1/foo6 b/sm1/foo6
 	index 462398b..dfda541 100644
 	--- a/sm1/foo6
@@ -566,7 +578,7 @@ test_expect_success 'modified submodule contains untracked and modified content'
 	+new
 	+modification
 	EOF
-	test_cmp expected actual
+	diff_cmp expected actual
 '
 
 test_expect_success 'modified submodule contains untracked and modified content (untracked ignored)' '
@@ -574,7 +586,7 @@ test_expect_success 'modified submodule contains untracked and modified content
 	git diff-index -p --ignore-submodules=untracked --submodule=diff HEAD >actual &&
 	cat >expected <<-EOF &&
 	Submodule sm1 contains modified content
-	Submodule sm1 17243c9..cfce562:
+	Submodule sm1 $head7..$head8:
 	diff --git a/sm1/foo6 b/sm1/foo6
 	index 462398b..e20e2d9 100644
 	--- a/sm1/foo6
@@ -585,14 +597,14 @@ test_expect_success 'modified submodule contains untracked and modified content
 	+modification
 	+modification
 	EOF
-	test_cmp expected actual
+	diff_cmp expected actual
 '
 
 test_expect_success 'modified submodule contains untracked and modified content (dirty ignored)' '
 	echo modification >> sm1/foo6 &&
 	git diff-index -p --ignore-submodules=dirty --submodule=diff HEAD >actual &&
 	cat >expected <<-EOF &&
-	Submodule sm1 17243c9..cfce562:
+	Submodule sm1 $head7..$head8:
 	diff --git a/sm1/foo6 b/sm1/foo6
 	index 462398b..3e75765 100644
 	--- a/sm1/foo6
@@ -601,7 +613,7 @@ test_expect_success 'modified submodule contains untracked and modified content
 	-foo6
 	+new
 	EOF
-	test_cmp expected actual
+	diff_cmp expected actual
 '
 
 test_expect_success 'modified submodule contains untracked and modified content (all ignored)' '
@@ -616,7 +628,7 @@ test_expect_success 'modified submodule contains modified content' '
 	git diff-index -p --submodule=diff HEAD >actual &&
 	cat >expected <<-EOF &&
 	Submodule sm1 contains modified content
-	Submodule sm1 17243c9..cfce562:
+	Submodule sm1 $head7..$head8:
 	diff --git a/sm1/foo6 b/sm1/foo6
 	index 462398b..ac466ca 100644
 	--- a/sm1/foo6
@@ -629,29 +641,29 @@ test_expect_success 'modified submodule contains modified content' '
 	+modification
 	+modification
 	EOF
-	test_cmp expected actual
+	diff_cmp expected actual
 '
 
 rm -rf sm1
 test_expect_success 'deleted submodule' '
 	git diff-index -p --submodule=diff HEAD >actual &&
 	cat >expected <<-EOF &&
-	Submodule sm1 17243c9...0000000 (submodule deleted)
+	Submodule sm1 $head7...0000000 (submodule deleted)
 	EOF
-	test_cmp expected actual
+	diff_cmp expected actual
 '
 
 test_expect_success 'create second submodule' '
 	test_create_repo sm2 &&
-	head7=$(add_file sm2 foo8 foo9) &&
+	head9=$(add_file sm2 foo8 foo9) &&
 	git add sm2
 '
 
 test_expect_success 'multiple submodules' '
 	git diff-index -p --submodule=diff HEAD >actual &&
 	cat >expected <<-EOF &&
-	Submodule sm1 17243c9...0000000 (submodule deleted)
-	Submodule sm2 0000000...a5a65c9 (new submodule)
+	Submodule sm1 $head7...0000000 (submodule deleted)
+	Submodule sm2 0000000...$head9 (new submodule)
 	diff --git a/sm2/foo8 b/sm2/foo8
 	new file mode 100644
 	index 0000000..db9916b
@@ -667,13 +679,13 @@ test_expect_success 'multiple submodules' '
 	@@ -0,0 +1 @@
 	+foo9
 	EOF
-	test_cmp expected actual
+	diff_cmp expected actual
 '
 
 test_expect_success 'path filter' '
 	git diff-index -p --submodule=diff HEAD sm2 >actual &&
 	cat >expected <<-EOF &&
-	Submodule sm2 0000000...a5a65c9 (new submodule)
+	Submodule sm2 0000000...$head9 (new submodule)
 	diff --git a/sm2/foo8 b/sm2/foo8
 	new file mode 100644
 	index 0000000..db9916b
@@ -689,15 +701,15 @@ test_expect_success 'path filter' '
 	@@ -0,0 +1 @@
 	+foo9
 	EOF
-	test_cmp expected actual
+	diff_cmp expected actual
 '
 
 commit_file sm2
 test_expect_success 'given commit' '
 	git diff-index -p --submodule=diff HEAD^ >actual &&
 	cat >expected <<-EOF &&
-	Submodule sm1 17243c9...0000000 (submodule deleted)
-	Submodule sm2 0000000...a5a65c9 (new submodule)
+	Submodule sm1 $head7...0000000 (submodule deleted)
+	Submodule sm2 0000000...$head9 (new submodule)
 	diff --git a/sm2/foo8 b/sm2/foo8
 	new file mode 100644
 	index 0000000..db9916b
@@ -713,7 +725,7 @@ test_expect_success 'given commit' '
 	@@ -0,0 +1 @@
 	+foo9
 	EOF
-	test_cmp expected actual
+	diff_cmp expected actual
 '
 
 test_expect_success 'setup .git file for sm2' '
@@ -726,8 +738,8 @@ test_expect_success 'setup .git file for sm2' '
 test_expect_success 'diff --submodule=diff with .git file' '
 	git diff --submodule=diff HEAD^ >actual &&
 	cat >expected <<-EOF &&
-	Submodule sm1 17243c9...0000000 (submodule deleted)
-	Submodule sm2 0000000...a5a65c9 (new submodule)
+	Submodule sm1 $head7...0000000 (submodule deleted)
+	Submodule sm2 0000000...$head9 (new submodule)
 	diff --git a/sm2/foo8 b/sm2/foo8
 	new file mode 100644
 	index 0000000..db9916b
@@ -743,25 +755,27 @@ test_expect_success 'diff --submodule=diff with .git file' '
 	@@ -0,0 +1 @@
 	+foo9
 	EOF
-	test_cmp expected actual
+	diff_cmp expected actual
 '
 
 test_expect_success 'setup nested submodule' '
 	git submodule add -f ./sm2 &&
 	git commit -a -m "add sm2" &&
 	git -C sm2 submodule add ../sm2 nested &&
-	git -C sm2 commit -a -m "nested sub"
+	git -C sm2 commit -a -m "nested sub" &&
+	head10=$(git -C sm2 rev-parse --short --verify HEAD)
 '
 
 test_expect_success 'move nested submodule HEAD' '
 	echo "nested content" >sm2/nested/file &&
 	git -C sm2/nested add file &&
-	git -C sm2/nested commit --allow-empty -m "new HEAD"
+	git -C sm2/nested commit --allow-empty -m "new HEAD" &&
+	head11=$(git -C sm2/nested rev-parse --short --verify HEAD)
 '
 
 test_expect_success 'diff --submodule=diff with moved nested submodule HEAD' '
 	cat >expected <<-EOF &&
-	Submodule nested a5a65c9..b55928c:
+	Submodule nested $head9..$head11:
 	diff --git a/nested/file b/nested/file
 	new file mode 100644
 	index 0000000..ca281f5
@@ -772,13 +786,13 @@ test_expect_success 'diff --submodule=diff with moved nested submodule HEAD' '
 	EOF
 	git -C sm2 diff --submodule=diff >actual 2>err &&
 	test_must_be_empty err &&
-	test_cmp expected actual
+	diff_cmp expected actual
 '
 
 test_expect_success 'diff --submodule=diff recurses into nested submodules' '
 	cat >expected <<-EOF &&
 	Submodule sm2 contains modified content
-	Submodule sm2 a5a65c9..280969a:
+	Submodule sm2 $head9..$head10:
 	diff --git a/sm2/.gitmodules b/sm2/.gitmodules
 	new file mode 100644
 	index 0000000..3a816b8
@@ -788,7 +802,7 @@ test_expect_success 'diff --submodule=diff recurses into nested submodules' '
 	+[submodule "nested"]
 	+	path = nested
 	+	url = ../sm2
-	Submodule nested 0000000...b55928c (new submodule)
+	Submodule nested 0000000...$head11 (new submodule)
 	diff --git a/sm2/nested/file b/sm2/nested/file
 	new file mode 100644
 	index 0000000..ca281f5
@@ -813,7 +827,7 @@ test_expect_success 'diff --submodule=diff recurses into nested submodules' '
 	EOF
 	git diff --submodule=diff >actual 2>err &&
 	test_must_be_empty err &&
-	test_cmp expected actual
+	diff_cmp expected actual
 '
 
 test_done
diff --git a/third_party/git/t/t4061-diff-indent.sh b/third_party/git/t/t4061-diff-indent.sh
index 2affd7a100..0f7a6d97a8 100755
--- a/third_party/git/t/t4061-diff-indent.sh
+++ b/third_party/git/t/t4061-diff-indent.sh
@@ -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
 }
diff --git a/third_party/git/t/t4064-diff-oidfind.sh b/third_party/git/t/t4064-diff-oidfind.sh
index 3bdf317af8..6d8c8986fc 100755
--- a/third_party/git/t/t4064-diff-oidfind.sh
+++ b/third_party/git/t/t4064-diff-oidfind.sh
@@ -65,4 +65,59 @@ test_expect_success 'find a submodule' '
 	test_cmp expect actual
 '
 
+test_expect_success 'set up merge tests' '
+	test_commit base &&
+
+	git checkout -b boring base^ &&
+	echo boring >file &&
+	git add file &&
+	git commit -m boring &&
+
+	git checkout -b interesting base^ &&
+	echo interesting >file &&
+	git add file &&
+	git commit -m interesting &&
+
+	blob=$(git rev-parse interesting:file)
+'
+
+test_expect_success 'detect merge which introduces blob' '
+	git checkout -B merge base &&
+	git merge --no-commit boring &&
+	echo interesting >file &&
+	git commit -am "introduce blob" &&
+	git diff-tree --format=%s --find-object=$blob -c --name-status HEAD >actual &&
+	cat >expect <<-\EOF &&
+	introduce blob
+
+	AM	file
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'detect merge which removes blob' '
+	git checkout -B merge interesting &&
+	git merge --no-commit base &&
+	echo boring >file &&
+	git commit -am "remove blob" &&
+	git diff-tree --format=%s --find-object=$blob -c --name-status HEAD >actual &&
+	cat >expect <<-\EOF &&
+	remove blob
+
+	MA	file
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'do not detect merge that does not touch blob' '
+	git checkout -B merge interesting &&
+	git merge -m "untouched blob" base &&
+	git diff-tree --format=%s --find-object=$blob -c --name-status HEAD >actual &&
+	cat >expect <<-\EOF &&
+	untouched blob
+
+	EOF
+	test_cmp expect actual
+'
+
 test_done
diff --git a/third_party/git/t/t4066-diff-emit-delay.sh b/third_party/git/t/t4066-diff-emit-delay.sh
index 5df6b5e64e..6331f63b12 100755
--- a/third_party/git/t/t4066-diff-emit-delay.sh
+++ b/third_party/git/t/t4066-diff-emit-delay.sh
@@ -18,7 +18,7 @@ test_expect_success 'set up history with a merge' '
 '
 
 test_expect_success 'log --cc -p --stat --color-moved' '
-	cat >expect <<-\EOF &&
+	cat >expect <<-EOF &&
 	commit D
 	---
 	 D.t | 1 +
@@ -26,7 +26,7 @@ test_expect_success 'log --cc -p --stat --color-moved' '
 
 	diff --git a/D.t b/D.t
 	new file mode 100644
-	index 0000000..1784810
+	index 0000000..$(git rev-parse --short D:D.t)
 	--- /dev/null
 	+++ b/D.t
 	@@ -0,0 +1 @@
@@ -42,7 +42,7 @@ test_expect_success 'log --cc -p --stat --color-moved' '
 
 	diff --git a/C.t b/C.t
 	new file mode 100644
-	index 0000000..3cc58df
+	index 0000000..$(git rev-parse --short C:C.t)
 	--- /dev/null
 	+++ b/C.t
 	@@ -0,0 +1 @@
@@ -54,7 +54,7 @@ test_expect_success 'log --cc -p --stat --color-moved' '
 
 	diff --git a/B.t b/B.t
 	new file mode 100644
-	index 0000000..223b783
+	index 0000000..$(git rev-parse --short B:B.t)
 	--- /dev/null
 	+++ b/B.t
 	@@ -0,0 +1 @@
@@ -66,7 +66,7 @@ test_expect_success 'log --cc -p --stat --color-moved' '
 
 	diff --git a/A.t b/A.t
 	new file mode 100644
-	index 0000000..f70f10e
+	index 0000000..$(git rev-parse --short A:A.t)
 	--- /dev/null
 	+++ b/A.t
 	@@ -0,0 +1 @@
diff --git a/third_party/git/t/t4067-diff-partial-clone.sh b/third_party/git/t/t4067-diff-partial-clone.sh
index 90c8fb2901..804f2a82e8 100755
--- a/third_party/git/t/t4067-diff-partial-clone.sh
+++ b/third_party/git/t/t4067-diff-partial-clone.sh
@@ -20,7 +20,7 @@ test_expect_success 'git show batches blobs' '
 	# 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 show HEAD &&
-	grep "git> done" trace >done_lines &&
+	grep "fetch> done" trace >done_lines &&
 	test_line_count = 1 done_lines
 '
 
@@ -44,7 +44,7 @@ test_expect_success 'diff batches blobs' '
 	# 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 HEAD^ HEAD &&
-	grep "git> done" trace >done_lines &&
+	grep "fetch> done" trace >done_lines &&
 	test_line_count = 1 done_lines
 '
 
@@ -75,6 +75,37 @@ test_expect_success 'diff skips same-OID blobs' '
 	! grep "want $(cat hash-b)" trace
 '
 
+test_expect_success 'when fetching missing objects, diff skips GITLINKs' '
+	test_when_finished "rm -rf sub server client trace" &&
+
+	test_create_repo sub &&
+	test_commit -C sub first &&
+
+	test_create_repo server &&
+	echo a >server/a &&
+	git -C server add a &&
+	git -C server submodule add "file://$(pwd)/sub" &&
+	git -C server commit -m x &&
+
+	test_commit -C server/sub second &&
+	echo another-a >server/a &&
+	git -C server add a sub &&
+	git -C server commit -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 &&
+
+	echo a | git hash-object --stdin >hash-old-a &&
+	echo another-a | git hash-object --stdin >hash-new-a &&
+
+	# Ensure that a and another-a are fetched, and check (by successful
+	# execution of the diff) that no invalid OIDs are sent.
+	GIT_TRACE_PACKET="$(pwd)/trace" git -C client diff HEAD^ HEAD &&
+	grep "want $(cat hash-old-a)" trace &&
+	grep "want $(cat hash-new-a)" trace
+'
+
 test_expect_success 'diff with rename detection batches blobs' '
 	test_when_finished "rm -rf server client trace" &&
 
@@ -94,9 +125,57 @@ test_expect_success 'diff with rename detection batches blobs' '
 
 	# 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 -M HEAD^ HEAD >out &&
-	grep "similarity index" out &&
-	grep "git> done" trace >done_lines &&
+	GIT_TRACE_PACKET="$(pwd)/trace" git -C client diff --raw -M HEAD^ HEAD >out &&
+	grep ":100644 100644.*R[0-9][0-9][0-9].*b.*c" out &&
+	grep "fetch> done" trace >done_lines &&
+	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 "fetch> done" trace >done_lines &&
 	test_line_count = 1 done_lines
 '
 
diff --git a/third_party/git/t/t4068-diff-symmetric.sh b/third_party/git/t/t4068-diff-symmetric.sh
new file mode 100755
index 0000000000..31d17a5af0
--- /dev/null
+++ b/third_party/git/t/t4068-diff-symmetric.sh
@@ -0,0 +1,91 @@
+#!/bin/sh
+
+test_description='behavior of diff with symmetric-diff setups'
+
+. ./test-lib.sh
+
+# build these situations:
+#  - normal merge with one merge base (br1...b2r);
+#  - criss-cross merge ie 2 merge bases (br1...master);
+#  - disjoint subgraph (orphan branch, br3...master).
+#
+#     B---E   <-- master
+#    / \ /
+#   A   X
+#    \ / \
+#     C---D--G   <-- br1
+#      \    /
+#       ---F   <-- br2
+#
+#  H  <-- br3
+#
+# We put files into a few commits so that we can verify the
+# output as well.
+
+test_expect_success setup '
+	git commit --allow-empty -m A &&
+	echo b >b &&
+	git add b &&
+	git commit -m B &&
+	git checkout -b br1 HEAD^ &&
+	echo c >c &&
+	git add c &&
+	git commit -m C &&
+	git tag commit-C &&
+	git merge -m D master &&
+	git tag commit-D &&
+	git checkout master &&
+	git merge -m E commit-C &&
+	git checkout -b br2 commit-C &&
+	echo f >f &&
+	git add f &&
+	git commit -m F &&
+	git checkout br1 &&
+	git merge -m G br2 &&
+	git checkout --orphan br3 &&
+	git commit -m H
+'
+
+test_expect_success 'diff with one merge base' '
+	git diff commit-D...br1 >tmp &&
+	tail -n 1 tmp >actual &&
+	echo +f >expect &&
+	test_cmp expect actual
+'
+
+# The output (in tmp) can have +b or +c depending
+# on which merge base (commit B or C) is picked.
+# It should have one of those two, which comes out
+# to seven lines.
+test_expect_success 'diff with two merge bases' '
+	git diff br1...master >tmp 2>err &&
+	test_line_count = 7 tmp &&
+	test_line_count = 1 err
+'
+
+test_expect_success 'diff with no merge bases' '
+	test_must_fail git diff br2...br3 >tmp 2>err &&
+	test_i18ngrep "fatal: br2...br3: no merge base" err
+'
+
+test_expect_success 'diff with too many symmetric differences' '
+	test_must_fail git diff br1...master br2...br3 >tmp 2>err &&
+	test_i18ngrep "usage" err
+'
+
+test_expect_success 'diff with symmetric difference and extraneous arg' '
+	test_must_fail git diff master br1...master >tmp 2>err &&
+	test_i18ngrep "usage" err
+'
+
+test_expect_success 'diff with two ranges' '
+	test_must_fail git diff master br1..master br2..br3 >tmp 2>err &&
+	test_i18ngrep "usage" err
+'
+
+test_expect_success 'diff with ranges and extra arg' '
+	test_must_fail git diff master br1..master commit-D >tmp 2>err &&
+	test_i18ngrep "usage" err
+'
+
+test_done
diff --git a/third_party/git/t/t4100/t-apply-1.patch b/third_party/git/t/t4100/t-apply-1.patch
index 90ab54f0f5..43394f8285 100644
--- a/third_party/git/t/t4100/t-apply-1.patch
+++ b/third_party/git/t/t4100/t-apply-1.patch
@@ -75,8 +75,8 @@ diff --git a/Documentation/git.txt b/Documentation/git.txt
 +link:git-ssh-pull.html[git-ssh-pull]::
  	Pulls from a remote repository over ssh connection
  
- Interogators:
-@@ -156,8 +156,8 @@ Interogators:
+ Interrogators:
+@@ -156,8 +156,8 @@ Interrogators:
  link:git-diff-helper.html[git-diff-helper]::
  	Generates patch format output for git-diff-*
  
diff --git a/third_party/git/t/t4100/t-apply-3.patch b/third_party/git/t/t4100/t-apply-3.patch
index 90cdbaa5bb..cac172e779 100644
--- a/third_party/git/t/t4100/t-apply-3.patch
+++ b/third_party/git/t/t4100/t-apply-3.patch
@@ -211,7 +211,7 @@ dissimilarity index 82%
 -
 -		/* If this is an exact directory match, we may have
 -		 * directory files following this path. Match on them.
--		 * Otherwise, we're at a pach subcomponent, and we need
+-		 * Otherwise, we're at a patch subcomponent, and we need
 -		 * to try to match again.
 -		 */
 -		if (mtype == 0)
diff --git a/third_party/git/t/t4100/t-apply-5.patch b/third_party/git/t/t4100/t-apply-5.patch
index 5f6ddc1059..57ec79d887 100644
--- a/third_party/git/t/t4100/t-apply-5.patch
+++ b/third_party/git/t/t4100/t-apply-5.patch
@@ -185,8 +185,8 @@ diff a/Documentation/git.txt b/Documentation/git.txt
 +link:git-ssh-pull.html[git-ssh-pull]::
  	Pulls from a remote repository over ssh connection
  
- Interogators:
-@@ -156,8 +156,8 @@ Interogators:
+ Interrogators:
+@@ -156,8 +156,8 @@ Interrogators:
  link:git-diff-helper.html[git-diff-helper]::
  	Generates patch format output for git-diff-*
  
diff --git a/third_party/git/t/t4100/t-apply-7.patch b/third_party/git/t/t4100/t-apply-7.patch
index 07c6589e74..fa24305108 100644
--- a/third_party/git/t/t4100/t-apply-7.patch
+++ b/third_party/git/t/t4100/t-apply-7.patch
@@ -335,7 +335,7 @@ diff a/ls-tree.c b/ls-tree.c
  
 -		/* If this is an exact directory match, we may have
 -		 * directory files following this path. Match on them.
--		 * Otherwise, we're at a pach subcomponent, and we need
+-		 * Otherwise, we're at a patch subcomponent, and we need
 -		 * to try to match again.
 +	if (e->directory) {
 +		/* If this is a directory, we have the following cases:
diff --git a/third_party/git/t/t4104-apply-boundary.sh b/third_party/git/t/t4104-apply-boundary.sh
index 32e3b0ee0b..71ef4132d1 100755
--- a/third_party/git/t/t4104-apply-boundary.sh
+++ b/third_party/git/t/t4104-apply-boundary.sh
@@ -3,80 +3,55 @@
 # Copyright (c) 2005 Junio C Hamano
 #
 
-test_description='git apply boundary tests
+test_description='git apply boundary tests'
 
-'
 . ./test-lib.sh
 
 L="c d e f g h i j k l m n o p q r s t u v w x"
 
 test_expect_success setup '
-	for i in b '"$L"' y
-	do
-		echo $i
-	done >victim &&
+	test_write_lines b $L y >victim &&
 	cat victim >original &&
 	git update-index --add victim &&
 
 	# add to the head
-	for i in a b '"$L"' y
-	do
-		echo $i
-	done >victim &&
+	test_write_lines a b $L y >victim &&
 	cat victim >add-a-expect &&
 	git diff victim >add-a-patch.with &&
 	git diff --unified=0 >add-a-patch.without &&
 
 	# insert at line two
-	for i in b a '"$L"' y
-	do
-		echo $i
-	done >victim &&
+	test_write_lines b a $L y >victim &&
 	cat victim >insert-a-expect &&
 	git diff victim >insert-a-patch.with &&
 	git diff --unified=0 >insert-a-patch.without &&
 
 	# modify at the head
-	for i in a '"$L"' y
-	do
-		echo $i
-	done >victim &&
+	test_write_lines a $L y >victim &&
 	cat victim >mod-a-expect &&
 	git diff victim >mod-a-patch.with &&
 	git diff --unified=0 >mod-a-patch.without &&
 
 	# remove from the head
-	for i in '"$L"' y
-	do
-		echo $i
-	done >victim &&
+	test_write_lines $L y >victim &&
 	cat victim >del-a-expect &&
 	git diff victim >del-a-patch.with &&
 	git diff --unified=0 >del-a-patch.without &&
 
 	# add to the tail
-	for i in b '"$L"' y z
-	do
-		echo $i
-	done >victim &&
+	test_write_lines b $L y z >victim &&
 	cat victim >add-z-expect &&
 	git diff victim >add-z-patch.with &&
 	git diff --unified=0 >add-z-patch.without &&
 
 	# modify at the tail
-	for i in b '"$L"' z
-	do
-		echo $i
-	done >victim &&
+	test_write_lines b $L z >victim &&
 	cat victim >mod-z-expect &&
 	git diff victim >mod-z-patch.with &&
 	git diff --unified=0 >mod-z-patch.without &&
 
 	# remove from the tail
-	for i in b '"$L"'
-	do
-		echo $i
-	done >victim &&
+	test_write_lines b $L >victim &&
 	cat victim >del-z-expect &&
 	git diff victim >del-z-patch.with &&
 	git diff --unified=0 >del-z-patch.without
@@ -88,15 +63,15 @@ for with in with without
 do
 	case "$with" in
 	with) u= ;;
-	without) u='--unidiff-zero ' ;;
+	without) u=--unidiff-zero ;;
 	esac
 	for kind in add-a add-z insert-a mod-a mod-z del-a del-z
 	do
 		test_expect_success "apply $kind-patch $with context" '
 			cat original >victim &&
 			git update-index victim &&
-			git apply --index '"$u$kind-patch.$with"' &&
-			test_cmp '"$kind"'-expect victim
+			git apply --index $u "$kind-patch.$with" &&
+			test_cmp "$kind-expect" victim
 		'
 	done
 done
@@ -110,13 +85,12 @@ do
 	test_expect_success "apply non-git $kind-patch without context" '
 		cat original >victim &&
 		git update-index victim &&
-		git apply --unidiff-zero --index '"$kind-ng.without"' &&
-		test_cmp '"$kind"'-expect victim
+		git apply --unidiff-zero --index "$kind-ng.without" &&
+		test_cmp "$kind-expect" victim
 	'
 done
 
 test_expect_success 'two lines' '
-
 	>file &&
 	git add file &&
 	echo aaa >file &&
@@ -125,11 +99,10 @@ test_expect_success 'two lines' '
 	echo bbb >file &&
 	git add file &&
 	test_must_fail git apply --check patch
-
 '
 
 test_expect_success 'apply patch with 3 context lines matching at end' '
-	{ echo a; echo b; echo c; echo d; } >file &&
+	test_write_lines a b c d >file &&
 	git add file &&
 	echo e >>file &&
 	git diff >patch &&
diff --git a/third_party/git/t/t4108-apply-threeway.sh b/third_party/git/t/t4108-apply-threeway.sh
index fa5d4efb89..d7349ced6b 100755
--- a/third_party/git/t/t4108-apply-threeway.sh
+++ b/third_party/git/t/t4108-apply-threeway.sh
@@ -4,23 +4,17 @@ test_description='git apply --3way'
 
 . ./test-lib.sh
 
-create_file () {
-	for i
-	do
-		echo "$i"
-	done
-}
-
-sanitize_conflicted_diff () {
+print_sanitized_conflicted_diff () {
+	git diff HEAD >diff.raw &&
 	sed -e '
 		/^index /d
-		s/^\(+[<>][<>][<>][<>]*\) .*/\1/
-	'
+		s/^\(+[<>|][<>|][<>|][<>|]*\) .*/\1/
+	' diff.raw
 }
 
 test_expect_success setup '
 	test_tick &&
-	create_file >one 1 2 3 4 5 6 7 &&
+	test_write_lines 1 2 3 4 5 6 7 >one &&
 	cat one >two &&
 	git add one two &&
 	git commit -m initial &&
@@ -28,13 +22,13 @@ test_expect_success setup '
 	git branch side &&
 
 	test_tick &&
-	create_file >one 1 two 3 4 5 six 7 &&
-	create_file >two 1 two 3 4 5 6 7 &&
+	test_write_lines 1 two 3 4 5 six 7 >one &&
+	test_write_lines 1 two 3 4 5 6 7 >two &&
 	git commit -a -m master &&
 
 	git checkout side &&
-	create_file >one 1 2 3 4 five 6 7 &&
-	create_file >two 1 2 3 4 five 6 7 &&
+	test_write_lines 1 2 3 4 five 6 7 >one &&
+	test_write_lines 1 2 3 4 five 6 7 >two &&
 	git commit -a -m side &&
 
 	git checkout master
@@ -52,7 +46,7 @@ test_expect_success 'apply without --3way' '
 	git diff-index --exit-code --cached HEAD
 '
 
-test_expect_success 'apply with --3way' '
+test_apply_with_3way () {
 	# Merging side should be similar to applying this patch
 	git diff ...side >P.diff &&
 
@@ -61,22 +55,31 @@ test_expect_success 'apply with --3way' '
 	git checkout master^0 &&
 	test_must_fail git merge --no-commit side &&
 	git ls-files -s >expect.ls &&
-	git diff HEAD | sanitize_conflicted_diff >expect.diff &&
+	print_sanitized_conflicted_diff >expect.diff &&
 
 	# should fail to apply
 	git reset --hard &&
 	git checkout master^0 &&
 	test_must_fail git apply --index --3way P.diff &&
 	git ls-files -s >actual.ls &&
-	git diff HEAD | sanitize_conflicted_diff >actual.diff &&
+	print_sanitized_conflicted_diff >actual.diff &&
 
 	# The result should resemble the corresponding merge
 	test_cmp expect.ls actual.ls &&
 	test_cmp expect.diff actual.diff
+}
+
+test_expect_success 'apply with --3way' '
+	test_apply_with_3way
+'
+
+test_expect_success 'apply with --3way with merge.conflictStyle = diff3' '
+	test_config merge.conflictStyle diff3 &&
+	test_apply_with_3way
 '
 
 test_expect_success 'apply with --3way with rerere enabled' '
-	git config rerere.enabled true &&
+	test_config rerere.enabled true &&
 
 	# Merging side should be similar to applying this patch
 	git diff ...side >P.diff &&
@@ -87,7 +90,7 @@ test_expect_success 'apply with --3way with rerere enabled' '
 	test_must_fail git merge --no-commit side &&
 
 	# Manually resolve and record the resolution
-	create_file 1 two 3 4 five six 7 >one &&
+	test_write_lines 1 two 3 4 five six 7 >one &&
 	git rerere &&
 	cat one >expect &&
 
@@ -104,14 +107,14 @@ test_expect_success 'apply -3 with add/add conflict setup' '
 	git reset --hard &&
 
 	git checkout -b adder &&
-	create_file 1 2 3 4 5 6 7 >three &&
-	create_file 1 2 3 4 5 6 7 >four &&
+	test_write_lines 1 2 3 4 5 6 7 >three &&
+	test_write_lines 1 2 3 4 5 6 7 >four &&
 	git add three four &&
 	git commit -m "add three and four" &&
 
 	git checkout -b another adder^ &&
-	create_file 1 2 3 4 5 6 7 >three &&
-	create_file 1 2 3 four 5 6 7 >four &&
+	test_write_lines 1 2 3 4 5 6 7 >three &&
+	test_write_lines 1 2 3 four 5 6 7 >four &&
 	git add three four &&
 	git commit -m "add three and four" &&
 
@@ -121,7 +124,7 @@ test_expect_success 'apply -3 with add/add conflict setup' '
 	git checkout adder^0 &&
 	test_must_fail git merge --no-commit another &&
 	git ls-files -s >expect.ls &&
-	git diff HEAD | sanitize_conflicted_diff >expect.diff
+	print_sanitized_conflicted_diff >expect.diff
 '
 
 test_expect_success 'apply -3 with add/add conflict' '
@@ -131,7 +134,7 @@ test_expect_success 'apply -3 with add/add conflict' '
 	test_must_fail git apply --index --3way P.diff &&
 	# ... and leave conflicts in the index and in the working tree
 	git ls-files -s >actual.ls &&
-	git diff HEAD | sanitize_conflicted_diff >actual.diff &&
+	print_sanitized_conflicted_diff >actual.diff &&
 
 	# The result should resemble the corresponding merge
 	test_cmp expect.ls actual.ls &&
diff --git a/third_party/git/t/t4117-apply-reject.sh b/third_party/git/t/t4117-apply-reject.sh
index f7de6f077a..0ee93fe845 100755
--- a/third_party/git/t/t4117-apply-reject.sh
+++ b/third_party/git/t/t4117-apply-reject.sh
@@ -74,7 +74,7 @@ test_expect_success 'apply with --reject should fail but update the file' '
 	test_must_fail git apply --reject patch.1 &&
 	test_cmp expected file1 &&
 
-	cat file1.rej &&
+	test_path_is_file file1.rej &&
 	test_path_is_missing file2.rej
 '
 
@@ -87,7 +87,7 @@ test_expect_success 'apply with --reject should fail but update the file' '
 	test_path_is_missing file1 &&
 	test_cmp expected file2 &&
 
-	cat file2.rej &&
+	test_path_is_file file2.rej &&
 	test_path_is_missing file1.rej
 
 '
@@ -101,7 +101,7 @@ test_expect_success 'the same test with --verbose' '
 	test_path_is_missing file1 &&
 	test_cmp expected file2 &&
 
-	cat file2.rej &&
+	test_path_is_file file2.rej &&
 	test_path_is_missing file1.rej
 
 '
diff --git a/third_party/git/t/t4124-apply-ws-rule.sh b/third_party/git/t/t4124-apply-ws-rule.sh
index ff51e9e789..0ca29821ec 100755
--- a/third_party/git/t/t4124-apply-ws-rule.sh
+++ b/third_party/git/t/t4124-apply-ws-rule.sh
@@ -35,9 +35,15 @@ prepare_test_file () {
 }
 
 apply_patch () {
+	cmd_prefix= &&
+	if test "x$1" = 'x!'
+	then
+		cmd_prefix=test_must_fail &&
+		shift
+	fi &&
 	>target &&
 	sed -e "s|\([ab]\)/file|\1/target|" <patch |
-	git apply "$@"
+	$cmd_prefix git apply "$@"
 }
 
 test_fix () {
@@ -46,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)
@@ -99,7 +112,7 @@ test_expect_success 'whitespace=warn, default rule' '
 
 test_expect_success 'whitespace=error-all, default rule' '
 
-	test_must_fail apply_patch --whitespace=error-all &&
+	apply_patch ! --whitespace=error-all &&
 	test_must_be_empty target
 
 '
diff --git a/third_party/git/t/t4134-apply-submodule.sh b/third_party/git/t/t4134-apply-submodule.sh
index 0043930ca6..d1c16ba33c 100755
--- a/third_party/git/t/t4134-apply-submodule.sh
+++ b/third_party/git/t/t4134-apply-submodule.sh
@@ -15,7 +15,7 @@ index 0000000..0123456
 --- /dev/null
 +++ b/dir/sm
 @@ -0,0 +1 @@
-+Subproject commit 0123456789abcdef0123456789abcdef01234567
++Subproject commit $(test_oid numeric)
 EOF
 	cat > remove-sm.patch <<EOF
 diff --git a/dir/sm b/dir/sm
@@ -24,7 +24,7 @@ index 0123456..0000000
 --- a/dir/sm
 +++ /dev/null
 @@ -1 +0,0 @@
--Subproject commit 0123456789abcdef0123456789abcdef01234567
+-Subproject commit $(test_oid numeric)
 EOF
 '
 
diff --git a/third_party/git/t/t4137-apply-submodule.sh b/third_party/git/t/t4137-apply-submodule.sh
index a9bd40a6d0..07d5262537 100755
--- a/third_party/git/t/t4137-apply-submodule.sh
+++ b/third_party/git/t/t4137-apply-submodule.sh
@@ -6,15 +6,19 @@ test_description='git apply handling submodules'
 . "$TEST_DIRECTORY"/lib-submodule-update.sh
 
 apply_index () {
-	git diff --ignore-submodules=dirty "..$1" | git apply --index -
+	git diff --ignore-submodules=dirty "..$1" >diff &&
+	may_only_be_test_must_fail "$2" &&
+	$2 git apply --index diff
 }
 
-test_submodule_switch "apply_index"
+test_submodule_switch_func "apply_index"
 
 apply_3way () {
-	git diff --ignore-submodules=dirty "..$1" | git apply --3way -
+	git diff --ignore-submodules=dirty "..$1" >diff &&
+	may_only_be_test_must_fail "$2" &&
+	$2 git apply --3way diff
 }
 
-test_submodule_switch "apply_3way"
+test_submodule_switch_func "apply_3way"
 
 test_done
diff --git a/third_party/git/t/t4138-apply-ws-expansion.sh b/third_party/git/t/t4138-apply-ws-expansion.sh
index 3b636a63a3..b19faeb67a 100755
--- a/third_party/git/t/t4138-apply-ws-expansion.sh
+++ b/third_party/git/t/t4138-apply-ws-expansion.sh
@@ -17,8 +17,8 @@ test_expect_success setup '
 	printf "\t%s\n" 1 2 3 >after &&
 	printf "%64s\n" a b c >>after &&
 	printf "\t%s\n" 4 5 6 >>after &&
-	git diff --no-index before after |
-		sed -e "s/before/test-1/" -e "s/after/test-1/" >patch1.patch &&
+	test_expect_code 1 git diff --no-index before after >patch1.patch.raw &&
+	sed -e "s/before/test-1/" -e "s/after/test-1/" patch1.patch.raw >patch1.patch &&
 	printf "%64s\n" 1 2 3 4 5 6 >test-1 &&
 	printf "%64s\n" 1 2 3 a b c 4 5 6 >expect-1 &&
 
@@ -33,8 +33,8 @@ test_expect_success setup '
 		x=$(( $x + 1 ))
 	done &&
 	printf "\t%s\n" d e f >>after &&
-	git diff --no-index before after |
-		sed -e "s/before/test-2/" -e "s/after/test-2/" >patch2.patch &&
+	test_expect_code 1 git diff --no-index before after >patch2.patch.raw &&
+	sed -e "s/before/test-2/" -e "s/after/test-2/" patch2.patch.raw >patch2.patch &&
 	printf "%64s\n" a b c d e f >test-2 &&
 	printf "%64s\n" a b c >expect-2 &&
 	x=1 &&
@@ -56,8 +56,8 @@ test_expect_success setup '
 		x=$(( $x + 1 ))
 	done &&
 	printf "\t%s\n" d e f >>after &&
-	git diff --no-index before after |
-	sed -e "s/before/test-3/" -e "s/after/test-3/" >patch3.patch &&
+	test_expect_code 1 git diff --no-index before after >patch3.patch.raw &&
+	sed -e "s/before/test-3/" -e "s/after/test-3/" patch3.patch.raw >patch3.patch &&
 	printf "%64s\n" a b c d e f >test-3 &&
 	printf "%64s\n" a b c >expect-3 &&
 	x=0 &&
@@ -84,8 +84,8 @@ test_expect_success setup '
 		printf "\t%02d\n" $x >>after
 		x=$(( $x + 1 ))
 	done &&
-	git diff --no-index before after |
-	sed -e "s/before/test-4/" -e "s/after/test-4/" >patch4.patch &&
+	test_expect_code 1 git diff --no-index before after >patch4.patch.raw &&
+	sed -e "s/before/test-4/" -e "s/after/test-4/" patch4.patch.raw >patch4.patch &&
 	>test-4 &&
 	x=0 &&
 	while test $x -lt 50
diff --git a/third_party/git/t/t4140-apply-ita.sh b/third_party/git/t/t4140-apply-ita.sh
new file mode 100755
index 0000000000..c614eaf04c
--- /dev/null
+++ b/third_party/git/t/t4140-apply-ita.sh
@@ -0,0 +1,56 @@
+#!/bin/sh
+
+test_description='git apply of i-t-a file'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+	test_write_lines 1 2 3 4 5 >blueprint &&
+
+	cat blueprint >test-file &&
+	git add -N test-file &&
+	git diff >creation-patch &&
+	grep "new file mode 100644" creation-patch &&
+
+	rm -f test-file &&
+	git diff >deletion-patch &&
+	grep "deleted file mode 100644" deletion-patch
+'
+
+test_expect_success 'apply creation patch to ita path (--cached)' '
+	git rm -f test-file &&
+	cat blueprint >test-file &&
+	git add -N test-file &&
+
+	git apply --cached creation-patch &&
+	git cat-file blob :test-file >actual &&
+	test_cmp blueprint actual
+'
+
+test_expect_success 'apply creation patch to ita path (--index)' '
+	git rm -f test-file &&
+	cat blueprint >test-file &&
+	git add -N test-file &&
+	rm -f test-file &&
+
+	test_must_fail git apply --index creation-patch
+'
+
+test_expect_success 'apply deletion patch to ita path (--cached)' '
+	git rm -f test-file &&
+	cat blueprint >test-file &&
+	git add -N test-file &&
+
+	git apply --cached deletion-patch &&
+	test_must_fail git ls-files --stage --error-unmatch test-file
+'
+
+test_expect_success 'apply deletion patch to ita path (--index)' '
+	cat blueprint >test-file &&
+	git add -N test-file &&
+
+	test_must_fail git apply --index deletion-patch &&
+	git ls-files --stage --error-unmatch test-file
+'
+
+test_done
diff --git a/third_party/git/t/t4150-am.sh b/third_party/git/t/t4150-am.sh
index 3f7f750cc8..3ecbef6f8e 100755
--- a/third_party/git/t/t4150-am.sh
+++ b/third_party/git/t/t4150-am.sh
@@ -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 &&
@@ -465,7 +465,7 @@ test_expect_success 'am changes committer and keeps author' '
 test_expect_success 'am --signoff adds Signed-off-by: line' '
 	rm -fr .git/rebase-apply &&
 	git reset --hard &&
-	git checkout -b master2 first &&
+	git checkout -b topic_2 first &&
 	git am --signoff <patch2 &&
 	{
 		printf "third\n\nSigned-off-by: %s <%s>\n\n" \
@@ -479,7 +479,7 @@ test_expect_success 'am --signoff adds Signed-off-by: line' '
 '
 
 test_expect_success 'am stays in branch' '
-	echo refs/heads/master2 >expected &&
+	echo refs/heads/topic_2 >expected &&
 	git symbolic-ref HEAD >actual &&
 	test_cmp expected actual
 '
@@ -540,7 +540,7 @@ test_expect_success 'am without --keep removes Re: and [PATCH] stuff' '
 	git reset --hard HEAD^ &&
 	git am <patch4 &&
 	git rev-parse HEAD >expected &&
-	git rev-parse master2 >actual &&
+	git rev-parse topic_2 >actual &&
 	test_cmp expected actual
 '
 
@@ -567,7 +567,7 @@ test_expect_success 'am --keep-non-patch really keeps the non-patch part' '
 test_expect_success 'setup am -3' '
 	rm -fr .git/rebase-apply &&
 	git reset --hard &&
-	git checkout -b base3way master2 &&
+	git checkout -b base3way topic_2 &&
 	sed -n -e "3,\$p" msg >file &&
 	head -n 9 msg >>file &&
 	git add file &&
@@ -666,6 +666,26 @@ test_expect_success 'am --show-current-patch' '
 	test_cmp .git/rebase-apply/0001 actual.patch
 '
 
+test_expect_success 'am --show-current-patch=raw' '
+	git am --show-current-patch=raw >actual.patch &&
+	test_cmp .git/rebase-apply/0001 actual.patch
+'
+
+test_expect_success 'am --show-current-patch=diff' '
+	git am --show-current-patch=diff >actual.patch &&
+	test_cmp .git/rebase-apply/patch actual.patch
+'
+
+test_expect_success 'am accepts repeated --show-current-patch' '
+	git am --show-current-patch --show-current-patch=raw >actual.patch &&
+	test_cmp .git/rebase-apply/0001 actual.patch
+'
+
+test_expect_success 'am detects incompatible --show-current-patch' '
+	test_must_fail git am --show-current-patch=raw --show-current-patch=diff &&
+	test_must_fail git am --show-current-patch --show-current-patch=diff
+'
+
 test_expect_success 'am --skip works' '
 	echo goodbye >expected &&
 	git am --skip &&
@@ -969,7 +989,7 @@ test_expect_success 'am -s unexpected trailer block' '
 	Signed-off-by: J C H <j@c.h>
 	EOF
 	git commit -F msg &&
-	git cat-file commit HEAD | sed -e '1,/^$/d' >original &&
+	git cat-file commit HEAD | sed -e "1,/^$/d" >original &&
 	git format-patch --stdout -1 >patch &&
 
 	git reset --hard HEAD^ &&
@@ -978,7 +998,7 @@ test_expect_success 'am -s unexpected trailer block' '
 		cat original &&
 		echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>"
 	) >expect &&
-	git cat-file commit HEAD | sed -e '1,/^$/d' >actual &&
+	git cat-file commit HEAD | sed -e "1,/^$/d" >actual &&
 	test_cmp expect actual &&
 
 	cat >msg <<-\EOF &&
@@ -989,7 +1009,7 @@ test_expect_success 'am -s unexpected trailer block' '
 	EOF
 	git reset HEAD^ &&
 	git commit -F msg file &&
-	git cat-file commit HEAD | sed -e '1,/^$/d' >original &&
+	git cat-file commit HEAD | sed -e "1,/^$/d" >original &&
 	git format-patch --stdout -1 >patch &&
 
 	git reset --hard HEAD^ &&
@@ -1000,7 +1020,7 @@ test_expect_success 'am -s unexpected trailer block' '
 		echo &&
 		echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>"
 	) >expect &&
-	git cat-file commit HEAD | sed -e '1,/^$/d' >actual &&
+	git cat-file commit HEAD | sed -e "1,/^$/d" >actual &&
 	test_cmp expect actual
 '
 
@@ -1061,4 +1081,72 @@ test_expect_success 'am --quit keeps HEAD where it is' '
 	test_cmp expected actual
 '
 
+test_expect_success 'am and .gitattibutes' '
+	test_create_repo attributes &&
+	(
+		cd attributes &&
+		test_commit init &&
+		git config filter.test.clean "sed -e '\''s/smudged/clean/g'\''" &&
+		git config filter.test.smudge "sed -e '\''s/clean/smudged/g'\''" &&
+
+		test_commit second &&
+		git checkout -b test HEAD^ &&
+
+		echo "*.txt filter=test conflict-marker-size=10" >.gitattributes &&
+		git add .gitattributes &&
+		test_commit third &&
+
+		echo "This text is smudged." >a.txt &&
+		git add a.txt &&
+		test_commit fourth &&
+
+		git checkout -b removal HEAD^ &&
+		git rm .gitattributes &&
+		git add -u &&
+		test_commit fifth &&
+		git cherry-pick test &&
+
+		git checkout -b conflict third &&
+		echo "This text is different." >a.txt &&
+		git add a.txt &&
+		test_commit sixth &&
+
+		git checkout test &&
+		git format-patch --stdout master..HEAD >patches &&
+		git reset --hard master &&
+		git am patches &&
+		grep "smudged" a.txt &&
+
+		git checkout removal &&
+		git reset --hard &&
+		git format-patch --stdout master..HEAD >patches &&
+		git reset --hard master &&
+		git am patches &&
+		grep "clean" a.txt &&
+
+		git checkout conflict &&
+		git reset --hard &&
+		git format-patch --stdout master..HEAD >patches &&
+		git reset --hard fourth &&
+		test_must_fail git am -3 patches &&
+		grep "<<<<<<<<<<" a.txt
+	)
+'
+
+test_expect_success 'apply binary blob in partial clone' '
+	printf "\\000" >binary &&
+	git add binary &&
+	git commit -m "binary blob" &&
+	git format-patch --stdout -m HEAD^ >patch &&
+
+	test_create_repo server &&
+	test_config -C server uploadpack.allowfilter 1 &&
+	test_config -C server uploadpack.allowanysha1inwant 1 &&
+	git clone --filter=blob:none "file://$(pwd)/server" client &&
+	test_when_finished "rm -rf client" &&
+
+	# Exercise to make sure that it works
+	git -C client am ../patch
+'
+
 test_done
diff --git a/third_party/git/t/t4200-rerere.sh b/third_party/git/t/t4200-rerere.sh
index 55b7750ade..b12b43e9e9 100755
--- a/third_party/git/t/t4200-rerere.sh
+++ b/third_party/git/t/t4200-rerere.sh
@@ -210,7 +210,7 @@ test_expect_success 'set up for garbage collection tests' '
 	echo Hello >$rr/preimage &&
 	echo World >$rr/postimage &&
 
-	sha2=4000000000000000000000000000000000000000 &&
+	sha2=$(test_oid deadbeef) &&
 	rr2=.git/rr-cache/$sha2 &&
 	mkdir $rr2 &&
 	echo Hello >$rr2/preimage &&
@@ -363,7 +363,7 @@ test_expect_success 'set up an unresolved merge' '
 	git reset --hard &&
 	git checkout version2 &&
 	fifth=$(git rev-parse fifth) &&
-	echo "$fifth		branch 'fifth' of ." |
+	echo "$fifth		branch fifth of ." |
 	git fmt-merge-msg >msg &&
 	ancestor=$(git merge-base version2 fifth) &&
 	test_must_fail git merge-recursive "$ancestor" -- HEAD fifth &&
diff --git a/third_party/git/t/t4201-shortlog.sh b/third_party/git/t/t4201-shortlog.sh
index d3a7ce6bbb..3d5c4a2086 100755
--- a/third_party/git/t/t4201-shortlog.sh
+++ b/third_party/git/t/t4201-shortlog.sh
@@ -215,4 +215,145 @@ test_expect_success 'shortlog --committer (external)' '
 	test_cmp expect actual
 '
 
+test_expect_success '--group=committer is the same as --committer' '
+	git shortlog -ns --group=committer HEAD >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'shortlog --group=trailer:signed-off-by' '
+	git commit --allow-empty -m foo -s &&
+	GIT_COMMITTER_NAME="SOB One" \
+	GIT_COMMITTER_EMAIL=sob@example.com \
+		git commit --allow-empty -m foo -s &&
+	git commit --allow-empty --amend --no-edit -s &&
+	cat >expect <<-\EOF &&
+	     2	C O Mitter <committer@example.com>
+	     1	SOB One <sob@example.com>
+	EOF
+	git shortlog -nse --group=trailer:signed-off-by HEAD >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'trailer idents are split' '
+	cat >expect <<-\EOF &&
+	     2	C O Mitter
+	     1	SOB One
+	EOF
+	git shortlog -ns --group=trailer:signed-off-by HEAD >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'trailer idents are mailmapped' '
+	cat >expect <<-\EOF &&
+	     2	C O Mitter
+	     1	Another Name
+	EOF
+	echo "Another Name <sob@example.com>" >mail.map &&
+	git -c mailmap.file=mail.map shortlog -ns \
+		--group=trailer:signed-off-by HEAD >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'shortlog de-duplicates trailers in a single commit' '
+	git commit --allow-empty -F - <<-\EOF &&
+	subject one
+
+	this message has two distinct values, plus a repeat
+
+	Repeated-trailer: Foo
+	Repeated-trailer: Bar
+	Repeated-trailer: Foo
+	EOF
+
+	git commit --allow-empty -F - <<-\EOF &&
+	subject two
+
+	similar to the previous, but without the second distinct value
+
+	Repeated-trailer: Foo
+	Repeated-trailer: Foo
+	EOF
+
+	cat >expect <<-\EOF &&
+	     2	Foo
+	     1	Bar
+	EOF
+	git shortlog -ns --group=trailer:repeated-trailer -2 HEAD >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'shortlog can match multiple groups' '
+	git commit --allow-empty -F - <<-\EOF &&
+	subject one
+
+	this has two trailers that are distinct from the author; it will count
+	3 times in the output
+
+	Some-trailer: User A <a@example.com>
+	Another-trailer: User B <b@example.com>
+	EOF
+
+	git commit --allow-empty -F - <<-\EOF &&
+	subject two
+
+	this one has two trailers, one of which is a duplicate with the author;
+	it will only be counted once for them
+
+	Another-trailer: A U Thor <author@example.com>
+	Some-trailer: User B <b@example.com>
+	EOF
+
+	cat >expect <<-\EOF &&
+	     2	A U Thor
+	     2	User B
+	     1	User A
+	EOF
+	git shortlog -ns \
+		--group=author \
+		--group=trailer:some-trailer \
+		--group=trailer:another-trailer \
+		-2 HEAD >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'set up option selection tests' '
+	git commit --allow-empty -F - <<-\EOF
+	subject
+
+	body
+
+	Trailer-one: value-one
+	Trailer-two: value-two
+	EOF
+'
+
+test_expect_success '--no-group resets group list to author' '
+	cat >expect <<-\EOF &&
+	     1	A U Thor
+	EOF
+	git shortlog -ns \
+		--group=committer \
+		--group=trailer:trailer-one \
+		--no-group \
+		-1 HEAD >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success '--no-group resets trailer list' '
+	cat >expect <<-\EOF &&
+	     1	value-two
+	EOF
+	git shortlog -ns \
+		--group=trailer:trailer-one \
+		--no-group \
+		--group=trailer:trailer-two \
+		-1 HEAD >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'stdin with multiple groups reports error' '
+	git log >log &&
+	test_must_fail git shortlog --group=author --group=committer <log
+'
+
 test_done
diff --git a/third_party/git/t/t4202-log.sh b/third_party/git/t/t4202-log.sh
index c20209324c..56d34ed465 100755
--- a/third_party/git/t/t4202-log.sh
+++ b/third_party/git/t/t4202-log.sh
@@ -5,6 +5,11 @@ test_description='git log'
 . ./test-lib.sh
 . "$TEST_DIRECTORY/lib-gpg.sh"
 . "$TEST_DIRECTORY/lib-terminal.sh"
+. "$TEST_DIRECTORY/lib-log-graph.sh"
+
+test_cmp_graph () {
+	lib_test_cmp_graph --format=%s "$@"
+}
 
 test_expect_success setup '
 
@@ -87,12 +92,12 @@ test_expect_success 'format %w(,1,2)' '
 '
 
 cat > expect << EOF
-804a787 sixth
-394ef78 fifth
-5d31159 fourth
-2fbe8c0 third
-f7dab8e second
-3a2fdcb initial
+$(git rev-parse --short :/sixth  ) sixth
+$(git rev-parse --short :/fifth  ) fifth
+$(git rev-parse --short :/fourth ) fourth
+$(git rev-parse --short :/third  ) third
+$(git rev-parse --short :/second ) second
+$(git rev-parse --short :/initial) initial
 EOF
 test_expect_success 'oneline' '
 
@@ -173,43 +178,45 @@ test_expect_success 'git config log.follow is overridden by --no-follow' '
 	verbose test "$actual" = "$expect"
 '
 
+# Note that these commits are intentionally listed out of order.
+last_three="$(git rev-parse :/fourth :/sixth :/fifth)"
 cat > expect << EOF
-804a787 sixth
-394ef78 fifth
-5d31159 fourth
+$(git rev-parse --short :/sixth ) sixth
+$(git rev-parse --short :/fifth ) fifth
+$(git rev-parse --short :/fourth) fourth
 EOF
 test_expect_success 'git log --no-walk <commits> sorts by commit time' '
-	git log --no-walk --oneline 5d31159 804a787 394ef78 > actual &&
+	git log --no-walk --oneline $last_three > actual &&
 	test_cmp expect actual
 '
 
 test_expect_success 'git log --no-walk=sorted <commits> sorts by commit time' '
-	git log --no-walk=sorted --oneline 5d31159 804a787 394ef78 > actual &&
+	git log --no-walk=sorted --oneline $last_three > actual &&
 	test_cmp expect actual
 '
 
 cat > expect << EOF
-=== 804a787 sixth
-=== 394ef78 fifth
-=== 5d31159 fourth
+=== $(git rev-parse --short :/sixth ) sixth
+=== $(git rev-parse --short :/fifth ) fifth
+=== $(git rev-parse --short :/fourth) fourth
 EOF
 test_expect_success 'git log --line-prefix="=== " --no-walk <commits> sorts by commit time' '
-	git log --line-prefix="=== " --no-walk --oneline 5d31159 804a787 394ef78 > actual &&
+	git log --line-prefix="=== " --no-walk --oneline $last_three > actual &&
 	test_cmp expect actual
 '
 
 cat > expect << EOF
-5d31159 fourth
-804a787 sixth
-394ef78 fifth
+$(git rev-parse --short :/fourth) fourth
+$(git rev-parse --short :/sixth ) sixth
+$(git rev-parse --short :/fifth ) fifth
 EOF
 test_expect_success 'git log --no-walk=unsorted <commits> leaves list of commits as given' '
-	git log --no-walk=unsorted --oneline 5d31159 804a787 394ef78 > actual &&
+	git log --no-walk=unsorted --oneline $last_three > actual &&
 	test_cmp expect actual
 '
 
 test_expect_success 'git show <commits> leaves list of commits as given' '
-	git show --oneline -s 5d31159 804a787 394ef78 > actual &&
+	git show --oneline -s $last_three > actual &&
 	test_cmp expect actual
 '
 
@@ -450,8 +457,7 @@ cat > expect <<EOF
 EOF
 
 test_expect_success 'simple log --graph' '
-	git log --graph --pretty=tformat:%s >actual &&
-	test_cmp expect actual
+	test_cmp_graph
 '
 
 cat > expect <<EOF
@@ -465,8 +471,7 @@ cat > expect <<EOF
 EOF
 
 test_expect_success 'simple log --graph --line-prefix="123 "' '
-	git log --graph --line-prefix="123 " --pretty=tformat:%s >actual &&
-	test_cmp expect actual
+	test_cmp_graph --line-prefix="123 "
 '
 
 test_expect_success 'set up merge history' '
@@ -493,9 +498,7 @@ cat > expect <<\EOF
 EOF
 
 test_expect_success 'log --graph with merge' '
-	git log --graph --date-order --pretty=tformat:%s |
-		sed "s/ *\$//" >actual &&
-	test_cmp expect actual
+	test_cmp_graph --date-order
 '
 
 cat > expect <<\EOF
@@ -514,9 +517,7 @@ cat > expect <<\EOF
 EOF
 
 test_expect_success 'log --graph --line-prefix="| | | " with merge' '
-	git log --line-prefix="| | | " --graph --date-order --pretty=tformat:%s |
-		sed "s/ *\$//" >actual &&
-	test_cmp expect actual
+	test_cmp_graph --line-prefix="| | | " --date-order
 '
 
 cat > expect.colors <<\EOF
@@ -536,9 +537,7 @@ EOF
 
 test_expect_success 'log --graph with merge with log.graphColors' '
 	test_config log.graphColors " blue,invalid-color, cyan, red  , " &&
-	git log --color=always --graph --date-order --pretty=tformat:%s |
-		test_decode_color | sed "s/ *\$//" >actual &&
-	test_cmp expect.colors actual
+	lib_test_cmp_colored_graph --date-order --format=%s
 '
 
 test_expect_success 'log --raw --graph -m with merge' '
@@ -667,16 +666,14 @@ cat > expect <<\EOF
 * | | fifth
 * | | fourth
 |/ /
-* | third
+* / third
 |/
 * second
 * initial
 EOF
 
 test_expect_success 'log --graph with merge' '
-	git log --graph --date-order --pretty=tformat:%s |
-		sed "s/ *\$//" >actual &&
-	test_cmp expect actual
+	test_cmp_graph --date-order
 '
 
 test_expect_success 'log.decorate configuration' '
@@ -745,7 +742,23 @@ test_expect_success 'decorate-refs with glob' '
 	octopus-a (octopus-a)
 	reach
 	EOF
+	cat >expect.no-decorate <<-\EOF &&
+	Merge-tag-reach
+	Merge-tags-octopus-a-and-octopus-b
+	seventh
+	octopus-b
+	octopus-a
+	reach
+	EOF
+	git log -n6 --decorate=short --pretty="tformat:%f%d" \
+		--decorate-refs="heads/octopus*" >actual &&
+	test_cmp expect.decorate actual &&
 	git log -n6 --decorate=short --pretty="tformat:%f%d" \
+		--decorate-refs-exclude="heads/octopus*" \
+		--decorate-refs="heads/octopus*" >actual &&
+	test_cmp expect.no-decorate actual &&
+	git -c log.excludeDecoration="heads/octopus*" log \
+		-n6 --decorate=short --pretty="tformat:%f%d" \
 		--decorate-refs="heads/octopus*" >actual &&
 	test_cmp expect.decorate actual
 '
@@ -790,6 +803,9 @@ test_expect_success 'decorate-refs-exclude with glob' '
 	EOF
 	git log -n6 --decorate=short --pretty="tformat:%f%d" \
 		--decorate-refs-exclude="heads/octopus*" >actual &&
+	test_cmp expect.decorate actual &&
+	git -c log.excludeDecoration="heads/octopus*" log \
+		-n6 --decorate=short --pretty="tformat:%f%d" >actual &&
 	test_cmp expect.decorate actual
 '
 
@@ -804,6 +820,9 @@ test_expect_success 'decorate-refs-exclude without globs' '
 	EOF
 	git log -n6 --decorate=short --pretty="tformat:%f%d" \
 		--decorate-refs-exclude="tags/reach" >actual &&
+	test_cmp expect.decorate actual &&
+	git -c log.excludeDecoration="tags/reach" log \
+		-n6 --decorate=short --pretty="tformat:%f%d" >actual &&
 	test_cmp expect.decorate actual
 '
 
@@ -819,11 +838,19 @@ test_expect_success 'multiple decorate-refs-exclude' '
 	git log -n6 --decorate=short --pretty="tformat:%f%d" \
 		--decorate-refs-exclude="heads/octopus*" \
 		--decorate-refs-exclude="tags/reach" >actual &&
+	test_cmp expect.decorate actual &&
+	git -c log.excludeDecoration="heads/octopus*" \
+		-c log.excludeDecoration="tags/reach" log \
+		-n6 --decorate=short --pretty="tformat:%f%d" >actual &&
+	test_cmp expect.decorate actual &&
+	git -c log.excludeDecoration="heads/octopus*" log \
+		--decorate-refs-exclude="tags/reach" \
+		-n6 --decorate=short --pretty="tformat:%f%d" >actual &&
 	test_cmp expect.decorate actual
 '
 
 test_expect_success 'decorate-refs and decorate-refs-exclude' '
-	cat >expect.decorate <<-\EOF &&
+	cat >expect.no-decorate <<-\EOF &&
 	Merge-tag-reach (master)
 	Merge-tags-octopus-a-and-octopus-b
 	seventh
@@ -834,6 +861,40 @@ test_expect_success 'decorate-refs and decorate-refs-exclude' '
 	git log -n6 --decorate=short --pretty="tformat:%f%d" \
 		--decorate-refs="heads/*" \
 		--decorate-refs-exclude="heads/oc*" >actual &&
+	test_cmp expect.no-decorate actual
+'
+
+test_expect_success 'deocrate-refs and log.excludeDecoration' '
+	cat >expect.decorate <<-\EOF &&
+	Merge-tag-reach (master)
+	Merge-tags-octopus-a-and-octopus-b
+	seventh
+	octopus-b (octopus-b)
+	octopus-a (octopus-a)
+	reach (reach)
+	EOF
+	git -c log.excludeDecoration="heads/oc*" log \
+		--decorate-refs="heads/*" \
+		-n6 --decorate=short --pretty="tformat:%f%d" >actual &&
+	test_cmp expect.decorate actual
+'
+
+test_expect_success 'decorate-refs-exclude and simplify-by-decoration' '
+	cat >expect.decorate <<-\EOF &&
+	Merge-tag-reach (HEAD -> master)
+	reach (tag: reach, reach)
+	seventh (tag: seventh)
+	Merge-branch-tangle
+	Merge-branch-side-early-part-into-tangle (tangle)
+	tangle-a (tag: tangle-a)
+	EOF
+	git log -n6 --decorate=short --pretty="tformat:%f%d" \
+		--decorate-refs-exclude="*octopus*" \
+		--simplify-by-decoration >actual &&
+	test_cmp expect.decorate actual &&
+	git -c log.excludeDecoration="*octopus*" log \
+		-n6 --decorate=short --pretty="tformat:%f%d" \
+		--simplify-by-decoration >actual &&
 	test_cmp expect.decorate actual
 '
 
@@ -942,7 +1003,7 @@ cat >expect <<\EOF
 | |
 | | diff --git a/reach.t b/reach.t
 | | new file mode 100644
-| | index 0000000..10c9591
+| | index BEFORE..AFTER
 | | --- /dev/null
 | | +++ b/reach.t
 | | @@ -0,0 +1 @@
@@ -965,7 +1026,7 @@ cat >expect <<\EOF
 | | |
 | | |   diff --git a/octopus-b.t b/octopus-b.t
 | | |   new file mode 100644
-| | |   index 0000000..d5fcad0
+| | |   index BEFORE..AFTER
 | | |   --- /dev/null
 | | |   +++ b/octopus-b.t
 | | |   @@ -0,0 +1 @@
@@ -981,7 +1042,7 @@ cat >expect <<\EOF
 | |
 | |   diff --git a/octopus-a.t b/octopus-a.t
 | |   new file mode 100644
-| |   index 0000000..11ee015
+| |   index BEFORE..AFTER
 | |   --- /dev/null
 | |   +++ b/octopus-a.t
 | |   @@ -0,0 +1 @@
@@ -997,7 +1058,7 @@ cat >expect <<\EOF
 |
 |   diff --git a/seventh.t b/seventh.t
 |   new file mode 100644
-|   index 0000000..9744ffc
+|   index BEFORE..AFTER
 |   --- /dev/null
 |   +++ b/seventh.t
 |   @@ -0,0 +1 @@
@@ -1031,7 +1092,7 @@ cat >expect <<\EOF
 | | | |
 | | | | diff --git a/tangle-a b/tangle-a
 | | | | new file mode 100644
-| | | | index 0000000..7898192
+| | | | index BEFORE..AFTER
 | | | | --- /dev/null
 | | | | +++ b/tangle-a
 | | | | @@ -0,0 +1 @@
@@ -1053,7 +1114,7 @@ cat >expect <<\EOF
 | | | |
 | | | |   diff --git a/2 b/2
 | | | |   new file mode 100644
-| | | |   index 0000000..0cfbf08
+| | | |   index BEFORE..AFTER
 | | | |   --- /dev/null
 | | | |   +++ b/2
 | | | |   @@ -0,0 +1 @@
@@ -1069,7 +1130,7 @@ cat >expect <<\EOF
 | | | |
 | | | | diff --git a/1 b/1
 | | | | new file mode 100644
-| | | | index 0000000..d00491f
+| | | | index BEFORE..AFTER
 | | | | --- /dev/null
 | | | | +++ b/1
 | | | | @@ -0,0 +1 @@
@@ -1085,7 +1146,7 @@ cat >expect <<\EOF
 | | | |
 | | | | diff --git a/one b/one
 | | | | new file mode 100644
-| | | | index 0000000..9a33383
+| | | | index BEFORE..AFTER
 | | | | --- /dev/null
 | | | | +++ b/one
 | | | | @@ -0,0 +1 @@
@@ -1101,7 +1162,7 @@ cat >expect <<\EOF
 | | |
 | | |   diff --git a/a/two b/a/two
 | | |   deleted file mode 100644
-| | |   index 9245af5..0000000
+| | |   index BEFORE..AFTER
 | | |   --- a/a/two
 | | |   +++ /dev/null
 | | |   @@ -1 +0,0 @@
@@ -1117,7 +1178,7 @@ cat >expect <<\EOF
 | | |
 | | | diff --git a/a/two b/a/two
 | | | new file mode 100644
-| | | index 0000000..9245af5
+| | | index BEFORE..AFTER
 | | | --- /dev/null
 | | | +++ b/a/two
 | | | @@ -0,0 +1 @@
@@ -1133,7 +1194,7 @@ cat >expect <<\EOF
 | |
 | |   diff --git a/ein b/ein
 | |   new file mode 100644
-| |   index 0000000..9d7e69f
+| |   index BEFORE..AFTER
 | |   --- /dev/null
 | |   +++ b/ein
 | |   @@ -0,0 +1 @@
@@ -1150,14 +1211,14 @@ cat >expect <<\EOF
 |
 |   diff --git a/ichi b/ichi
 |   new file mode 100644
-|   index 0000000..9d7e69f
+|   index BEFORE..AFTER
 |   --- /dev/null
 |   +++ b/ichi
 |   @@ -0,0 +1 @@
 |   +ichi
 |   diff --git a/one b/one
 |   deleted file mode 100644
-|   index 9d7e69f..0000000
+|   index BEFORE..AFTER
 |   --- a/one
 |   +++ /dev/null
 |   @@ -1 +0,0 @@
@@ -1172,7 +1233,7 @@ cat >expect <<\EOF
 |  1 file changed, 1 insertion(+), 1 deletion(-)
 |
 | diff --git a/one b/one
-| index 5626abf..9d7e69f 100644
+| index BEFORE..AFTER 100644
 | --- a/one
 | +++ b/one
 | @@ -1 +1 @@
@@ -1189,30 +1250,15 @@ cat >expect <<\EOF
 
   diff --git a/one b/one
   new file mode 100644
-  index 0000000..5626abf
+  index BEFORE..AFTER
   --- /dev/null
   +++ b/one
   @@ -0,0 +1 @@
   +one
 EOF
 
-sanitize_output () {
-	sed -e 's/ *$//' \
-	    -e 's/commit [0-9a-f]*$/commit COMMIT_OBJECT_NAME/' \
-	    -e 's/Merge: [ 0-9a-f]*$/Merge: MERGE_PARENTS/' \
-	    -e 's/Merge tag.*/Merge HEADS DESCRIPTION/' \
-	    -e 's/Merge commit.*/Merge HEADS DESCRIPTION/' \
-	    -e 's/, 0 deletions(-)//' \
-	    -e 's/, 0 insertions(+)//' \
-	    -e 's/ 1 files changed, / 1 file changed, /' \
-	    -e 's/, 1 deletions(-)/, 1 deletion(-)/' \
-	    -e 's/, 1 insertions(+)/, 1 insertion(+)/'
-}
-
 test_expect_success 'log --graph with diff and stats' '
-	git log --no-renames --graph --pretty=short --stat -p >actual &&
-	sanitize_output >actual.sanitized <actual &&
-	test_i18ncmp expect actual.sanitized
+	lib_test_cmp_short_graph --no-renames --stat -p
 '
 
 cat >expect <<\EOF
@@ -1232,7 +1278,7 @@ cat >expect <<\EOF
 *** | |
 *** | | diff --git a/reach.t b/reach.t
 *** | | new file mode 100644
-*** | | index 0000000..10c9591
+*** | | index BEFORE..AFTER
 *** | | --- /dev/null
 *** | | +++ b/reach.t
 *** | | @@ -0,0 +1 @@
@@ -1255,7 +1301,7 @@ cat >expect <<\EOF
 *** | | |
 *** | | |   diff --git a/octopus-b.t b/octopus-b.t
 *** | | |   new file mode 100644
-*** | | |   index 0000000..d5fcad0
+*** | | |   index BEFORE..AFTER
 *** | | |   --- /dev/null
 *** | | |   +++ b/octopus-b.t
 *** | | |   @@ -0,0 +1 @@
@@ -1271,7 +1317,7 @@ cat >expect <<\EOF
 *** | |
 *** | |   diff --git a/octopus-a.t b/octopus-a.t
 *** | |   new file mode 100644
-*** | |   index 0000000..11ee015
+*** | |   index BEFORE..AFTER
 *** | |   --- /dev/null
 *** | |   +++ b/octopus-a.t
 *** | |   @@ -0,0 +1 @@
@@ -1287,7 +1333,7 @@ cat >expect <<\EOF
 *** |
 *** |   diff --git a/seventh.t b/seventh.t
 *** |   new file mode 100644
-*** |   index 0000000..9744ffc
+*** |   index BEFORE..AFTER
 *** |   --- /dev/null
 *** |   +++ b/seventh.t
 *** |   @@ -0,0 +1 @@
@@ -1321,7 +1367,7 @@ cat >expect <<\EOF
 *** | | | |
 *** | | | | diff --git a/tangle-a b/tangle-a
 *** | | | | new file mode 100644
-*** | | | | index 0000000..7898192
+*** | | | | index BEFORE..AFTER
 *** | | | | --- /dev/null
 *** | | | | +++ b/tangle-a
 *** | | | | @@ -0,0 +1 @@
@@ -1343,7 +1389,7 @@ cat >expect <<\EOF
 *** | | | |
 *** | | | |   diff --git a/2 b/2
 *** | | | |   new file mode 100644
-*** | | | |   index 0000000..0cfbf08
+*** | | | |   index BEFORE..AFTER
 *** | | | |   --- /dev/null
 *** | | | |   +++ b/2
 *** | | | |   @@ -0,0 +1 @@
@@ -1359,7 +1405,7 @@ cat >expect <<\EOF
 *** | | | |
 *** | | | | diff --git a/1 b/1
 *** | | | | new file mode 100644
-*** | | | | index 0000000..d00491f
+*** | | | | index BEFORE..AFTER
 *** | | | | --- /dev/null
 *** | | | | +++ b/1
 *** | | | | @@ -0,0 +1 @@
@@ -1375,7 +1421,7 @@ cat >expect <<\EOF
 *** | | | |
 *** | | | | diff --git a/one b/one
 *** | | | | new file mode 100644
-*** | | | | index 0000000..9a33383
+*** | | | | index BEFORE..AFTER
 *** | | | | --- /dev/null
 *** | | | | +++ b/one
 *** | | | | @@ -0,0 +1 @@
@@ -1391,7 +1437,7 @@ cat >expect <<\EOF
 *** | | |
 *** | | |   diff --git a/a/two b/a/two
 *** | | |   deleted file mode 100644
-*** | | |   index 9245af5..0000000
+*** | | |   index BEFORE..AFTER
 *** | | |   --- a/a/two
 *** | | |   +++ /dev/null
 *** | | |   @@ -1 +0,0 @@
@@ -1407,7 +1453,7 @@ cat >expect <<\EOF
 *** | | |
 *** | | | diff --git a/a/two b/a/two
 *** | | | new file mode 100644
-*** | | | index 0000000..9245af5
+*** | | | index BEFORE..AFTER
 *** | | | --- /dev/null
 *** | | | +++ b/a/two
 *** | | | @@ -0,0 +1 @@
@@ -1423,7 +1469,7 @@ cat >expect <<\EOF
 *** | |
 *** | |   diff --git a/ein b/ein
 *** | |   new file mode 100644
-*** | |   index 0000000..9d7e69f
+*** | |   index BEFORE..AFTER
 *** | |   --- /dev/null
 *** | |   +++ b/ein
 *** | |   @@ -0,0 +1 @@
@@ -1440,14 +1486,14 @@ cat >expect <<\EOF
 *** |
 *** |   diff --git a/ichi b/ichi
 *** |   new file mode 100644
-*** |   index 0000000..9d7e69f
+*** |   index BEFORE..AFTER
 *** |   --- /dev/null
 *** |   +++ b/ichi
 *** |   @@ -0,0 +1 @@
 *** |   +ichi
 *** |   diff --git a/one b/one
 *** |   deleted file mode 100644
-*** |   index 9d7e69f..0000000
+*** |   index BEFORE..AFTER
 *** |   --- a/one
 *** |   +++ /dev/null
 *** |   @@ -1 +0,0 @@
@@ -1462,7 +1508,7 @@ cat >expect <<\EOF
 *** |  1 file changed, 1 insertion(+), 1 deletion(-)
 *** |
 *** | diff --git a/one b/one
-*** | index 5626abf..9d7e69f 100644
+*** | index BEFORE..AFTER 100644
 *** | --- a/one
 *** | +++ b/one
 *** | @@ -1 +1 @@
@@ -1479,7 +1525,7 @@ cat >expect <<\EOF
 ***
 ***   diff --git a/one b/one
 ***   new file mode 100644
-***   index 0000000..5626abf
+***   index BEFORE..AFTER
 ***   --- /dev/null
 ***   +++ b/one
 ***   @@ -0,0 +1 @@
@@ -1487,9 +1533,7 @@ cat >expect <<\EOF
 EOF
 
 test_expect_success 'log --line-prefix="*** " --graph with diff and stats' '
-	git log --line-prefix="*** " --no-renames --graph --pretty=short --stat -p >actual &&
-	sanitize_output >actual.sanitized <actual &&
-	test_i18ncmp expect actual.sanitized
+	lib_test_cmp_short_graph --line-prefix="*** " --no-renames --stat -p
 '
 
 cat >expect <<-\EOF
@@ -1511,9 +1555,7 @@ cat >expect <<-\EOF
 EOF
 
 test_expect_success 'log --graph with --name-status' '
-	git log --graph --format=%s --name-status tangle..reach >actual &&
-	sanitize_output <actual >actual.sanitized &&
-	test_cmp expect actual.sanitized
+	test_cmp_graph --name-status tangle..reach
 '
 
 cat >expect <<-\EOF
@@ -1535,9 +1577,7 @@ cat >expect <<-\EOF
 EOF
 
 test_expect_success 'log --graph with --name-only' '
-	git log --graph --format=%s --name-only tangle..reach >actual &&
-	sanitize_output <actual >actual.sanitized &&
-	test_cmp expect actual.sanitized
+	test_cmp_graph --name-only tangle..reach
 '
 
 test_expect_success 'dotdot is a parent directory' '
@@ -1555,6 +1595,14 @@ test_expect_success GPG 'setup signed branch' '
 	git commit -S -m signed_commit
 '
 
+test_expect_success GPG 'setup signed branch with subkey' '
+	test_when_finished "git reset --hard && git checkout master" &&
+	git checkout -b signed-subkey master &&
+	echo foo >foo &&
+	git add foo &&
+	git commit -SB7227189 -m signed_commit
+'
+
 test_expect_success GPGSM 'setup signed branch x509' '
 	test_when_finished "git reset --hard && git checkout master" &&
 	git checkout -b signed-x509 master &&
@@ -1565,6 +1613,18 @@ test_expect_success GPGSM 'setup signed branch x509' '
 	git commit -S -m signed_commit
 '
 
+test_expect_success GPGSM 'log x509 fingerprint' '
+	echo "F8BF62E0693D0694816377099909C779FA23FD65 | " >expect &&
+	git log -n1 --format="%GF | %GP" signed-x509 >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success GPGSM 'log OpenPGP fingerprint' '
+	echo "D4BE22311AD3131E5EDA29A461092E85B7227189" > expect &&
+	git log -n1 --format="%GP" signed-subkey >actual &&
+	test_cmp expect actual
+'
+
 test_expect_success GPG 'log --graph --show-signature' '
 	git log --graph --show-signature -n1 signed >actual &&
 	grep "^| gpg: Signature made" actual &&
@@ -1596,6 +1656,86 @@ test_expect_success GPG 'log --graph --show-signature for merged tag' '
 	grep "^| | gpg: Good signature" actual
 '
 
+test_expect_success GPG 'log --graph --show-signature for merged tag in shallow clone' '
+	test_when_finished "git reset --hard && git checkout master" &&
+	git checkout -b plain-shallow master &&
+	echo aaa >bar &&
+	git add bar &&
+	git commit -m bar_commit &&
+	git checkout --detach master &&
+	echo bbb >baz &&
+	git add baz &&
+	git commit -m baz_commit &&
+	git tag -s -m signed_tag_msg signed_tag_shallow &&
+	hash=$(git rev-parse HEAD) &&
+	git checkout plain-shallow &&
+	git merge --no-ff -m msg signed_tag_shallow &&
+	git clone --depth 1 --no-local . shallow &&
+	test_when_finished "rm -rf shallow" &&
+	git -C shallow log --graph --show-signature -n1 plain-shallow >actual &&
+	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 -E "^| | 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 &&
@@ -1617,6 +1757,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
@@ -1665,6 +1850,16 @@ test_expect_success 'log does not default to HEAD when rev input is given' '
 	test_must_be_empty actual
 '
 
+test_expect_success 'do not default to HEAD with ignored object on cmdline' '
+	git log --ignore-missing $ZERO_OID >actual &&
+	test_must_be_empty actual
+'
+
+test_expect_success 'do not default to HEAD with ignored object on stdin' '
+	echo $ZERO_OID | git log --ignore-missing --stdin >actual &&
+	test_must_be_empty actual
+'
+
 test_expect_success 'set up --source tests' '
 	git checkout --orphan source-a &&
 	test_commit one &&
@@ -1674,10 +1869,10 @@ test_expect_success 'set up --source tests' '
 '
 
 test_expect_success 'log --source paints branch names' '
-	cat >expect <<-\EOF &&
-	09e12a9	source-b three
-	8e393e1	source-a two
-	1ac6c77	source-b one
+	cat >expect <<-EOF &&
+	$(git rev-parse --short :/three)	source-b three
+	$(git rev-parse --short :/two  )	source-a two
+	$(git rev-parse --short :/one  )	source-b one
 	EOF
 	git log --oneline --source source-a source-b >actual &&
 	test_cmp expect actual
@@ -1685,19 +1880,19 @@ test_expect_success 'log --source paints branch names' '
 
 test_expect_success 'log --source paints tag names' '
 	git tag -m tagged source-tag &&
-	cat >expect <<-\EOF &&
-	09e12a9	source-tag three
-	8e393e1	source-a two
-	1ac6c77	source-tag one
+	cat >expect <<-EOF &&
+	$(git rev-parse --short :/three)	source-tag three
+	$(git rev-parse --short :/two  )	source-a two
+	$(git rev-parse --short :/one  )	source-tag one
 	EOF
 	git log --oneline --source source-tag source-a >actual &&
 	test_cmp expect actual
 '
 
 test_expect_success 'log --source paints symmetric ranges' '
-	cat >expect <<-\EOF &&
-	09e12a9	source-b three
-	8e393e1	source-a two
+	cat >expect <<-EOF &&
+	$(git rev-parse --short :/three)	source-b three
+	$(git rev-parse --short :/two  )	source-a two
 	EOF
 	git log --oneline --source source-a...source-b >actual &&
 	test_cmp expect actual
@@ -1707,4 +1902,11 @@ test_expect_success '--exclude-promisor-objects does not BUG-crash' '
 	test_must_fail git log --exclude-promisor-objects source-a
 '
 
+test_expect_success 'log --end-of-options' '
+       git update-ref refs/heads/--source HEAD &&
+       git log --end-of-options --source >actual &&
+       git log >expect &&
+       test_cmp expect actual
+'
+
 test_done
diff --git a/third_party/git/t/t4203-mailmap.sh b/third_party/git/t/t4203-mailmap.sh
index 918ada69eb..586c3a86b1 100755
--- a/third_party/git/t/t4203-mailmap.sh
+++ b/third_party/git/t/t4203-mailmap.sh
@@ -13,8 +13,8 @@ fuzz_blame () {
 }
 
 test_expect_success setup '
-	cat >contacts <<-\EOF &&
-	A U Thor <author@example.com>
+	cat >contacts <<- EOF &&
+	$GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>
 	nick1 <bugs@company.xx>
 	EOF
 
@@ -33,19 +33,19 @@ test_expect_success 'check-mailmap no arguments' '
 '
 
 test_expect_success 'check-mailmap arguments' '
-	cat >expect <<-\EOF &&
-	A U Thor <author@example.com>
+	cat >expect <<- EOF &&
+	$GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>
 	nick1 <bugs@company.xx>
 	EOF
 	git check-mailmap \
-		"A U Thor <author@example.com>" \
+		"$GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>" \
 		"nick1 <bugs@company.xx>" >actual &&
 	test_cmp expect actual
 '
 
 test_expect_success 'check-mailmap --stdin' '
-	cat >expect <<-\EOF &&
-	A U Thor <author@example.com>
+	cat >expect <<- EOF &&
+	$GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>
 	nick1 <bugs@company.xx>
 	EOF
 	git check-mailmap --stdin <contacts >actual &&
@@ -66,8 +66,8 @@ test_expect_success 'check-mailmap bogus contact' '
 	test_must_fail git check-mailmap bogus
 '
 
-cat >expect <<\EOF
-A U Thor (1):
+cat >expect << EOF
+$GIT_AUTHOR_NAME (1):
       initial
 
 nick1 (1):
@@ -90,7 +90,7 @@ nick1 (1):
 EOF
 
 test_expect_success 'default .mailmap' '
-	echo "Repo Guy <author@example.com>" > .mailmap &&
+	echo "Repo Guy <$GIT_AUTHOR_EMAIL>" > .mailmap &&
 	git shortlog HEAD >actual &&
 	test_cmp expect actual
 '
@@ -122,7 +122,7 @@ Internal Guy (1):
 
 EOF
 test_expect_success 'mailmap.file override' '
-	echo "External Guy <author@example.com>" >> internal_mailmap/.mailmap &&
+	echo "External Guy <$GIT_AUTHOR_EMAIL>" >> internal_mailmap/.mailmap &&
 	git config mailmap.file internal_mailmap/.mailmap &&
 	git shortlog HEAD >actual &&
 	test_cmp expect actual
@@ -178,8 +178,8 @@ test_expect_success 'name entry after email entry, case-insensitive' '
 	test_cmp expect actual
 '
 
-cat >expect <<\EOF
-A U Thor (1):
+cat >expect << EOF
+$GIT_AUTHOR_NAME (1):
       initial
 
 nick1 (1):
@@ -195,18 +195,18 @@ test_expect_success 'No mailmap files, but configured' '
 test_expect_success 'setup mailmap blob tests' '
 	git checkout -b map &&
 	test_when_finished "git checkout master" &&
-	cat >just-bugs <<-\EOF &&
+	cat >just-bugs <<- EOF &&
 	Blob Guy <bugs@company.xx>
 	EOF
-	cat >both <<-\EOF &&
-	Blob Guy <author@example.com>
+	cat >both <<- EOF &&
+	Blob Guy <$GIT_AUTHOR_EMAIL>
 	Blob Guy <bugs@company.xx>
 	EOF
-	printf "Tricky Guy <author@example.com>" >no-newline &&
+	printf "Tricky Guy <$GIT_AUTHOR_EMAIL>" >no-newline &&
 	git add just-bugs both no-newline &&
 	git commit -m "my mailmaps" &&
-	echo "Repo Guy <author@example.com>" >.mailmap &&
-	echo "Internal Guy <author@example.com>" >internal.map
+	echo "Repo Guy <$GIT_AUTHOR_EMAIL>" >.mailmap &&
+	echo "Internal Guy <$GIT_AUTHOR_EMAIL>" >internal.map
 '
 
 test_expect_success 'mailmap.blob set' '
@@ -266,12 +266,12 @@ test_expect_success 'mailmap.blob defaults to off in non-bare repo' '
 	git init non-bare &&
 	(
 		cd non-bare &&
-		test_commit one .mailmap "Fake Name <author@example.com>" &&
+		test_commit one .mailmap "Fake Name <$GIT_AUTHOR_EMAIL>" &&
 		echo "     1	Fake Name" >expect &&
 		git shortlog -ns HEAD >actual &&
 		test_cmp expect actual &&
 		rm .mailmap &&
-		echo "     1	A U Thor" >expect &&
+		echo "     1	$GIT_AUTHOR_NAME" >expect &&
 		git shortlog -ns HEAD >actual &&
 		test_cmp expect actual
 	)
@@ -305,26 +305,26 @@ test_expect_success 'cleanup after mailmap.blob tests' '
 '
 
 test_expect_success 'single-character name' '
-	echo "     1	A <author@example.com>" >expect &&
+	echo "     1	A <$GIT_AUTHOR_EMAIL>" >expect &&
 	echo "     1	nick1 <bugs@company.xx>" >>expect &&
-	echo "A <author@example.com>" >.mailmap &&
+	echo "A <$GIT_AUTHOR_EMAIL>" >.mailmap &&
 	test_when_finished "rm .mailmap" &&
 	git shortlog -es HEAD >actual &&
 	test_cmp expect actual
 '
 
 test_expect_success 'preserve canonical email case' '
-	echo "     1	A U Thor <AUTHOR@example.com>" >expect &&
+	echo "     1	$GIT_AUTHOR_NAME <AUTHOR@example.com>" >expect &&
 	echo "     1	nick1 <bugs@company.xx>" >>expect &&
-	echo "<AUTHOR@example.com> <author@example.com>" >.mailmap &&
+	echo "<AUTHOR@example.com> <$GIT_AUTHOR_EMAIL>" >.mailmap &&
 	test_when_finished "rm .mailmap" &&
 	git shortlog -es HEAD >actual &&
 	test_cmp expect actual
 '
 
 # Extended mailmap configurations should give us the following output for shortlog
-cat >expect <<\EOF
-A U Thor <author@example.com> (1):
+cat >expect << EOF
+$GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> (1):
       initial
 
 CTO <cto@company.xx> (1):
@@ -370,7 +370,7 @@ test_expect_success 'Shortlog output (complex mapping)' '
 	git commit --author "CTO <cto@coompany.xx>" -m seventh &&
 
 	mkdir -p internal_mailmap &&
-	echo "Committed <committer@example.com>" > internal_mailmap/.mailmap &&
+	echo "Committed <$GIT_COMMITTER_EMAIL>" > internal_mailmap/.mailmap &&
 	echo "<cto@company.xx>                       <cto@coompany.xx>" >> internal_mailmap/.mailmap &&
 	echo "Some Dude <some@dude.xx>         nick1 <bugs@company.xx>" >> internal_mailmap/.mailmap &&
 	echo "Other Author <other@author.xx>   nick2 <bugs@company.xx>" >> internal_mailmap/.mailmap &&
@@ -384,27 +384,27 @@ test_expect_success 'Shortlog output (complex mapping)' '
 '
 
 # git log with --pretty format which uses the name and email mailmap placemarkers
-cat >expect <<\EOF
+cat >expect << EOF
 Author CTO <cto@coompany.xx> maps to CTO <cto@company.xx>
-Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>
+Committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> maps to Committed <$GIT_COMMITTER_EMAIL>
 
 Author claus <me@company.xx> maps to Santa Claus <santa.claus@northpole.xx>
-Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>
+Committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> maps to Committed <$GIT_COMMITTER_EMAIL>
 
 Author santa <me@company.xx> maps to Santa Claus <santa.claus@northpole.xx>
-Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>
+Committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> maps to Committed <$GIT_COMMITTER_EMAIL>
 
 Author nick2 <nick2@company.xx> maps to Other Author <other@author.xx>
-Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>
+Committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> maps to Committed <$GIT_COMMITTER_EMAIL>
 
 Author nick2 <bugs@company.xx> maps to Other Author <other@author.xx>
-Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>
+Committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> maps to Committed <$GIT_COMMITTER_EMAIL>
 
 Author nick1 <bugs@company.xx> maps to Some Dude <some@dude.xx>
-Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>
+Committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> maps to Committed <$GIT_COMMITTER_EMAIL>
 
-Author A U Thor <author@example.com> maps to A U Thor <author@example.com>
-Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>
+Author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> maps to $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>
+Committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> maps to Committed <$GIT_COMMITTER_EMAIL>
 EOF
 
 test_expect_success 'Log output (complex mapping)' '
@@ -412,14 +412,42 @@ test_expect_success 'Log output (complex mapping)' '
 	test_cmp expect actual
 '
 
-cat >expect <<\EOF
+cat >expect << EOF
+Author email cto@coompany.xx has local-part cto
+Committer email $GIT_COMMITTER_EMAIL has local-part $TEST_COMMITTER_LOCALNAME
+
+Author email me@company.xx has local-part me
+Committer email $GIT_COMMITTER_EMAIL has local-part $TEST_COMMITTER_LOCALNAME
+
+Author email me@company.xx has local-part me
+Committer email $GIT_COMMITTER_EMAIL has local-part $TEST_COMMITTER_LOCALNAME
+
+Author email nick2@company.xx has local-part nick2
+Committer email $GIT_COMMITTER_EMAIL has local-part $TEST_COMMITTER_LOCALNAME
+
+Author email bugs@company.xx has local-part bugs
+Committer email $GIT_COMMITTER_EMAIL has local-part $TEST_COMMITTER_LOCALNAME
+
+Author email bugs@company.xx has local-part bugs
+Committer email $GIT_COMMITTER_EMAIL has local-part $TEST_COMMITTER_LOCALNAME
+
+Author email author@example.com has local-part author
+Committer email $GIT_COMMITTER_EMAIL has local-part $TEST_COMMITTER_LOCALNAME
+EOF
+
+test_expect_success 'Log output (local-part email address)' '
+	git log --pretty=format:"Author email %ae has local-part %al%nCommitter email %ce has local-part %cl%n" >actual &&
+	test_cmp expect actual
+'
+
+cat >expect << EOF
 Author: CTO <cto@company.xx>
 Author: Santa Claus <santa.claus@northpole.xx>
 Author: Santa Claus <santa.claus@northpole.xx>
 Author: Other Author <other@author.xx>
 Author: Other Author <other@author.xx>
 Author: Some Dude <some@dude.xx>
-Author: A U Thor <author@example.com>
+Author: $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>
 EOF
 
 test_expect_success 'Log output with --use-mailmap' '
@@ -427,14 +455,14 @@ test_expect_success 'Log output with --use-mailmap' '
 	test_cmp expect actual
 '
 
-cat >expect <<\EOF
+cat >expect << EOF
 Author: CTO <cto@company.xx>
 Author: Santa Claus <santa.claus@northpole.xx>
 Author: Santa Claus <santa.claus@northpole.xx>
 Author: Other Author <other@author.xx>
 Author: Other Author <other@author.xx>
 Author: Some Dude <some@dude.xx>
-Author: A U Thor <author@example.com>
+Author: $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>
 EOF
 
 test_expect_success 'Log output with log.mailmap' '
@@ -443,28 +471,28 @@ test_expect_success 'Log output with log.mailmap' '
 '
 
 test_expect_success 'log.mailmap=false disables mailmap' '
-	cat >expect <<-\EOF &&
+	cat >expect <<- EOF &&
 	Author: CTO <cto@coompany.xx>
 	Author: claus <me@company.xx>
 	Author: santa <me@company.xx>
 	Author: nick2 <nick2@company.xx>
 	Author: nick2 <bugs@company.xx>
 	Author: nick1 <bugs@company.xx>
-	Author: A U Thor <author@example.com>
+	Author: $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>
 	EOF
 	git -c log.mailmap=False log | grep Author > actual &&
 	test_cmp expect actual
 '
 
 test_expect_success '--no-use-mailmap disables mailmap' '
-	cat >expect <<-\EOF &&
+	cat >expect <<- EOF &&
 	Author: CTO <cto@coompany.xx>
 	Author: claus <me@company.xx>
 	Author: santa <me@company.xx>
 	Author: nick2 <nick2@company.xx>
 	Author: nick2 <bugs@company.xx>
 	Author: nick1 <bugs@company.xx>
-	Author: A U Thor <author@example.com>
+	Author: $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>
 	EOF
 	git log --no-use-mailmap | grep Author > actual &&
 	test_cmp expect actual
@@ -500,8 +528,8 @@ test_expect_success 'Only grep replaced author with --use-mailmap' '
 '
 
 # git blame
-cat >expect <<\EOF
-^OBJI (A U Thor     DATE 1) one
+cat >expect <<EOF
+^OBJI ($GIT_AUTHOR_NAME     DATE 1) one
 OBJID (Some Dude    DATE 2) two
 OBJID (Other Author DATE 3) three
 OBJID (Other Author DATE 4) four
diff --git a/third_party/git/t/t4204-patch-id.sh b/third_party/git/t/t4204-patch-id.sh
index 0288c17ec6..8ff8bd84c7 100755
--- a/third_party/git/t/t4204-patch-id.sh
+++ b/third_party/git/t/t4204-patch-id.sh
@@ -25,7 +25,7 @@ test_expect_success 'setup' '
 
 test_expect_success 'patch-id output is well-formed' '
 	git log -p -1 | git patch-id >output &&
-	grep "^[a-f0-9]\{40\} $(git rev-parse HEAD)$" output
+	grep "^$OID_REGEX $(git rev-parse HEAD)$" output
 '
 
 #calculate patch id. Make sure output is not empty.
diff --git a/third_party/git/t/t4205-log-pretty-formats.sh b/third_party/git/t/t4205-log-pretty-formats.sh
index f42a69faa2..204c149d5a 100755
--- a/third_party/git/t/t4205-log-pretty-formats.sh
+++ b/third_party/git/t/t4205-log-pretty-formats.sh
@@ -134,6 +134,36 @@ test_expect_failure C_LOCALE_OUTPUT 'NUL termination with --stat' '
 	test_cmp expected actual
 '
 
+for p in short medium full fuller email raw
+do
+	test_expect_success "NUL termination with --reflog --pretty=$p" '
+		revs="$(git rev-list --reflog)" &&
+		for r in $revs
+		do
+			git show -s "$r" --pretty="$p" &&
+			printf "\0" || return 1
+		done >expect &&
+		{
+			git log -z --reflog --pretty="$p" &&
+			printf "\0"
+		} >actual &&
+		test_cmp expect actual
+	'
+done
+
+test_expect_success 'NUL termination with --reflog --pretty=oneline' '
+	revs="$(git rev-list --reflog)" &&
+	for r in $revs
+	do
+		git show -s --pretty=oneline "$r" >raw &&
+		cat raw | lf_to_nul || exit 1
+	done >expect &&
+	# the trailing NUL is already produced so we do not need to
+	# output another one
+	git log -z --pretty=oneline --reflog >actual &&
+	test_cmp expect actual
+'
+
 test_expect_success 'setup more commits' '
 	test_commit "message one" one one message-one &&
 	test_commit "message two" two two message-two &&
@@ -503,6 +533,12 @@ test_expect_success 'ISO and ISO-strict date formats display the same values' '
 	test_cmp expected actual
 '
 
+test_expect_success 'short date' '
+	git log --format=%ad%n%cd --date=short >expected &&
+	git log --format=%as%n%cs >actual &&
+	test_cmp expected actual
+'
+
 # get new digests (with no abbreviations)
 test_expect_success 'set up log decoration tests' '
 	head1=$(git rev-parse --verify HEAD~0) &&
@@ -640,7 +676,7 @@ test_expect_success 'pretty format %(trailers:key=foo) multiple keys' '
 	test_cmp expect actual
 '
 
-test_expect_success '%(trailers:key=nonexistant) becomes empty' '
+test_expect_success '%(trailers:key=nonexistent) becomes empty' '
 	git log --no-walk --pretty="x%(trailers:key=Nacked-by)x" >actual &&
 	echo "xx" >expect &&
 	test_cmp expect actual
@@ -788,4 +824,47 @@ test_expect_success '%S in git log --format works with other placeholders (part
 	test_cmp expect actual
 '
 
+test_expect_success 'log --pretty=reference' '
+	git log --pretty="tformat:%h (%s, %as)" >expect &&
+	git log --pretty=reference >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'log --pretty=reference with log.date is overridden by short date' '
+	git log --pretty="tformat:%h (%s, %as)" >expect &&
+	test_config log.date rfc &&
+	git log --pretty=reference >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'log --pretty=reference with explicit date overrides short date' '
+	git log --date=rfc --pretty="tformat:%h (%s, %ad)" >expect &&
+	git log --date=rfc --pretty=reference >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'log --pretty=reference is never unabbreviated' '
+	git log --pretty="tformat:%h (%s, %as)" >expect &&
+	git log --no-abbrev-commit --pretty=reference >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'log --pretty=reference is never decorated' '
+	git log --pretty="tformat:%h (%s, %as)" >expect &&
+	git log --decorate=short --pretty=reference >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'log --pretty=reference does not output reflog info' '
+	git log --walk-reflogs --pretty="tformat:%h (%s, %as)" >expect &&
+	git log --walk-reflogs --pretty=reference >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'log --pretty=reference is colored appropriately' '
+	git log --color=always --pretty="tformat:%C(auto)%h (%s, %as)" >expect &&
+	git log --color=always --pretty=reference >actual &&
+	test_cmp expect actual
+'
+
 test_done
diff --git a/third_party/git/t/t4208-log-magic-pathspec.sh b/third_party/git/t/t4208-log-magic-pathspec.sh
index 4c8f3b8e1b..6cdbe4747a 100755
--- a/third_party/git/t/t4208-log-magic-pathspec.sh
+++ b/third_party/git/t/t4208-log-magic-pathspec.sh
@@ -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' '
diff --git a/third_party/git/t/t4210-log-i18n.sh b/third_party/git/t/t4210-log-i18n.sh
index 7c519436ef..d2dfcf164e 100755
--- a/third_party/git/t/t4210-log-i18n.sh
+++ b/third_party/git/t/t4210-log-i18n.sh
@@ -1,12 +1,22 @@
 #!/bin/sh
 
 test_description='test log with i18n features'
-. ./test-lib.sh
+. ./lib-gettext.sh
 
 # two forms of รฉ
 utf8_e=$(printf '\303\251')
 latin1_e=$(printf '\351')
 
+# invalid UTF-8
+invalid_e=$(printf '\303\50)') # ")" at end to close opening "("
+
+have_reg_illseq=
+if test_have_prereq GETTEXT_LOCALE &&
+	! LC_ALL=$is_IS_locale test-tool regex --silent $latin1_e
+then
+	have_reg_illseq=1
+fi
+
 test_expect_success 'create commits in different encodings' '
 	test_tick &&
 	cat >msg <<-EOF &&
@@ -53,4 +63,72 @@ test_expect_success 'log --grep does not find non-reencoded values (latin1)' '
 	test_must_be_empty actual
 '
 
+triggers_undefined_behaviour () {
+	local engine=$1
+
+	case $engine in
+	fixed)
+		if test -n "$have_reg_illseq" &&
+			! test_have_prereq LIBPCRE2
+		then
+			return 0
+		fi
+		;;
+	basic|extended)
+		if test -n "$have_reg_illseq"
+		then
+			return 0
+		fi
+		;;
+	esac
+	return 1
+}
+
+mismatched_git_log () {
+	local pattern=$1
+
+	LC_ALL=$is_IS_locale git log --encoding=ISO-8859-1 --format=%s \
+		--grep=$pattern
+}
+
+for engine in fixed basic extended perl
+do
+	prereq=
+	if test $engine = "perl"
+	then
+		prereq=PCRE
+	fi
+	force_regex=
+	if test $engine != "fixed"
+	then
+		force_regex='.*'
+	fi
+
+	test_expect_success $prereq "config grep.patternType=$engine" "
+		git config grep.patternType $engine
+	"
+
+	test_expect_success GETTEXT_LOCALE,$prereq "log --grep does not find non-reencoded values (latin1 + locale)" "
+		mismatched_git_log '$force_regex$utf8_e' >actual &&
+		test_must_be_empty actual
+	"
+
+	if ! triggers_undefined_behaviour $engine
+	then
+		test_expect_success !MINGW,GETTEXT_LOCALE,$prereq "log --grep searches in log output encoding (latin1 + locale)" "
+			cat >expect <<-\EOF &&
+			latin1
+			utf8
+			EOF
+			mismatched_git_log '$force_regex$latin1_e' >actual &&
+			test_cmp expect actual
+		"
+
+		test_expect_success GETTEXT_LOCALE,$prereq "log --grep does not die on invalid UTF-8 value (latin1 + locale + invalid needle)" "
+			mismatched_git_log '$force_regex$invalid_e' >actual &&
+			test_must_be_empty actual
+		"
+	fi
+done
+
 test_done
diff --git a/third_party/git/t/t4211-line-log.sh b/third_party/git/t/t4211-line-log.sh
index 1db7bd0f59..2d1d7b5d19 100755
--- a/third_party/git/t/t4211-line-log.sh
+++ b/third_party/git/t/t4211-line-log.sh
@@ -11,7 +11,7 @@ test_expect_success 'setup (import history)' '
 canned_test_1 () {
 	test_expect_$1 "$2" "
 		git log $2 >actual &&
-		test_cmp \"\$TEST_DIRECTORY\"/t4211/expect.$3 actual
+		test_cmp \"\$TEST_DIRECTORY\"/t4211/$(test_oid algo)/expect.$3 actual
 	"
 }
 
@@ -132,4 +132,162 @@ test_expect_success '--raw is forbidden' '
 	test_must_fail git log -L1,24:b.c --raw
 '
 
+test_expect_success 'setup for checking fancy rename following' '
+	git checkout --orphan moves-start &&
+	git reset --hard &&
+
+	printf "%s\n"    12 13 14 15      b c d e   >file-1 &&
+	printf "%s\n"    22 23 24 25      B C D E   >file-2 &&
+	git add file-1 file-2 &&
+	test_tick &&
+	git commit -m "Add file-1 and file-2" &&
+	oid_add_f1_f2=$(git rev-parse --short HEAD) &&
+
+	git checkout -b moves-main &&
+	printf "%s\n" 11 12 13 14 15      b c d e   >file-1 &&
+	git commit -a -m "Modify file-1 on main" &&
+	oid_mod_f1_main=$(git rev-parse --short HEAD) &&
+
+	printf "%s\n" 21 22 23 24 25      B C D E   >file-2 &&
+	git commit -a -m "Modify file-2 on main #1" &&
+	oid_mod_f2_main_1=$(git rev-parse --short HEAD) &&
+
+	git mv file-1 renamed-1 &&
+	git commit -m "Rename file-1 to renamed-1 on main" &&
+
+	printf "%s\n" 11 12 13 14 15      b c d e f >renamed-1 &&
+	git commit -a -m "Modify renamed-1 on main" &&
+	oid_mod_r1_main=$(git rev-parse --short HEAD) &&
+
+	printf "%s\n" 21 22 23 24 25      B C D E F >file-2 &&
+	git commit -a -m "Modify file-2 on main #2" &&
+	oid_mod_f2_main_2=$(git rev-parse --short HEAD) &&
+
+	git checkout -b moves-side moves-start &&
+	printf "%s\n"    12 13 14 15 16   b c d e   >file-1 &&
+	git commit -a -m "Modify file-1 on side #1" &&
+	oid_mod_f1_side_1=$(git rev-parse --short HEAD) &&
+
+	printf "%s\n"    22 23 24 25 26   B C D E   >file-2 &&
+	git commit -a -m "Modify file-2 on side" &&
+	oid_mod_f2_side=$(git rev-parse --short HEAD) &&
+
+	git mv file-2 renamed-2 &&
+	git commit -m "Rename file-2 to renamed-2 on side" &&
+
+	printf "%s\n"    12 13 14 15 16 a b c d e   >file-1 &&
+	git commit -a -m "Modify file-1 on side #2" &&
+	oid_mod_f1_side_2=$(git rev-parse --short HEAD) &&
+
+	printf "%s\n"    22 23 24 25 26 A B C D E   >renamed-2 &&
+	git commit -a -m "Modify renamed-2 on side" &&
+	oid_mod_r2_side=$(git rev-parse --short HEAD) &&
+
+	git checkout moves-main &&
+	git merge moves-side &&
+	oid_merge=$(git rev-parse --short HEAD)
+'
+
+test_expect_success 'fancy rename following #1' '
+	cat >expect <<-EOF &&
+	$oid_merge Merge branch '\''moves-side'\'' into moves-main
+	$oid_mod_f1_side_2 Modify file-1 on side #2
+	$oid_mod_f1_side_1 Modify file-1 on side #1
+	$oid_mod_r1_main Modify renamed-1 on main
+	$oid_mod_f1_main Modify file-1 on main
+	$oid_add_f1_f2 Add file-1 and file-2
+	EOF
+	git log -L1:renamed-1 --oneline --no-patch >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'fancy rename following #2' '
+	cat >expect <<-EOF &&
+	$oid_merge Merge branch '\''moves-side'\'' into moves-main
+	$oid_mod_r2_side Modify renamed-2 on side
+	$oid_mod_f2_side Modify file-2 on side
+	$oid_mod_f2_main_2 Modify file-2 on main #2
+	$oid_mod_f2_main_1 Modify file-2 on main #1
+	$oid_add_f1_f2 Add file-1 and file-2
+	EOF
+	git log -L1:renamed-2 --oneline --no-patch >actual &&
+	test_cmp expect actual
+'
+
+# Create the following linear history, where each commit does what its
+# subject line promises:
+#
+#   * 66c6410 Modify func2() in file.c
+#   * 50834e5 Modify other-file
+#   * fe5851c Modify func1() in file.c
+#   * 8c7c7dd Add other-file
+#   * d5f4417 Add func1() and func2() in file.c
+test_expect_success 'setup for checking line-log and parent oids' '
+	git checkout --orphan parent-oids &&
+	git reset --hard &&
+
+	cat >file.c <<-\EOF &&
+	int func1()
+	{
+	    return F1;
+	}
+
+	int func2()
+	{
+	    return F2;
+	}
+	EOF
+	git add file.c &&
+	test_tick &&
+	first_tick=$test_tick &&
+	git commit -m "Add func1() and func2() in file.c" &&
+
+	echo 1 >other-file &&
+	git add other-file &&
+	test_tick &&
+	git commit -m "Add other-file" &&
+
+	sed -e "s/F1/F1 + 1/" file.c >tmp &&
+	mv tmp file.c &&
+	git commit -a -m "Modify func1() in file.c" &&
+
+	echo 2 >other-file &&
+	git commit -a -m "Modify other-file" &&
+
+	sed -e "s/F2/F2 + 2/" file.c >tmp &&
+	mv tmp file.c &&
+	git commit -a -m "Modify func2() in file.c" &&
+
+	head_oid=$(git rev-parse --short HEAD) &&
+	prev_oid=$(git rev-parse --short HEAD^) &&
+	root_oid=$(git rev-parse --short HEAD~4)
+'
+
+# Parent oid should be from immediate parent.
+test_expect_success 'parent oids without parent rewriting' '
+	cat >expect <<-EOF &&
+	$head_oid $prev_oid Modify func2() in file.c
+	$root_oid  Add func1() and func2() in file.c
+	EOF
+	git log --format="%h %p %s" --no-patch -L:func2:file.c >actual &&
+	test_cmp expect actual
+'
+
+# Parent oid should be from the most recent ancestor touching func2(),
+# i.e. in this case from the root commit.
+test_expect_success 'parent oids with parent rewriting' '
+	cat >expect <<-EOF &&
+	$head_oid $root_oid Modify func2() in file.c
+	$root_oid  Add func1() and func2() in file.c
+	EOF
+	git log --format="%h %p %s" --no-patch -L:func2:file.c --parents >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'line-log with --before' '
+	echo $root_oid >expect &&
+	git log --format=%h --no-patch -L:func2:file.c --before=$first_tick >actual &&
+	test_cmp expect actual
+'
+
 test_done
diff --git a/third_party/git/t/t4211/expect.beginning-of-file b/third_party/git/t/t4211/sha1/expect.beginning-of-file
index 91b4054898..91b4054898 100644
--- a/third_party/git/t/t4211/expect.beginning-of-file
+++ b/third_party/git/t/t4211/sha1/expect.beginning-of-file
diff --git a/third_party/git/t/t4211/expect.end-of-file b/third_party/git/t/t4211/sha1/expect.end-of-file
index bd25bb2f59..bd25bb2f59 100644
--- a/third_party/git/t/t4211/expect.end-of-file
+++ b/third_party/git/t/t4211/sha1/expect.end-of-file
diff --git a/third_party/git/t/t4211/expect.move-support-f b/third_party/git/t/t4211/sha1/expect.move-support-f
index c905e01bc2..c905e01bc2 100644
--- a/third_party/git/t/t4211/expect.move-support-f
+++ b/third_party/git/t/t4211/sha1/expect.move-support-f
diff --git a/third_party/git/t/t4211/expect.multiple b/third_party/git/t/t4211/sha1/expect.multiple
index 76ad5b598c..76ad5b598c 100644
--- a/third_party/git/t/t4211/expect.multiple
+++ b/third_party/git/t/t4211/sha1/expect.multiple
diff --git a/third_party/git/t/t4211/expect.multiple-overlapping b/third_party/git/t/t4211/sha1/expect.multiple-overlapping
index d930b6eec4..d930b6eec4 100644
--- a/third_party/git/t/t4211/expect.multiple-overlapping
+++ b/third_party/git/t/t4211/sha1/expect.multiple-overlapping
diff --git a/third_party/git/t/t4211/expect.multiple-superset b/third_party/git/t/t4211/sha1/expect.multiple-superset
index d930b6eec4..d930b6eec4 100644
--- a/third_party/git/t/t4211/expect.multiple-superset
+++ b/third_party/git/t/t4211/sha1/expect.multiple-superset
diff --git a/third_party/git/t/t4211/expect.parallel-change-f-to-main b/third_party/git/t/t4211/sha1/expect.parallel-change-f-to-main
index 052def8074..052def8074 100644
--- a/third_party/git/t/t4211/expect.parallel-change-f-to-main
+++ b/third_party/git/t/t4211/sha1/expect.parallel-change-f-to-main
diff --git a/third_party/git/t/t4211/expect.simple-f b/third_party/git/t/t4211/sha1/expect.simple-f
index a1f5bc49c8..a1f5bc49c8 100644
--- a/third_party/git/t/t4211/expect.simple-f
+++ b/third_party/git/t/t4211/sha1/expect.simple-f
diff --git a/third_party/git/t/t4211/expect.simple-f-to-main b/third_party/git/t/t4211/sha1/expect.simple-f-to-main
index a475768710..a475768710 100644
--- a/third_party/git/t/t4211/expect.simple-f-to-main
+++ b/third_party/git/t/t4211/sha1/expect.simple-f-to-main
diff --git a/third_party/git/t/t4211/expect.simple-main b/third_party/git/t/t4211/sha1/expect.simple-main
index 39ce39bebe..39ce39bebe 100644
--- a/third_party/git/t/t4211/expect.simple-main
+++ b/third_party/git/t/t4211/sha1/expect.simple-main
diff --git a/third_party/git/t/t4211/expect.simple-main-to-end b/third_party/git/t/t4211/sha1/expect.simple-main-to-end
index 8480bd9cc4..8480bd9cc4 100644
--- a/third_party/git/t/t4211/expect.simple-main-to-end
+++ b/third_party/git/t/t4211/sha1/expect.simple-main-to-end
diff --git a/third_party/git/t/t4211/expect.two-ranges b/third_party/git/t/t4211/sha1/expect.two-ranges
index 6109aa0dce..6109aa0dce 100644
--- a/third_party/git/t/t4211/expect.two-ranges
+++ b/third_party/git/t/t4211/sha1/expect.two-ranges
diff --git a/third_party/git/t/t4211/expect.vanishes-early b/third_party/git/t/t4211/sha1/expect.vanishes-early
index 1f7cd06941..1f7cd06941 100644
--- a/third_party/git/t/t4211/expect.vanishes-early
+++ b/third_party/git/t/t4211/sha1/expect.vanishes-early
diff --git a/third_party/git/t/t4211/sha256/expect.beginning-of-file b/third_party/git/t/t4211/sha256/expect.beginning-of-file
new file mode 100644
index 0000000000..5adfdfc1a1
--- /dev/null
+++ b/third_party/git/t/t4211/sha256/expect.beginning-of-file
@@ -0,0 +1,43 @@
+commit 62a40b38fa4f00800004aee81ef287b7201317594ebcb990f38cbe493b01d200
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:47:40 2013 +0100
+
+    change at very beginning
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -1,3 +1,4 @@
++#include <unistd.h>
+ #include <stdio.h>
+ 
+ long f(long x)
+
+commit ccf97b9878189c40a981da50b15713bb80a35755326320ec80900caf22ced46f
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:45:16 2013 +0100
+
+    touch both functions
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -1,3 +1,3 @@
+ #include <stdio.h>
+ 
+-int f(int x)
++long f(long x)
+
+commit 1dd7e9b2b1699324b53b341e728653b913bc192a14dfea168c5b51f2b3d03592
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:44:48 2013 +0100
+
+    initial
+
+diff --git a/a.c b/a.c
+--- /dev/null
++++ b/a.c
+@@ -0,0 +1,3 @@
++#include <stdio.h>
++
++int f(int x)
diff --git a/third_party/git/t/t4211/sha256/expect.end-of-file b/third_party/git/t/t4211/sha256/expect.end-of-file
new file mode 100644
index 0000000000..03ab5c1784
--- /dev/null
+++ b/third_party/git/t/t4211/sha256/expect.end-of-file
@@ -0,0 +1,62 @@
+commit 5526ed05c2476b56af8b7be499e8f78bd50f490740733a9ca7e1f55878fa90a9
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:48:43 2013 +0100
+
+    change back to complete line
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -20,3 +20,5 @@
+ 	printf("%ld\n", f(15));
+ 	return 0;
+-}
+\ No newline at end of file
++}
++
++/* incomplete lines are bad! */
+
+commit 29f32ac3141c48b22803e5c4127b719917b67d0f8ca8c5248bebfa2a19f7da10
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:48:10 2013 +0100
+
+    change to an incomplete line at end
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -20,3 +20,3 @@
+ 	printf("%ld\n", f(15));
+ 	return 0;
+-}
++}
+\ No newline at end of file
+
+commit ccf97b9878189c40a981da50b15713bb80a35755326320ec80900caf22ced46f
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:45:16 2013 +0100
+
+    touch both functions
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -19,3 +19,3 @@
+-	printf("%d\n", f(15));
++	printf("%ld\n", f(15));
+ 	return 0;
+ }
+
+commit 1dd7e9b2b1699324b53b341e728653b913bc192a14dfea168c5b51f2b3d03592
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:44:48 2013 +0100
+
+    initial
+
+diff --git a/a.c b/a.c
+--- /dev/null
++++ b/a.c
+@@ -0,0 +18,3 @@
++	printf("%d\n", f(15));
++	return 0;
++}
diff --git a/third_party/git/t/t4211/sha256/expect.move-support-f b/third_party/git/t/t4211/sha256/expect.move-support-f
new file mode 100644
index 0000000000..223b4ed2a0
--- /dev/null
+++ b/third_party/git/t/t4211/sha256/expect.move-support-f
@@ -0,0 +1,80 @@
+commit 4f7a58195a92c400e28a2354328587f1ff14fb77f5cf894536f17ccbc72931b9
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:49:50 2013 +0100
+
+    another simple change
+
+diff --git a/b.c b/b.c
+--- a/b.c
++++ b/b.c
+@@ -4,9 +4,9 @@
+ long f(long x)
+ {
+ 	int s = 0;
+ 	while (x) {
+-		x >>= 1;
++		x /= 2;
+ 		s++;
+ 	}
+ 	return s;
+ }
+
+commit ccf97b9878189c40a981da50b15713bb80a35755326320ec80900caf22ced46f
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:45:16 2013 +0100
+
+    touch both functions
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,9 +3,9 @@
+-int f(int x)
++long f(long x)
+ {
+ 	int s = 0;
+ 	while (x) {
+ 		x >>= 1;
+ 		s++;
+ 	}
+ 	return s;
+ }
+
+commit f6434acd34260a6c9f61e96d96bf9a323d330561df5b1ca2631104f82026dfed
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:44:55 2013 +0100
+
+    change f()
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,8 +3,9 @@
+ int f(int x)
+ {
+ 	int s = 0;
+ 	while (x) {
+ 		x >>= 1;
+ 		s++;
+ 	}
++	return s;
+ }
+
+commit 1dd7e9b2b1699324b53b341e728653b913bc192a14dfea168c5b51f2b3d03592
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:44:48 2013 +0100
+
+    initial
+
+diff --git a/a.c b/a.c
+--- /dev/null
++++ b/a.c
+@@ -0,0 +3,8 @@
++int f(int x)
++{
++	int s = 0;
++	while (x) {
++		x >>= 1;
++		s++;
++	}
++}
diff --git a/third_party/git/t/t4211/sha256/expect.multiple b/third_party/git/t/t4211/sha256/expect.multiple
new file mode 100644
index 0000000000..ca00409b9a
--- /dev/null
+++ b/third_party/git/t/t4211/sha256/expect.multiple
@@ -0,0 +1,104 @@
+commit 5526ed05c2476b56af8b7be499e8f78bd50f490740733a9ca7e1f55878fa90a9
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:48:43 2013 +0100
+
+    change back to complete line
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -18,5 +18,7 @@
+ int main ()
+ {
+ 	printf("%ld\n", f(15));
+ 	return 0;
+-}
+\ No newline at end of file
++}
++
++/* incomplete lines are bad! */
+
+commit 29f32ac3141c48b22803e5c4127b719917b67d0f8ca8c5248bebfa2a19f7da10
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:48:10 2013 +0100
+
+    change to an incomplete line at end
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -18,5 +18,5 @@
+ int main ()
+ {
+ 	printf("%ld\n", f(15));
+ 	return 0;
+-}
++}
+\ No newline at end of file
+
+commit ccf97b9878189c40a981da50b15713bb80a35755326320ec80900caf22ced46f
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:45:16 2013 +0100
+
+    touch both functions
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,9 +3,9 @@
+-int f(int x)
++long f(long x)
+ {
+ 	int s = 0;
+ 	while (x) {
+ 		x >>= 1;
+ 		s++;
+ 	}
+ 	return s;
+ }
+@@ -17,5 +17,5 @@
+ int main ()
+ {
+-	printf("%d\n", f(15));
++	printf("%ld\n", f(15));
+ 	return 0;
+ }
+
+commit f6434acd34260a6c9f61e96d96bf9a323d330561df5b1ca2631104f82026dfed
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:44:55 2013 +0100
+
+    change f()
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,8 +3,9 @@
+ int f(int x)
+ {
+ 	int s = 0;
+ 	while (x) {
+ 		x >>= 1;
+ 		s++;
+ 	}
++	return s;
+ }
+
+commit 1dd7e9b2b1699324b53b341e728653b913bc192a14dfea168c5b51f2b3d03592
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:44:48 2013 +0100
+
+    initial
+
+diff --git a/a.c b/a.c
+--- /dev/null
++++ b/a.c
+@@ -0,0 +3,8 @@
++int f(int x)
++{
++	int s = 0;
++	while (x) {
++		x >>= 1;
++		s++;
++	}
++}
diff --git a/third_party/git/t/t4211/sha256/expect.multiple-overlapping b/third_party/git/t/t4211/sha256/expect.multiple-overlapping
new file mode 100644
index 0000000000..9015a45a25
--- /dev/null
+++ b/third_party/git/t/t4211/sha256/expect.multiple-overlapping
@@ -0,0 +1,187 @@
+commit 5526ed05c2476b56af8b7be499e8f78bd50f490740733a9ca7e1f55878fa90a9
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:48:43 2013 +0100
+
+    change back to complete line
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -4,19 +4,21 @@
+ long f(long x)
+ {
+ 	int s = 0;
+ 	while (x) {
+ 		x >>= 1;
+ 		s++;
+ 	}
+ 	return s;
+ }
+ 
+ /*
+  * This is only an example!
+  */
+ 
+ int main ()
+ {
+ 	printf("%ld\n", f(15));
+ 	return 0;
+-}
+\ No newline at end of file
++}
++
++/* incomplete lines are bad! */
+
+commit 29f32ac3141c48b22803e5c4127b719917b67d0f8ca8c5248bebfa2a19f7da10
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:48:10 2013 +0100
+
+    change to an incomplete line at end
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -4,19 +4,19 @@
+ long f(long x)
+ {
+ 	int s = 0;
+ 	while (x) {
+ 		x >>= 1;
+ 		s++;
+ 	}
+ 	return s;
+ }
+ 
+ /*
+  * This is only an example!
+  */
+ 
+ int main ()
+ {
+ 	printf("%ld\n", f(15));
+ 	return 0;
+-}
++}
+\ No newline at end of file
+
+commit 5a1b3989063d55e71e7685efa3392f133385b4034bddde530dcb5090d8b8b8ca
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:45:41 2013 +0100
+
+    touch comment
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,19 +3,19 @@
+ long f(long x)
+ {
+ 	int s = 0;
+ 	while (x) {
+ 		x >>= 1;
+ 		s++;
+ 	}
+ 	return s;
+ }
+ 
+ /*
+- * A comment.
++ * This is only an example!
+  */
+ 
+ int main ()
+ {
+ 	printf("%ld\n", f(15));
+ 	return 0;
+ }
+
+commit ccf97b9878189c40a981da50b15713bb80a35755326320ec80900caf22ced46f
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:45:16 2013 +0100
+
+    touch both functions
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,19 +3,19 @@
+-int f(int x)
++long f(long x)
+ {
+ 	int s = 0;
+ 	while (x) {
+ 		x >>= 1;
+ 		s++;
+ 	}
+ 	return s;
+ }
+ 
+ /*
+  * A comment.
+  */
+ 
+ int main ()
+ {
+-	printf("%d\n", f(15));
++	printf("%ld\n", f(15));
+ 	return 0;
+ }
+
+commit f6434acd34260a6c9f61e96d96bf9a323d330561df5b1ca2631104f82026dfed
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:44:55 2013 +0100
+
+    change f()
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,18 +3,19 @@
+ int f(int x)
+ {
+ 	int s = 0;
+ 	while (x) {
+ 		x >>= 1;
+ 		s++;
+ 	}
++	return s;
+ }
+ 
+ /*
+  * A comment.
+  */
+ 
+ int main ()
+ {
+ 	printf("%d\n", f(15));
+ 	return 0;
+ }
+
+commit 1dd7e9b2b1699324b53b341e728653b913bc192a14dfea168c5b51f2b3d03592
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:44:48 2013 +0100
+
+    initial
+
+diff --git a/a.c b/a.c
+--- /dev/null
++++ b/a.c
+@@ -0,0 +3,18 @@
++int f(int x)
++{
++	int s = 0;
++	while (x) {
++		x >>= 1;
++		s++;
++	}
++}
++
++/*
++ * A comment.
++ */
++
++int main ()
++{
++	printf("%d\n", f(15));
++	return 0;
++}
diff --git a/third_party/git/t/t4211/sha256/expect.multiple-superset b/third_party/git/t/t4211/sha256/expect.multiple-superset
new file mode 100644
index 0000000000..9015a45a25
--- /dev/null
+++ b/third_party/git/t/t4211/sha256/expect.multiple-superset
@@ -0,0 +1,187 @@
+commit 5526ed05c2476b56af8b7be499e8f78bd50f490740733a9ca7e1f55878fa90a9
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:48:43 2013 +0100
+
+    change back to complete line
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -4,19 +4,21 @@
+ long f(long x)
+ {
+ 	int s = 0;
+ 	while (x) {
+ 		x >>= 1;
+ 		s++;
+ 	}
+ 	return s;
+ }
+ 
+ /*
+  * This is only an example!
+  */
+ 
+ int main ()
+ {
+ 	printf("%ld\n", f(15));
+ 	return 0;
+-}
+\ No newline at end of file
++}
++
++/* incomplete lines are bad! */
+
+commit 29f32ac3141c48b22803e5c4127b719917b67d0f8ca8c5248bebfa2a19f7da10
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:48:10 2013 +0100
+
+    change to an incomplete line at end
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -4,19 +4,19 @@
+ long f(long x)
+ {
+ 	int s = 0;
+ 	while (x) {
+ 		x >>= 1;
+ 		s++;
+ 	}
+ 	return s;
+ }
+ 
+ /*
+  * This is only an example!
+  */
+ 
+ int main ()
+ {
+ 	printf("%ld\n", f(15));
+ 	return 0;
+-}
++}
+\ No newline at end of file
+
+commit 5a1b3989063d55e71e7685efa3392f133385b4034bddde530dcb5090d8b8b8ca
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:45:41 2013 +0100
+
+    touch comment
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,19 +3,19 @@
+ long f(long x)
+ {
+ 	int s = 0;
+ 	while (x) {
+ 		x >>= 1;
+ 		s++;
+ 	}
+ 	return s;
+ }
+ 
+ /*
+- * A comment.
++ * This is only an example!
+  */
+ 
+ int main ()
+ {
+ 	printf("%ld\n", f(15));
+ 	return 0;
+ }
+
+commit ccf97b9878189c40a981da50b15713bb80a35755326320ec80900caf22ced46f
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:45:16 2013 +0100
+
+    touch both functions
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,19 +3,19 @@
+-int f(int x)
++long f(long x)
+ {
+ 	int s = 0;
+ 	while (x) {
+ 		x >>= 1;
+ 		s++;
+ 	}
+ 	return s;
+ }
+ 
+ /*
+  * A comment.
+  */
+ 
+ int main ()
+ {
+-	printf("%d\n", f(15));
++	printf("%ld\n", f(15));
+ 	return 0;
+ }
+
+commit f6434acd34260a6c9f61e96d96bf9a323d330561df5b1ca2631104f82026dfed
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:44:55 2013 +0100
+
+    change f()
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,18 +3,19 @@
+ int f(int x)
+ {
+ 	int s = 0;
+ 	while (x) {
+ 		x >>= 1;
+ 		s++;
+ 	}
++	return s;
+ }
+ 
+ /*
+  * A comment.
+  */
+ 
+ int main ()
+ {
+ 	printf("%d\n", f(15));
+ 	return 0;
+ }
+
+commit 1dd7e9b2b1699324b53b341e728653b913bc192a14dfea168c5b51f2b3d03592
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:44:48 2013 +0100
+
+    initial
+
+diff --git a/a.c b/a.c
+--- /dev/null
++++ b/a.c
+@@ -0,0 +3,18 @@
++int f(int x)
++{
++	int s = 0;
++	while (x) {
++		x >>= 1;
++		s++;
++	}
++}
++
++/*
++ * A comment.
++ */
++
++int main ()
++{
++	printf("%d\n", f(15));
++	return 0;
++}
diff --git a/third_party/git/t/t4211/sha256/expect.parallel-change-f-to-main b/third_party/git/t/t4211/sha256/expect.parallel-change-f-to-main
new file mode 100644
index 0000000000..e68f8928ea
--- /dev/null
+++ b/third_party/git/t/t4211/sha256/expect.parallel-change-f-to-main
@@ -0,0 +1,160 @@
+commit 98117c2059b76c36995748fb97b02542aef477fe26379e94c18fd70f7790bc67
+Merge: b511694 4f7a581
+Author: Thomas Rast <trast@inf.ethz.ch>
+Date:   Fri Apr 12 16:16:24 2013 +0200
+
+    Merge across the rename
+
+
+commit 4f7a58195a92c400e28a2354328587f1ff14fb77f5cf894536f17ccbc72931b9
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:49:50 2013 +0100
+
+    another simple change
+
+diff --git a/b.c b/b.c
+--- a/b.c
++++ b/b.c
+@@ -4,14 +4,14 @@
+ long f(long x)
+ {
+ 	int s = 0;
+ 	while (x) {
+-		x >>= 1;
++		x /= 2;
+ 		s++;
+ 	}
+ 	return s;
+ }
+ 
+ /*
+  * This is only an example!
+  */
+ 
+
+commit b511694f5337663fbd697622993a5f8e1099eca84be4df313f2b3ee94a098b42
+Author: Thomas Rast <trast@inf.ethz.ch>
+Date:   Fri Apr 12 16:15:57 2013 +0200
+
+    change on another line of history while rename happens
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -4,14 +4,14 @@
+ long f(long x)
+ {
+ 	int s = 0;
+ 	while (x) {
+ 		x >>= 1;
+ 		s++;
+ 	}
+ 	return s;
+ }
+ 
+ /*
+- * This is only an example!
++ * This is only a short example!
+  */
+ 
+
+commit 5a1b3989063d55e71e7685efa3392f133385b4034bddde530dcb5090d8b8b8ca
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:45:41 2013 +0100
+
+    touch comment
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,14 +3,14 @@
+ long f(long x)
+ {
+ 	int s = 0;
+ 	while (x) {
+ 		x >>= 1;
+ 		s++;
+ 	}
+ 	return s;
+ }
+ 
+ /*
+- * A comment.
++ * This is only an example!
+  */
+ 
+
+commit ccf97b9878189c40a981da50b15713bb80a35755326320ec80900caf22ced46f
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:45:16 2013 +0100
+
+    touch both functions
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,14 +3,14 @@
+-int f(int x)
++long f(long x)
+ {
+ 	int s = 0;
+ 	while (x) {
+ 		x >>= 1;
+ 		s++;
+ 	}
+ 	return s;
+ }
+ 
+ /*
+  * A comment.
+  */
+ 
+
+commit f6434acd34260a6c9f61e96d96bf9a323d330561df5b1ca2631104f82026dfed
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:44:55 2013 +0100
+
+    change f()
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,13 +3,14 @@
+ int f(int x)
+ {
+ 	int s = 0;
+ 	while (x) {
+ 		x >>= 1;
+ 		s++;
+ 	}
++	return s;
+ }
+ 
+ /*
+  * A comment.
+  */
+ 
+
+commit 1dd7e9b2b1699324b53b341e728653b913bc192a14dfea168c5b51f2b3d03592
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:44:48 2013 +0100
+
+    initial
+
+diff --git a/a.c b/a.c
+--- /dev/null
++++ b/a.c
+@@ -0,0 +3,13 @@
++int f(int x)
++{
++	int s = 0;
++	while (x) {
++		x >>= 1;
++		s++;
++	}
++}
++
++/*
++ * A comment.
++ */
++
diff --git a/third_party/git/t/t4211/sha256/expect.simple-f b/third_party/git/t/t4211/sha256/expect.simple-f
new file mode 100644
index 0000000000..65508d7c0b
--- /dev/null
+++ b/third_party/git/t/t4211/sha256/expect.simple-f
@@ -0,0 +1,59 @@
+commit ccf97b9878189c40a981da50b15713bb80a35755326320ec80900caf22ced46f
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:45:16 2013 +0100
+
+    touch both functions
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,9 +3,9 @@
+-int f(int x)
++long f(long x)
+ {
+ 	int s = 0;
+ 	while (x) {
+ 		x >>= 1;
+ 		s++;
+ 	}
+ 	return s;
+ }
+
+commit f6434acd34260a6c9f61e96d96bf9a323d330561df5b1ca2631104f82026dfed
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:44:55 2013 +0100
+
+    change f()
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,8 +3,9 @@
+ int f(int x)
+ {
+ 	int s = 0;
+ 	while (x) {
+ 		x >>= 1;
+ 		s++;
+ 	}
++	return s;
+ }
+
+commit 1dd7e9b2b1699324b53b341e728653b913bc192a14dfea168c5b51f2b3d03592
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:44:48 2013 +0100
+
+    initial
+
+diff --git a/a.c b/a.c
+--- /dev/null
++++ b/a.c
+@@ -0,0 +3,8 @@
++int f(int x)
++{
++	int s = 0;
++	while (x) {
++		x >>= 1;
++		s++;
++	}
++}
diff --git a/third_party/git/t/t4211/sha256/expect.simple-f-to-main b/third_party/git/t/t4211/sha256/expect.simple-f-to-main
new file mode 100644
index 0000000000..77b721c196
--- /dev/null
+++ b/third_party/git/t/t4211/sha256/expect.simple-f-to-main
@@ -0,0 +1,100 @@
+commit 5a1b3989063d55e71e7685efa3392f133385b4034bddde530dcb5090d8b8b8ca
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:45:41 2013 +0100
+
+    touch comment
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,14 +3,14 @@
+ long f(long x)
+ {
+ 	int s = 0;
+ 	while (x) {
+ 		x >>= 1;
+ 		s++;
+ 	}
+ 	return s;
+ }
+ 
+ /*
+- * A comment.
++ * This is only an example!
+  */
+ 
+
+commit ccf97b9878189c40a981da50b15713bb80a35755326320ec80900caf22ced46f
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:45:16 2013 +0100
+
+    touch both functions
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,14 +3,14 @@
+-int f(int x)
++long f(long x)
+ {
+ 	int s = 0;
+ 	while (x) {
+ 		x >>= 1;
+ 		s++;
+ 	}
+ 	return s;
+ }
+ 
+ /*
+  * A comment.
+  */
+ 
+
+commit f6434acd34260a6c9f61e96d96bf9a323d330561df5b1ca2631104f82026dfed
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:44:55 2013 +0100
+
+    change f()
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,13 +3,14 @@
+ int f(int x)
+ {
+ 	int s = 0;
+ 	while (x) {
+ 		x >>= 1;
+ 		s++;
+ 	}
++	return s;
+ }
+ 
+ /*
+  * A comment.
+  */
+ 
+
+commit 1dd7e9b2b1699324b53b341e728653b913bc192a14dfea168c5b51f2b3d03592
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:44:48 2013 +0100
+
+    initial
+
+diff --git a/a.c b/a.c
+--- /dev/null
++++ b/a.c
+@@ -0,0 +3,13 @@
++int f(int x)
++{
++	int s = 0;
++	while (x) {
++		x >>= 1;
++		s++;
++	}
++}
++
++/*
++ * A comment.
++ */
++
diff --git a/third_party/git/t/t4211/sha256/expect.simple-main b/third_party/git/t/t4211/sha256/expect.simple-main
new file mode 100644
index 0000000000..d20708c9f9
--- /dev/null
+++ b/third_party/git/t/t4211/sha256/expect.simple-main
@@ -0,0 +1,68 @@
+commit 5526ed05c2476b56af8b7be499e8f78bd50f490740733a9ca7e1f55878fa90a9
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:48:43 2013 +0100
+
+    change back to complete line
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -18,5 +18,5 @@
+ int main ()
+ {
+ 	printf("%ld\n", f(15));
+ 	return 0;
+-}
+\ No newline at end of file
++}
+
+commit 29f32ac3141c48b22803e5c4127b719917b67d0f8ca8c5248bebfa2a19f7da10
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:48:10 2013 +0100
+
+    change to an incomplete line at end
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -18,5 +18,5 @@
+ int main ()
+ {
+ 	printf("%ld\n", f(15));
+ 	return 0;
+-}
++}
+\ No newline at end of file
+
+commit ccf97b9878189c40a981da50b15713bb80a35755326320ec80900caf22ced46f
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:45:16 2013 +0100
+
+    touch both functions
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -17,5 +17,5 @@
+ int main ()
+ {
+-	printf("%d\n", f(15));
++	printf("%ld\n", f(15));
+ 	return 0;
+ }
+
+commit 1dd7e9b2b1699324b53b341e728653b913bc192a14dfea168c5b51f2b3d03592
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:44:48 2013 +0100
+
+    initial
+
+diff --git a/a.c b/a.c
+--- /dev/null
++++ b/a.c
+@@ -0,0 +16,5 @@
++int main ()
++{
++	printf("%d\n", f(15));
++	return 0;
++}
diff --git a/third_party/git/t/t4211/sha256/expect.simple-main-to-end b/third_party/git/t/t4211/sha256/expect.simple-main-to-end
new file mode 100644
index 0000000000..617cdf3481
--- /dev/null
+++ b/third_party/git/t/t4211/sha256/expect.simple-main-to-end
@@ -0,0 +1,70 @@
+commit 5526ed05c2476b56af8b7be499e8f78bd50f490740733a9ca7e1f55878fa90a9
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:48:43 2013 +0100
+
+    change back to complete line
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -18,5 +18,7 @@
+ int main ()
+ {
+ 	printf("%ld\n", f(15));
+ 	return 0;
+-}
+\ No newline at end of file
++}
++
++/* incomplete lines are bad! */
+
+commit 29f32ac3141c48b22803e5c4127b719917b67d0f8ca8c5248bebfa2a19f7da10
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:48:10 2013 +0100
+
+    change to an incomplete line at end
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -18,5 +18,5 @@
+ int main ()
+ {
+ 	printf("%ld\n", f(15));
+ 	return 0;
+-}
++}
+\ No newline at end of file
+
+commit ccf97b9878189c40a981da50b15713bb80a35755326320ec80900caf22ced46f
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:45:16 2013 +0100
+
+    touch both functions
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -17,5 +17,5 @@
+ int main ()
+ {
+-	printf("%d\n", f(15));
++	printf("%ld\n", f(15));
+ 	return 0;
+ }
+
+commit 1dd7e9b2b1699324b53b341e728653b913bc192a14dfea168c5b51f2b3d03592
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:44:48 2013 +0100
+
+    initial
+
+diff --git a/a.c b/a.c
+--- /dev/null
++++ b/a.c
+@@ -0,0 +16,5 @@
++int main ()
++{
++	printf("%d\n", f(15));
++	return 0;
++}
diff --git a/third_party/git/t/t4211/sha256/expect.two-ranges b/third_party/git/t/t4211/sha256/expect.two-ranges
new file mode 100644
index 0000000000..af57c8b997
--- /dev/null
+++ b/third_party/git/t/t4211/sha256/expect.two-ranges
@@ -0,0 +1,102 @@
+commit 5526ed05c2476b56af8b7be499e8f78bd50f490740733a9ca7e1f55878fa90a9
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:48:43 2013 +0100
+
+    change back to complete line
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -18,5 +18,5 @@
+ int main ()
+ {
+ 	printf("%ld\n", f(15));
+ 	return 0;
+-}
+\ No newline at end of file
++}
+
+commit 29f32ac3141c48b22803e5c4127b719917b67d0f8ca8c5248bebfa2a19f7da10
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:48:10 2013 +0100
+
+    change to an incomplete line at end
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -18,5 +18,5 @@
+ int main ()
+ {
+ 	printf("%ld\n", f(15));
+ 	return 0;
+-}
++}
+\ No newline at end of file
+
+commit ccf97b9878189c40a981da50b15713bb80a35755326320ec80900caf22ced46f
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:45:16 2013 +0100
+
+    touch both functions
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,9 +3,9 @@
+-int f(int x)
++long f(long x)
+ {
+ 	int s = 0;
+ 	while (x) {
+ 		x >>= 1;
+ 		s++;
+ 	}
+ 	return s;
+ }
+@@ -17,5 +17,5 @@
+ int main ()
+ {
+-	printf("%d\n", f(15));
++	printf("%ld\n", f(15));
+ 	return 0;
+ }
+
+commit f6434acd34260a6c9f61e96d96bf9a323d330561df5b1ca2631104f82026dfed
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:44:55 2013 +0100
+
+    change f()
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,8 +3,9 @@
+ int f(int x)
+ {
+ 	int s = 0;
+ 	while (x) {
+ 		x >>= 1;
+ 		s++;
+ 	}
++	return s;
+ }
+
+commit 1dd7e9b2b1699324b53b341e728653b913bc192a14dfea168c5b51f2b3d03592
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:44:48 2013 +0100
+
+    initial
+
+diff --git a/a.c b/a.c
+--- /dev/null
++++ b/a.c
+@@ -0,0 +3,8 @@
++int f(int x)
++{
++	int s = 0;
++	while (x) {
++		x >>= 1;
++		s++;
++	}
++}
diff --git a/third_party/git/t/t4211/sha256/expect.vanishes-early b/third_party/git/t/t4211/sha256/expect.vanishes-early
new file mode 100644
index 0000000000..11ec9bdecf
--- /dev/null
+++ b/third_party/git/t/t4211/sha256/expect.vanishes-early
@@ -0,0 +1,39 @@
+commit 5526ed05c2476b56af8b7be499e8f78bd50f490740733a9ca7e1f55878fa90a9
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:48:43 2013 +0100
+
+    change back to complete line
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -22,1 +24,1 @@
+-}
+\ No newline at end of file
++/* incomplete lines are bad! */
+
+commit 29f32ac3141c48b22803e5c4127b719917b67d0f8ca8c5248bebfa2a19f7da10
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:48:10 2013 +0100
+
+    change to an incomplete line at end
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -22,1 +22,1 @@
+-}
++}
+\ No newline at end of file
+
+commit 1dd7e9b2b1699324b53b341e728653b913bc192a14dfea168c5b51f2b3d03592
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:44:48 2013 +0100
+
+    initial
+
+diff --git a/a.c b/a.c
+--- /dev/null
++++ b/a.c
+@@ -0,0 +20,1 @@
++}
diff --git a/third_party/git/t/t4213-log-tabexpand.sh b/third_party/git/t/t4213-log-tabexpand.sh
index 7f90f58c03..53a4af3244 100755
--- a/third_party/git/t/t4213-log-tabexpand.sh
+++ b/third_party/git/t/t4213-log-tabexpand.sh
@@ -36,7 +36,7 @@ count_expand ()
 	esac
 
 	# Prefix the output with the command line arguments, and
-	# replace SP with a dot both in the expecte and actual output
+	# replace SP with a dot both in the expected and actual output
 	# so that test_cmp would show the difference together with the
 	# breakage in a way easier to consume by the debugging user.
 	{
diff --git a/third_party/git/t/t4214-log-graph-octopus.sh b/third_party/git/t/t4214-log-graph-octopus.sh
index dab96c89aa..a080325098 100755
--- a/third_party/git/t/t4214-log-graph-octopus.sh
+++ b/third_party/git/t/t4214-log-graph-octopus.sh
@@ -3,82 +3,86 @@
 test_description='git log --graph of skewed left octopus merge.'
 
 . ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-log-graph.sh
+
+test_cmp_graph () {
+	cat >expect &&
+	lib_test_cmp_graph --color=never --date-order --format=%s "$@"
+}
+
+test_cmp_colored_graph () {
+	lib_test_cmp_colored_graph --date-order --format=%s "$@"
+}
 
 test_expect_success 'set up merge history' '
-	cat >expect.uncolored <<-\EOF &&
+	test_commit initial &&
+	for i in 1 2 3 4 ; do
+		git checkout master -b $i || return $?
+		# Make tag name different from branch name, to avoid
+		# ambiguity error when calling checkout.
+		test_commit $i $i $i tag$i || return $?
+	done &&
+	git checkout 1 -b merge &&
+	test_merge octopus-merge 1 2 3 4 &&
+	test_commit after-merge &&
+	git checkout 1 -b L &&
+	test_commit left &&
+	git checkout 4 -b crossover &&
+	test_commit after-4 &&
+	git checkout initial -b more-L &&
+	test_commit after-initial
+'
+
+test_expect_success 'log --graph with tricky octopus merge, no color' '
+	test_cmp_graph left octopus-merge <<-\EOF
 	* left
-	| *---.   octopus-merge
-	| |\ \ \
-	|/ / / /
+	| *-.   octopus-merge
+	|/|\ \
 	| | | * 4
 	| | * | 3
 	| | |/
-	| * | 2
+	| * / 2
 	| |/
-	* | 1
+	* / 1
 	|/
 	* initial
 	EOF
+'
+
+test_expect_success 'log --graph with tricky octopus merge with colors' '
+	test_config log.graphColors red,green,yellow,blue,magenta,cyan &&
 	cat >expect.colors <<-\EOF &&
 	* left
-	<RED>|<RESET> *<BLUE>-<RESET><BLUE>-<RESET><MAGENTA>-<RESET><MAGENTA>.<RESET>   octopus-merge
-	<RED>|<RESET> <RED>|<RESET><YELLOW>\<RESET> <BLUE>\<RESET> <MAGENTA>\<RESET>
-	<RED>|<RESET><RED>/<RESET> <YELLOW>/<RESET> <BLUE>/<RESET> <MAGENTA>/<RESET>
+	<RED>|<RESET> *<MAGENTA>-<RESET><MAGENTA>.<RESET>   octopus-merge
+	<RED>|<RESET><RED>/<RESET><YELLOW>|<RESET><BLUE>\<RESET> <MAGENTA>\<RESET>
 	<RED>|<RESET> <YELLOW>|<RESET> <BLUE>|<RESET> * 4
 	<RED>|<RESET> <YELLOW>|<RESET> * <MAGENTA>|<RESET> 3
 	<RED>|<RESET> <YELLOW>|<RESET> <MAGENTA>|<RESET><MAGENTA>/<RESET>
-	<RED>|<RESET> * <MAGENTA>|<RESET> 2
+	<RED>|<RESET> * <MAGENTA>/<RESET> 2
 	<RED>|<RESET> <MAGENTA>|<RESET><MAGENTA>/<RESET>
-	* <MAGENTA>|<RESET> 1
+	* <MAGENTA>/<RESET> 1
 	<MAGENTA>|<RESET><MAGENTA>/<RESET>
 	* initial
 	EOF
-	test_commit initial &&
-	for i in 1 2 3 4 ; do
-		git checkout master -b $i || return $?
-		# Make tag name different from branch name, to avoid
-		# ambiguity error when calling checkout.
-		test_commit $i $i $i tag$i || return $?
-	done &&
-	git checkout 1 -b merge &&
-	test_tick &&
-	git merge -m octopus-merge 1 2 3 4 &&
-	git checkout 1 -b L &&
-	test_commit left
-'
-
-test_expect_success 'log --graph with tricky octopus merge with colors' '
-	test_config log.graphColors red,green,yellow,blue,magenta,cyan &&
-	git log --color=always --graph --date-order --pretty=tformat:%s --all >actual.colors.raw &&
-	test_decode_color <actual.colors.raw | sed "s/ *\$//" >actual.colors &&
-	test_cmp expect.colors actual.colors
-'
-
-test_expect_success 'log --graph with tricky octopus merge, no color' '
-	git log --color=never --graph --date-order --pretty=tformat:%s --all >actual.raw &&
-	sed "s/ *\$//" actual.raw >actual &&
-	test_cmp expect.uncolored actual
+	test_cmp_colored_graph left octopus-merge
 '
 
 # Repeat the previous two tests with "normal" octopus merge (i.e.,
 # without the first parent skewing to the "left" branch column).
 
 test_expect_success 'log --graph with normal octopus merge, no color' '
-	cat >expect.uncolored <<-\EOF &&
+	test_cmp_graph octopus-merge <<-\EOF
 	*---.   octopus-merge
 	|\ \ \
 	| | | * 4
 	| | * | 3
 	| | |/
-	| * | 2
+	| * / 2
 	| |/
-	* | 1
+	* / 1
 	|/
 	* initial
 	EOF
-	git log --color=never --graph --date-order --pretty=tformat:%s merge >actual.raw &&
-	sed "s/ *\$//" actual.raw >actual &&
-	test_cmp expect.uncolored actual
 '
 
 test_expect_success 'log --graph with normal octopus merge with colors' '
@@ -88,15 +92,256 @@ test_expect_success 'log --graph with normal octopus merge with colors' '
 	<RED>|<RESET> <GREEN>|<RESET> <YELLOW>|<RESET> * 4
 	<RED>|<RESET> <GREEN>|<RESET> * <BLUE>|<RESET> 3
 	<RED>|<RESET> <GREEN>|<RESET> <BLUE>|<RESET><BLUE>/<RESET>
-	<RED>|<RESET> * <BLUE>|<RESET> 2
+	<RED>|<RESET> * <BLUE>/<RESET> 2
 	<RED>|<RESET> <BLUE>|<RESET><BLUE>/<RESET>
-	* <BLUE>|<RESET> 1
+	* <BLUE>/<RESET> 1
 	<BLUE>|<RESET><BLUE>/<RESET>
 	* initial
 	EOF
 	test_config log.graphColors red,green,yellow,blue,magenta,cyan &&
-	git log --color=always --graph --date-order --pretty=tformat:%s merge >actual.colors.raw &&
-	test_decode_color <actual.colors.raw | sed "s/ *\$//" >actual.colors &&
-	test_cmp expect.colors actual.colors
+	test_cmp_colored_graph octopus-merge
 '
+
+test_expect_success 'log --graph with normal octopus merge and child, no color' '
+	test_cmp_graph after-merge <<-\EOF
+	* after-merge
+	*---.   octopus-merge
+	|\ \ \
+	| | | * 4
+	| | * | 3
+	| | |/
+	| * / 2
+	| |/
+	* / 1
+	|/
+	* initial
+	EOF
+'
+
+test_expect_success 'log --graph with normal octopus and child merge with colors' '
+	cat >expect.colors <<-\EOF &&
+	* after-merge
+	*<BLUE>-<RESET><BLUE>-<RESET><MAGENTA>-<RESET><MAGENTA>.<RESET>   octopus-merge
+	<GREEN>|<RESET><YELLOW>\<RESET> <BLUE>\<RESET> <MAGENTA>\<RESET>
+	<GREEN>|<RESET> <YELLOW>|<RESET> <BLUE>|<RESET> * 4
+	<GREEN>|<RESET> <YELLOW>|<RESET> * <MAGENTA>|<RESET> 3
+	<GREEN>|<RESET> <YELLOW>|<RESET> <MAGENTA>|<RESET><MAGENTA>/<RESET>
+	<GREEN>|<RESET> * <MAGENTA>/<RESET> 2
+	<GREEN>|<RESET> <MAGENTA>|<RESET><MAGENTA>/<RESET>
+	* <MAGENTA>/<RESET> 1
+	<MAGENTA>|<RESET><MAGENTA>/<RESET>
+	* initial
+	EOF
+	test_config log.graphColors red,green,yellow,blue,magenta,cyan &&
+	test_cmp_colored_graph after-merge
+'
+
+test_expect_success 'log --graph with tricky octopus merge and its child, no color' '
+	test_cmp_graph left after-merge <<-\EOF
+	* left
+	| * after-merge
+	| *-.   octopus-merge
+	|/|\ \
+	| | | * 4
+	| | * | 3
+	| | |/
+	| * / 2
+	| |/
+	* / 1
+	|/
+	* initial
+	EOF
+'
+
+test_expect_success 'log --graph with tricky octopus merge and its child with colors' '
+	test_config log.graphColors red,green,yellow,blue,magenta,cyan &&
+	cat >expect.colors <<-\EOF &&
+	* left
+	<RED>|<RESET> * after-merge
+	<RED>|<RESET> *<CYAN>-<RESET><CYAN>.<RESET>   octopus-merge
+	<RED>|<RESET><RED>/<RESET><BLUE>|<RESET><MAGENTA>\<RESET> <CYAN>\<RESET>
+	<RED>|<RESET> <BLUE>|<RESET> <MAGENTA>|<RESET> * 4
+	<RED>|<RESET> <BLUE>|<RESET> * <CYAN>|<RESET> 3
+	<RED>|<RESET> <BLUE>|<RESET> <CYAN>|<RESET><CYAN>/<RESET>
+	<RED>|<RESET> * <CYAN>/<RESET> 2
+	<RED>|<RESET> <CYAN>|<RESET><CYAN>/<RESET>
+	* <CYAN>/<RESET> 1
+	<CYAN>|<RESET><CYAN>/<RESET>
+	* initial
+	EOF
+	test_cmp_colored_graph left after-merge
+'
+
+test_expect_success 'log --graph with crossover in octopus merge, no color' '
+	test_cmp_graph after-4 octopus-merge <<-\EOF
+	* after-4
+	| *---.   octopus-merge
+	| |\ \ \
+	| |_|_|/
+	|/| | |
+	* | | | 4
+	| | | * 3
+	| |_|/
+	|/| |
+	| | * 2
+	| |/
+	|/|
+	| * 1
+	|/
+	* initial
+	EOF
+'
+
+test_expect_success 'log --graph with crossover in octopus merge with colors' '
+	test_config log.graphColors red,green,yellow,blue,magenta,cyan &&
+	cat >expect.colors <<-\EOF &&
+	* after-4
+	<RED>|<RESET> *<BLUE>-<RESET><BLUE>-<RESET><RED>-<RESET><RED>.<RESET>   octopus-merge
+	<RED>|<RESET> <GREEN>|<RESET><YELLOW>\<RESET> <BLUE>\<RESET> <RED>\<RESET>
+	<RED>|<RESET> <GREEN>|<RESET><RED>_<RESET><YELLOW>|<RESET><RED>_<RESET><BLUE>|<RESET><RED>/<RESET>
+	<RED>|<RESET><RED>/<RESET><GREEN>|<RESET> <YELLOW>|<RESET> <BLUE>|<RESET>
+	* <GREEN>|<RESET> <YELLOW>|<RESET> <BLUE>|<RESET> 4
+	<MAGENTA>|<RESET> <GREEN>|<RESET> <YELLOW>|<RESET> * 3
+	<MAGENTA>|<RESET> <GREEN>|<RESET><MAGENTA>_<RESET><YELLOW>|<RESET><MAGENTA>/<RESET>
+	<MAGENTA>|<RESET><MAGENTA>/<RESET><GREEN>|<RESET> <YELLOW>|<RESET>
+	<MAGENTA>|<RESET> <GREEN>|<RESET> * 2
+	<MAGENTA>|<RESET> <GREEN>|<RESET><MAGENTA>/<RESET>
+	<MAGENTA>|<RESET><MAGENTA>/<RESET><GREEN>|<RESET>
+	<MAGENTA>|<RESET> * 1
+	<MAGENTA>|<RESET><MAGENTA>/<RESET>
+	* initial
+	EOF
+	test_cmp_colored_graph after-4 octopus-merge
+'
+
+test_expect_success 'log --graph with crossover in octopus merge and its child, no color' '
+	test_cmp_graph after-4 after-merge <<-\EOF
+	* after-4
+	| * after-merge
+	| *---.   octopus-merge
+	| |\ \ \
+	| |_|_|/
+	|/| | |
+	* | | | 4
+	| | | * 3
+	| |_|/
+	|/| |
+	| | * 2
+	| |/
+	|/|
+	| * 1
+	|/
+	* initial
+	EOF
+'
+
+test_expect_success 'log --graph with crossover in octopus merge and its child with colors' '
+	test_config log.graphColors red,green,yellow,blue,magenta,cyan &&
+	cat >expect.colors <<-\EOF &&
+	* after-4
+	<RED>|<RESET> * after-merge
+	<RED>|<RESET> *<MAGENTA>-<RESET><MAGENTA>-<RESET><RED>-<RESET><RED>.<RESET>   octopus-merge
+	<RED>|<RESET> <YELLOW>|<RESET><BLUE>\<RESET> <MAGENTA>\<RESET> <RED>\<RESET>
+	<RED>|<RESET> <YELLOW>|<RESET><RED>_<RESET><BLUE>|<RESET><RED>_<RESET><MAGENTA>|<RESET><RED>/<RESET>
+	<RED>|<RESET><RED>/<RESET><YELLOW>|<RESET> <BLUE>|<RESET> <MAGENTA>|<RESET>
+	* <YELLOW>|<RESET> <BLUE>|<RESET> <MAGENTA>|<RESET> 4
+	<CYAN>|<RESET> <YELLOW>|<RESET> <BLUE>|<RESET> * 3
+	<CYAN>|<RESET> <YELLOW>|<RESET><CYAN>_<RESET><BLUE>|<RESET><CYAN>/<RESET>
+	<CYAN>|<RESET><CYAN>/<RESET><YELLOW>|<RESET> <BLUE>|<RESET>
+	<CYAN>|<RESET> <YELLOW>|<RESET> * 2
+	<CYAN>|<RESET> <YELLOW>|<RESET><CYAN>/<RESET>
+	<CYAN>|<RESET><CYAN>/<RESET><YELLOW>|<RESET>
+	<CYAN>|<RESET> * 1
+	<CYAN>|<RESET><CYAN>/<RESET>
+	* initial
+	EOF
+	test_cmp_colored_graph after-4 after-merge
+'
+
+test_expect_success 'log --graph with unrelated commit and octopus tip, no color' '
+	test_cmp_graph after-initial octopus-merge <<-\EOF
+	* after-initial
+	| *---.   octopus-merge
+	| |\ \ \
+	| | | | * 4
+	| |_|_|/
+	|/| | |
+	| | | * 3
+	| |_|/
+	|/| |
+	| | * 2
+	| |/
+	|/|
+	| * 1
+	|/
+	* initial
+	EOF
+'
+
+test_expect_success 'log --graph with unrelated commit and octopus tip with colors' '
+	test_config log.graphColors red,green,yellow,blue,magenta,cyan &&
+	cat >expect.colors <<-\EOF &&
+	* after-initial
+	<RED>|<RESET> *<BLUE>-<RESET><BLUE>-<RESET><MAGENTA>-<RESET><MAGENTA>.<RESET>   octopus-merge
+	<RED>|<RESET> <GREEN>|<RESET><YELLOW>\<RESET> <BLUE>\<RESET> <MAGENTA>\<RESET>
+	<RED>|<RESET> <GREEN>|<RESET> <YELLOW>|<RESET> <BLUE>|<RESET> * 4
+	<RED>|<RESET> <GREEN>|<RESET><RED>_<RESET><YELLOW>|<RESET><RED>_<RESET><BLUE>|<RESET><RED>/<RESET>
+	<RED>|<RESET><RED>/<RESET><GREEN>|<RESET> <YELLOW>|<RESET> <BLUE>|<RESET>
+	<RED>|<RESET> <GREEN>|<RESET> <YELLOW>|<RESET> * 3
+	<RED>|<RESET> <GREEN>|<RESET><RED>_<RESET><YELLOW>|<RESET><RED>/<RESET>
+	<RED>|<RESET><RED>/<RESET><GREEN>|<RESET> <YELLOW>|<RESET>
+	<RED>|<RESET> <GREEN>|<RESET> * 2
+	<RED>|<RESET> <GREEN>|<RESET><RED>/<RESET>
+	<RED>|<RESET><RED>/<RESET><GREEN>|<RESET>
+	<RED>|<RESET> * 1
+	<RED>|<RESET><RED>/<RESET>
+	* initial
+	EOF
+	test_cmp_colored_graph after-initial octopus-merge
+'
+
+test_expect_success 'log --graph with unrelated commit and octopus child, no color' '
+	test_cmp_graph after-initial after-merge <<-\EOF
+	* after-initial
+	| * after-merge
+	| *---.   octopus-merge
+	| |\ \ \
+	| | | | * 4
+	| |_|_|/
+	|/| | |
+	| | | * 3
+	| |_|/
+	|/| |
+	| | * 2
+	| |/
+	|/|
+	| * 1
+	|/
+	* initial
+	EOF
+'
+
+test_expect_success 'log --graph with unrelated commit and octopus child with colors' '
+	test_config log.graphColors red,green,yellow,blue,magenta,cyan &&
+	cat >expect.colors <<-\EOF &&
+	* after-initial
+	<RED>|<RESET> * after-merge
+	<RED>|<RESET> *<MAGENTA>-<RESET><MAGENTA>-<RESET><CYAN>-<RESET><CYAN>.<RESET>   octopus-merge
+	<RED>|<RESET> <YELLOW>|<RESET><BLUE>\<RESET> <MAGENTA>\<RESET> <CYAN>\<RESET>
+	<RED>|<RESET> <YELLOW>|<RESET> <BLUE>|<RESET> <MAGENTA>|<RESET> * 4
+	<RED>|<RESET> <YELLOW>|<RESET><RED>_<RESET><BLUE>|<RESET><RED>_<RESET><MAGENTA>|<RESET><RED>/<RESET>
+	<RED>|<RESET><RED>/<RESET><YELLOW>|<RESET> <BLUE>|<RESET> <MAGENTA>|<RESET>
+	<RED>|<RESET> <YELLOW>|<RESET> <BLUE>|<RESET> * 3
+	<RED>|<RESET> <YELLOW>|<RESET><RED>_<RESET><BLUE>|<RESET><RED>/<RESET>
+	<RED>|<RESET><RED>/<RESET><YELLOW>|<RESET> <BLUE>|<RESET>
+	<RED>|<RESET> <YELLOW>|<RESET> * 2
+	<RED>|<RESET> <YELLOW>|<RESET><RED>/<RESET>
+	<RED>|<RESET><RED>/<RESET><YELLOW>|<RESET>
+	<RED>|<RESET> * 1
+	<RED>|<RESET><RED>/<RESET>
+	* initial
+	EOF
+	test_cmp_colored_graph after-initial after-merge
+'
+
 test_done
diff --git a/third_party/git/t/t4215-log-skewed-merges.sh b/third_party/git/t/t4215-log-skewed-merges.sh
new file mode 100755
index 0000000000..28d0779a8c
--- /dev/null
+++ b/third_party/git/t/t4215-log-skewed-merges.sh
@@ -0,0 +1,373 @@
+#!/bin/sh
+
+test_description='git log --graph of skewed merges'
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-log-graph.sh
+
+check_graph () {
+	cat >expect &&
+	lib_test_cmp_graph --format=%s "$@"
+}
+
+test_expect_success 'log --graph with merge fusing with its left and right neighbors' '
+	git checkout --orphan _p &&
+	test_commit A &&
+	test_commit B &&
+	git checkout -b _q @^ && test_commit C &&
+	git checkout -b _r @^ && test_commit D &&
+	git checkout _p && git merge --no-ff _q _r -m E &&
+	git checkout _r && test_commit F &&
+	git checkout _p && git merge --no-ff _r -m G &&
+	git checkout @^^ && git merge --no-ff _p -m H &&
+
+	check_graph <<-\EOF
+	*   H
+	|\
+	| *   G
+	| |\
+	| | * F
+	| * | E
+	|/|\|
+	| | * D
+	| * | C
+	| |/
+	* / B
+	|/
+	* A
+	EOF
+'
+
+test_expect_success 'log --graph with left-skewed merge' '
+	git checkout --orphan 0_p && test_commit 0_A &&
+	git checkout -b 0_q 0_p && test_commit 0_B &&
+	git checkout -b 0_r 0_p &&
+	test_commit 0_C &&
+	test_commit 0_D &&
+	git checkout -b 0_s 0_p && test_commit 0_E &&
+	git checkout -b 0_t 0_p && git merge --no-ff 0_r^ 0_s -m 0_F &&
+	git checkout 0_p && git merge --no-ff 0_s -m 0_G &&
+	git checkout @^ && git merge --no-ff 0_q 0_r 0_t 0_p -m 0_H &&
+
+	check_graph <<-\EOF
+	*-----.   0_H
+	|\ \ \ \
+	| | | | * 0_G
+	| |_|_|/|
+	|/| | | |
+	| | | * | 0_F
+	| |_|/|\|
+	|/| | | |
+	| | | | * 0_E
+	| |_|_|/
+	|/| | |
+	| | * | 0_D
+	| | |/
+	| | * 0_C
+	| |/
+	|/|
+	| * 0_B
+	|/
+	* 0_A
+	EOF
+'
+
+test_expect_success 'log --graph with nested left-skewed merge' '
+	git checkout --orphan 1_p &&
+	test_commit 1_A &&
+	test_commit 1_B &&
+	test_commit 1_C &&
+	git checkout -b 1_q @^ && test_commit 1_D &&
+	git checkout 1_p && git merge --no-ff 1_q -m 1_E &&
+	git checkout -b 1_r @~3 && test_commit 1_F &&
+	git checkout 1_p && git merge --no-ff 1_r -m 1_G &&
+	git checkout @^^ && git merge --no-ff 1_p -m 1_H &&
+
+	check_graph <<-\EOF
+	*   1_H
+	|\
+	| *   1_G
+	| |\
+	| | * 1_F
+	| * | 1_E
+	|/| |
+	| * | 1_D
+	* | | 1_C
+	|/ /
+	* / 1_B
+	|/
+	* 1_A
+	EOF
+'
+
+test_expect_success 'log --graph with nested left-skewed merge following normal merge' '
+	git checkout --orphan 2_p &&
+	test_commit 2_A &&
+	test_commit 2_B &&
+	test_commit 2_C &&
+	git checkout -b 2_q @^^ &&
+	test_commit 2_D &&
+	test_commit 2_E &&
+	git checkout -b 2_r @^ && test_commit 2_F &&
+	git checkout 2_q &&
+	git merge --no-ff 2_r -m 2_G &&
+	git merge --no-ff 2_p^ -m 2_H &&
+	git checkout -b 2_s @^^ && git merge --no-ff 2_q -m 2_J &&
+	git checkout 2_p && git merge --no-ff 2_s -m 2_K &&
+
+	check_graph <<-\EOF
+	*   2_K
+	|\
+	| *   2_J
+	| |\
+	| | *   2_H
+	| | |\
+	| | * | 2_G
+	| |/| |
+	| | * | 2_F
+	| * | | 2_E
+	| |/ /
+	| * | 2_D
+	* | | 2_C
+	| |/
+	|/|
+	* | 2_B
+	|/
+	* 2_A
+	EOF
+'
+
+test_expect_success 'log --graph with nested right-skewed merge following left-skewed merge' '
+	git checkout --orphan 3_p &&
+	test_commit 3_A &&
+	git checkout -b 3_q &&
+	test_commit 3_B &&
+	test_commit 3_C &&
+	git checkout -b 3_r @^ &&
+	test_commit 3_D &&
+	git checkout 3_q && git merge --no-ff 3_r -m 3_E &&
+	git checkout 3_p && git merge --no-ff 3_q -m 3_F &&
+	git checkout 3_r && test_commit 3_G &&
+	git checkout 3_p && git merge --no-ff 3_r -m 3_H &&
+	git checkout @^^ && git merge --no-ff 3_p -m 3_J &&
+
+	check_graph <<-\EOF
+	*   3_J
+	|\
+	| *   3_H
+	| |\
+	| | * 3_G
+	| * | 3_F
+	|/| |
+	| * | 3_E
+	| |\|
+	| | * 3_D
+	| * | 3_C
+	| |/
+	| * 3_B
+	|/
+	* 3_A
+	EOF
+'
+
+test_expect_success 'log --graph with right-skewed merge following a left-skewed one' '
+	git checkout --orphan 4_p &&
+	test_commit 4_A &&
+	test_commit 4_B &&
+	test_commit 4_C &&
+	git checkout -b 4_q @^^ && test_commit 4_D &&
+	git checkout -b 4_r 4_p^ && git merge --no-ff 4_q -m 4_E &&
+	git checkout -b 4_s 4_p^^ &&
+	git merge --no-ff 4_r -m 4_F &&
+	git merge --no-ff 4_p -m 4_G &&
+	git checkout @^^ && git merge --no-ff 4_s -m 4_H &&
+
+	check_graph --date-order <<-\EOF
+	*   4_H
+	|\
+	| *   4_G
+	| |\
+	| * | 4_F
+	|/| |
+	| * |   4_E
+	| |\ \
+	| | * | 4_D
+	| |/ /
+	|/| |
+	| | * 4_C
+	| |/
+	| * 4_B
+	|/
+	* 4_A
+	EOF
+'
+
+test_expect_success 'log --graph with octopus merge with column joining its penultimate parent' '
+	git checkout --orphan 5_p &&
+	test_commit 5_A &&
+	git branch 5_q &&
+	git branch 5_r &&
+	test_commit 5_B &&
+	git checkout 5_q && test_commit 5_C &&
+	git checkout 5_r && test_commit 5_D &&
+	git checkout 5_p &&
+	git merge --no-ff 5_q 5_r -m 5_E &&
+	git checkout 5_q && test_commit 5_F &&
+	git checkout -b 5_s 5_p^ &&
+	git merge --no-ff 5_p 5_q -m 5_G &&
+	git checkout 5_r &&
+	git merge --no-ff 5_s -m 5_H &&
+
+	check_graph <<-\EOF
+	*   5_H
+	|\
+	| *-.   5_G
+	| |\ \
+	| | | * 5_F
+	| | * |   5_E
+	| |/|\ \
+	| |_|/ /
+	|/| | /
+	| | |/
+	* | | 5_D
+	| | * 5_C
+	| |/
+	|/|
+	| * 5_B
+	|/
+	* 5_A
+	EOF
+'
+
+test_expect_success 'log --graph with multiple tips' '
+	git checkout --orphan 6_1 &&
+	test_commit 6_A &&
+	git branch 6_2 &&
+	git branch 6_4 &&
+	test_commit 6_B &&
+	git branch 6_3 &&
+	test_commit 6_C &&
+	git checkout 6_2 && test_commit 6_D &&
+	git checkout 6_3 && test_commit 6_E &&
+	git checkout -b 6_5 6_1 &&
+	git merge --no-ff 6_2 -m 6_F &&
+	git checkout 6_4 && test_commit 6_G &&
+	git checkout 6_3 &&
+	git merge --no-ff 6_4 -m 6_H &&
+	git checkout 6_1 &&
+	git merge --no-ff 6_2 -m 6_I &&
+
+	check_graph 6_1 6_3 6_5 <<-\EOF
+	*   6_I
+	|\
+	| | *   6_H
+	| | |\
+	| | | * 6_G
+	| | * | 6_E
+	| | | | * 6_F
+	| |_|_|/|
+	|/| | |/
+	| | |/|
+	| |/| |
+	| * | | 6_D
+	| | |/
+	| |/|
+	* | | 6_C
+	| |/
+	|/|
+	* | 6_B
+	|/
+	* 6_A
+	EOF
+'
+
+test_expect_success 'log --graph with multiple tips and colors' '
+	test_config log.graphColors red,green,yellow,blue,magenta,cyan &&
+	cat >expect.colors <<-\EOF &&
+	*   6_I
+	<RED>|<RESET><GREEN>\<RESET>
+	<RED>|<RESET> <GREEN>|<RESET> *   6_H
+	<RED>|<RESET> <GREEN>|<RESET> <YELLOW>|<RESET><BLUE>\<RESET>
+	<RED>|<RESET> <GREEN>|<RESET> <YELLOW>|<RESET> * 6_G
+	<RED>|<RESET> <GREEN>|<RESET> <YELLOW>|<RESET> <BLUE>|<RESET> * 6_F
+	<RED>|<RESET> <GREEN>|<RESET><RED>_<RESET><YELLOW>|<RESET><RED>_<RESET><BLUE>|<RESET><RED>/<RESET><GREEN>|<RESET>
+	<RED>|<RESET><RED>/<RESET><GREEN>|<RESET> <YELLOW>|<RESET> <BLUE>|<RESET><GREEN>/<RESET>
+	<RED>|<RESET> <GREEN>|<RESET> <YELLOW>|<RESET><GREEN>/<RESET><BLUE>|<RESET>
+	<RED>|<RESET> <GREEN>|<RESET><GREEN>/<RESET><YELLOW>|<RESET> <BLUE>|<RESET>
+	<RED>|<RESET> <GREEN>|<RESET> * <BLUE>|<RESET> 6_E
+	<RED>|<RESET> * <CYAN>|<RESET> <BLUE>|<RESET> 6_D
+	<RED>|<RESET> <BLUE>|<RESET> <CYAN>|<RESET><BLUE>/<RESET>
+	<RED>|<RESET> <BLUE>|<RESET><BLUE>/<RESET><CYAN>|<RESET>
+	* <BLUE>|<RESET> <CYAN>|<RESET> 6_C
+	<CYAN>|<RESET> <BLUE>|<RESET><CYAN>/<RESET>
+	<CYAN>|<RESET><CYAN>/<RESET><BLUE>|<RESET>
+	* <BLUE>|<RESET> 6_B
+	<BLUE>|<RESET><BLUE>/<RESET>
+	* 6_A
+	EOF
+	lib_test_cmp_colored_graph --date-order --pretty=tformat:%s 6_1 6_3 6_5
+'
+
+test_expect_success 'log --graph with multiple tips' '
+	git checkout --orphan 7_1 &&
+	test_commit 7_A &&
+	test_commit 7_B &&
+	test_commit 7_C &&
+	git checkout -b 7_2 7_1~2 &&
+	test_commit 7_D &&
+	test_commit 7_E &&
+	git checkout -b 7_3 7_1~1 &&
+	test_commit 7_F &&
+	test_commit 7_G &&
+	git checkout -b 7_4 7_2~1 &&
+	test_commit 7_H &&
+	git checkout -b 7_5 7_1~2 &&
+	test_commit 7_I &&
+	git checkout -b 7_6 7_3~1 &&
+	test_commit 7_J &&
+	git checkout -b M_1 7_1 &&
+	git merge --no-ff 7_2 -m 7_M1 &&
+	git checkout -b M_3 7_3 &&
+	git merge --no-ff 7_4 -m 7_M2 &&
+	git checkout -b M_5 7_5 &&
+	git merge --no-ff 7_6 -m 7_M3 &&
+	git checkout -b M_7 7_1 &&
+	git merge --no-ff 7_2 7_3 -m 7_M4 &&
+
+	check_graph M_1 M_3 M_5 M_7 <<-\EOF
+	*   7_M1
+	|\
+	| | *   7_M2
+	| | |\
+	| | | * 7_H
+	| | | | *   7_M3
+	| | | | |\
+	| | | | | * 7_J
+	| | | | * | 7_I
+	| | | | | | *   7_M4
+	| |_|_|_|_|/|\
+	|/| | | | |/ /
+	| | |_|_|/| /
+	| |/| | | |/
+	| | | |_|/|
+	| | |/| | |
+	| | * | | | 7_G
+	| | | |_|/
+	| | |/| |
+	| | * | | 7_F
+	| * | | | 7_E
+	| | |/ /
+	| |/| |
+	| * | | 7_D
+	| | |/
+	| |/|
+	* | | 7_C
+	| |/
+	|/|
+	* | 7_B
+	|/
+	* 7_A
+	EOF
+'
+
+test_done
diff --git a/third_party/git/t/t4216-log-bloom.sh b/third_party/git/t/t4216-log-bloom.sh
new file mode 100755
index 0000000000..d11040ce41
--- /dev/null
+++ b/third_party/git/t/t4216-log-bloom.sh
@@ -0,0 +1,405 @@
+#!/bin/sh
+
+test_description='git log for a path with Bloom filters'
+. ./test-lib.sh
+
+GIT_TEST_COMMIT_GRAPH=0
+GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS=0
+
+test_expect_success 'setup test - repo, commits, commit graph, log outputs' '
+	git init &&
+	mkdir A A/B A/B/C &&
+	test_commit c1 A/file1 &&
+	test_commit c2 A/B/file2 &&
+	test_commit c3 A/B/C/file3 &&
+	test_commit c4 A/file1 &&
+	test_commit c5 A/B/file2 &&
+	test_commit c6 A/B/C/file3 &&
+	test_commit c7 A/file1 &&
+	test_commit c8 A/B/file2 &&
+	test_commit c9 A/B/C/file3 &&
+	test_commit c10 file_to_be_deleted &&
+	git checkout -b side HEAD~4 &&
+	test_commit side-1 file4 &&
+	git checkout master &&
+	git merge side &&
+	test_commit c11 file5 &&
+	mv file5 file5_renamed &&
+	git add file5_renamed &&
+	git commit -m "rename" &&
+	rm file_to_be_deleted &&
+	git add . &&
+	git commit -m "file removed" &&
+	git commit --allow-empty -m "empty" &&
+	git commit-graph write --reachable --changed-paths &&
+
+	test_oid_cache <<-EOF
+	oid_version sha1:1
+	oid_version sha256:2
+	EOF
+'
+
+graph_read_expect () {
+	NUM_CHUNKS=5
+	cat >expect <<- EOF
+	header: 43475048 1 $(test_oid oid_version) $NUM_CHUNKS 0
+	num_commits: $1
+	chunks: oid_fanout oid_lookup commit_metadata bloom_indexes bloom_data
+	EOF
+	test-tool read-graph >actual &&
+	test_cmp expect actual
+}
+
+test_expect_success 'commit-graph write wrote out the bloom chunks' '
+	graph_read_expect 16
+'
+
+# Turn off any inherited trace2 settings for this test.
+sane_unset GIT_TRACE2 GIT_TRACE2_PERF GIT_TRACE2_EVENT
+sane_unset GIT_TRACE2_PERF_BRIEF
+sane_unset GIT_TRACE2_CONFIG_PARAMS
+
+setup () {
+	rm -f "$TRASH_DIRECTORY/trace.perf" &&
+	git -c core.commitGraph=false log --pretty="format:%s" $1 >log_wo_bloom &&
+	GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.perf" git -c core.commitGraph=true log --pretty="format:%s" $1 >log_w_bloom
+}
+
+test_bloom_filters_used () {
+	log_args=$1
+	bloom_trace_prefix="statistics:{\"filter_not_present\":${2:-0},\"maybe\""
+	setup "$log_args" &&
+	grep -q "$bloom_trace_prefix" "$TRASH_DIRECTORY/trace.perf" &&
+	test_cmp log_wo_bloom log_w_bloom &&
+    test_path_is_file "$TRASH_DIRECTORY/trace.perf"
+}
+
+test_bloom_filters_not_used () {
+	log_args=$1
+	setup "$log_args" &&
+	! grep -q "statistics:{\"filter_not_present\":" "$TRASH_DIRECTORY/trace.perf" &&
+	test_cmp log_wo_bloom log_w_bloom
+}
+
+for path in A A/B A/B/C A/file1 A/B/file2 A/B/C/file3 file4 file5 file5_renamed file_to_be_deleted
+do
+	for option in "" \
+	      "--all" \
+		      "--full-history" \
+		      "--full-history --simplify-merges" \
+		      "--simplify-merges" \
+		      "--simplify-by-decoration" \
+		      "--follow" \
+		      "--first-parent" \
+		      "--topo-order" \
+		      "--date-order" \
+		      "--author-date-order" \
+		      "--ancestry-path side..master"
+	do
+		test_expect_success "git log option: $option for path: $path" '
+			test_bloom_filters_used "$option -- $path" &&
+			test_config commitgraph.readChangedPaths false &&
+			test_bloom_filters_not_used "$option -- $path"
+		'
+	done
+done
+
+test_expect_success 'git log -- folder works with and without the trailing slash' '
+	test_bloom_filters_used "-- A" &&
+	test_bloom_filters_used "-- A/"
+'
+
+test_expect_success 'git log for path that does not exist. ' '
+	test_bloom_filters_used "-- path_does_not_exist"
+'
+
+test_expect_success 'git log with --walk-reflogs does not use Bloom filters' '
+	test_bloom_filters_not_used "--walk-reflogs -- A"
+'
+
+test_expect_success 'git log -- multiple path specs does not use Bloom filters' '
+	test_bloom_filters_not_used "-- file4 A/file1"
+'
+
+test_expect_success 'git log -- "." pathspec at root does not use Bloom filters' '
+	test_bloom_filters_not_used "-- ."
+'
+
+test_expect_success 'git log with wildcard that resolves to a single path uses Bloom filters' '
+	test_bloom_filters_used "-- *4" &&
+	test_bloom_filters_used "-- *renamed"
+'
+
+test_expect_success 'git log with wildcard that resolves to a multiple paths does not uses Bloom filters' '
+	test_bloom_filters_not_used "-- *" &&
+	test_bloom_filters_not_used "-- file*"
+'
+
+test_expect_success 'setup - add commit-graph to the chain without Bloom filters' '
+	test_commit c14 A/anotherFile2 &&
+	test_commit c15 A/B/anotherFile2 &&
+	test_commit c16 A/B/C/anotherFile2 &&
+	git commit-graph write --reachable --split --no-changed-paths &&
+	test_line_count = 2 .git/objects/info/commit-graphs/commit-graph-chain
+'
+
+test_expect_success 'use Bloom filters even if the latest graph does not have Bloom filters' '
+	# Ensure that the number of empty filters is equal to the number of
+	# filters in the latest graph layer to prove that they are loaded (and
+	# ignored).
+	test_bloom_filters_used "-- A/B" 3
+'
+
+test_expect_success 'setup - add commit-graph to the chain with Bloom filters' '
+	test_commit c17 A/anotherFile3 &&
+	git commit-graph write --reachable --changed-paths --split &&
+	test_line_count = 3 .git/objects/info/commit-graphs/commit-graph-chain
+'
+
+test_bloom_filters_used_when_some_filters_are_missing () {
+	log_args=$1
+	bloom_trace_prefix="statistics:{\"filter_not_present\":3,\"maybe\":6,\"definitely_not\":9"
+	setup "$log_args" &&
+	grep -q "$bloom_trace_prefix" "$TRASH_DIRECTORY/trace.perf" &&
+	test_cmp log_wo_bloom log_w_bloom
+}
+
+test_expect_success 'Use Bloom filters if they exist in the latest but not all commit graphs in the chain.' '
+	test_bloom_filters_used_when_some_filters_are_missing "-- A/B"
+'
+
+test_expect_success 'persist filter settings' '
+	test_when_finished rm -rf .git/objects/info/commit-graph* &&
+	rm -rf .git/objects/info/commit-graph* &&
+	GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \
+		GIT_TRACE2_EVENT_NESTING=5 \
+		GIT_TEST_BLOOM_SETTINGS_NUM_HASHES=9 \
+		GIT_TEST_BLOOM_SETTINGS_BITS_PER_ENTRY=15 \
+		git commit-graph write --reachable --changed-paths &&
+	grep "{\"hash_version\":1,\"num_hashes\":9,\"bits_per_entry\":15,\"max_changed_paths\":512" trace2.txt &&
+	GIT_TRACE2_EVENT="$(pwd)/trace2-auto.txt" \
+		GIT_TRACE2_EVENT_NESTING=5 \
+		git commit-graph write --reachable --changed-paths &&
+	grep "{\"hash_version\":1,\"num_hashes\":9,\"bits_per_entry\":15,\"max_changed_paths\":512" trace2-auto.txt
+'
+
+test_max_changed_paths () {
+	grep "\"max_changed_paths\":$1" $2
+}
+
+test_filter_not_computed () {
+	grep "\"key\":\"filter-not-computed\",\"value\":\"$1\"" $2
+}
+
+test_filter_computed () {
+	grep "\"key\":\"filter-computed\",\"value\":\"$1\"" $2
+}
+
+test_filter_trunc_empty () {
+	grep "\"key\":\"filter-trunc-empty\",\"value\":\"$1\"" $2
+}
+
+test_filter_trunc_large () {
+	grep "\"key\":\"filter-trunc-large\",\"value\":\"$1\"" $2
+}
+
+test_expect_success 'correctly report changes over limit' '
+	git init limits &&
+	(
+		cd limits &&
+		mkdir d &&
+		mkdir d/e &&
+
+		for i in $(test_seq 1 2)
+		do
+			printf $i >d/file$i.txt &&
+			printf $i >d/e/file$i.txt || return 1
+		done &&
+
+		mkdir mode &&
+		printf bash >mode/script.sh &&
+
+		mkdir foo &&
+		touch foo/bar &&
+		touch foo.txt &&
+
+		git add d foo foo.txt mode &&
+		git commit -m "files" &&
+
+		# Commit has 7 file and 4 directory adds
+		GIT_TEST_BLOOM_SETTINGS_MAX_CHANGED_PATHS=10 \
+			GIT_TRACE2_EVENT="$(pwd)/trace" \
+			git commit-graph write --reachable --changed-paths &&
+		test_max_changed_paths 10 trace &&
+		test_filter_computed 1 trace &&
+		test_filter_trunc_large 1 trace &&
+
+		for path in $(git ls-tree -r --name-only HEAD)
+		do
+			git -c commitGraph.readChangedPaths=false log \
+				-- $path >expect &&
+			git log -- $path >actual &&
+			test_cmp expect actual || return 1
+		done &&
+
+		# Make a variety of path changes
+		printf new1 >d/e/file1.txt &&
+		printf new2 >d/file2.txt &&
+		rm d/e/file2.txt &&
+		rm -r foo &&
+		printf text >foo &&
+		mkdir f &&
+		printf new1 >f/file1.txt &&
+
+		# including a mode-only change (counts as modified)
+		git update-index --chmod=+x mode/script.sh &&
+
+		git add foo d f &&
+		git commit -m "complicated" &&
+
+		# start from scratch and rebuild
+		rm -f .git/objects/info/commit-graph &&
+		GIT_TEST_BLOOM_SETTINGS_MAX_CHANGED_PATHS=10 \
+			GIT_TRACE2_EVENT="$(pwd)/trace-edit" \
+			git commit-graph write --reachable --changed-paths &&
+		test_max_changed_paths 10 trace-edit &&
+		test_filter_computed 2 trace-edit &&
+		test_filter_trunc_large 2 trace-edit &&
+
+		for path in $(git ls-tree -r --name-only HEAD)
+		do
+			git -c commitGraph.readChangedPaths=false log \
+				-- $path >expect &&
+			git log -- $path >actual &&
+			test_cmp expect actual || return 1
+		done &&
+
+		# start from scratch and rebuild
+		rm -f .git/objects/info/commit-graph &&
+		GIT_TEST_BLOOM_SETTINGS_MAX_CHANGED_PATHS=11 \
+			GIT_TRACE2_EVENT="$(pwd)/trace-update" \
+			git commit-graph write --reachable --changed-paths &&
+		test_max_changed_paths 11 trace-update &&
+		test_filter_computed 2 trace-update &&
+		test_filter_trunc_large 0 trace-update &&
+
+		for path in $(git ls-tree -r --name-only HEAD)
+		do
+			git -c commitGraph.readChangedPaths=false log \
+				-- $path >expect &&
+			git log -- $path >actual &&
+			test_cmp expect actual || return 1
+		done
+	)
+'
+
+test_expect_success 'correctly report commits with no changed paths' '
+	git init empty &&
+	test_when_finished "rm -fr empty" &&
+	(
+		cd empty &&
+
+		git commit --allow-empty -m "initial commit" &&
+
+		GIT_TRACE2_EVENT="$(pwd)/trace.event" \
+			git commit-graph write --reachable --changed-paths &&
+		test_filter_computed 1 trace.event &&
+		test_filter_not_computed 0 trace.event &&
+		test_filter_trunc_empty 1 trace.event &&
+		test_filter_trunc_large 0 trace.event
+	)
+'
+
+test_expect_success 'Bloom generation is limited by --max-new-filters' '
+	(
+		cd limits &&
+		test_commit c2 filter &&
+		test_commit c3 filter &&
+		test_commit c4 no-filter &&
+
+		rm -f trace.event &&
+		GIT_TRACE2_EVENT="$(pwd)/trace.event" \
+			git commit-graph write --reachable --split=replace \
+				--changed-paths --max-new-filters=2 &&
+
+		test_filter_computed 2 trace.event &&
+		test_filter_not_computed 3 trace.event &&
+		test_filter_trunc_empty 0 trace.event &&
+		test_filter_trunc_large 0 trace.event
+	)
+'
+
+test_expect_success 'Bloom generation backfills previously-skipped filters' '
+	# Check specifying commitGraph.maxNewFilters over "git config" works.
+	test_config -C limits commitGraph.maxNewFilters 1 &&
+	(
+		cd limits &&
+
+		rm -f trace.event &&
+		GIT_TRACE2_EVENT="$(pwd)/trace.event" \
+			git commit-graph write --reachable --changed-paths \
+				--split=replace &&
+		test_filter_computed 1 trace.event &&
+		test_filter_not_computed 4 trace.event &&
+		test_filter_trunc_empty 0 trace.event &&
+		test_filter_trunc_large 0 trace.event
+	)
+'
+
+test_expect_success '--max-new-filters overrides configuration' '
+	git init override &&
+	test_when_finished "rm -fr override" &&
+	test_config -C override commitGraph.maxNewFilters 2 &&
+	(
+		cd override &&
+		test_commit one &&
+		test_commit two &&
+
+		rm -f trace.event &&
+		GIT_TRACE2_EVENT="$(pwd)/trace.event" \
+			git commit-graph write --reachable --changed-paths \
+				--max-new-filters=1 &&
+		test_filter_computed 1 trace.event &&
+		test_filter_not_computed 1 trace.event &&
+		test_filter_trunc_empty 0 trace.event &&
+		test_filter_trunc_large 0 trace.event
+	)
+'
+
+test_expect_success 'Bloom generation backfills empty commits' '
+	git init empty &&
+	test_when_finished "rm -fr empty" &&
+	(
+		cd empty &&
+		for i in $(test_seq 1 6)
+		do
+			git commit --allow-empty -m "$i"
+		done &&
+
+		# Generate Bloom filters for empty commits 1-6, two at a time.
+		for i in $(test_seq 1 3)
+		do
+			rm -f trace.event &&
+			GIT_TRACE2_EVENT="$(pwd)/trace.event" \
+				git commit-graph write --reachable \
+					--changed-paths --max-new-filters=2 &&
+			test_filter_computed 2 trace.event &&
+			test_filter_not_computed 4 trace.event &&
+			test_filter_trunc_empty 2 trace.event &&
+			test_filter_trunc_large 0 trace.event
+		done &&
+
+		# Finally, make sure that once all commits have filters, that
+		# none are subsequently recomputed.
+		rm -f trace.event &&
+		GIT_TRACE2_EVENT="$(pwd)/trace.event" \
+			git commit-graph write --reachable \
+				--changed-paths --max-new-filters=2 &&
+		test_filter_computed 0 trace.event &&
+		test_filter_not_computed 6 trace.event &&
+		test_filter_trunc_empty 0 trace.event &&
+		test_filter_trunc_large 0 trace.event
+	)
+'
+
+test_done
diff --git a/third_party/git/t/t4254-am-corrupt.sh b/third_party/git/t/t4254-am-corrupt.sh
index fd3bdbfe2c..daf01c309d 100755
--- a/third_party/git/t/t4254-am-corrupt.sh
+++ b/third_party/git/t/t4254-am-corrupt.sh
@@ -3,6 +3,37 @@
 test_description='git am with corrupt input'
 . ./test-lib.sh
 
+make_mbox_with_nul () {
+	space=' '
+	q_nul_in_subject=
+	q_nul_in_body=
+	while test $# -ne 0
+	do
+		case "$1" in
+		subject) q_nul_in_subject='=00' ;;
+		body)    q_nul_in_body='=00' ;;
+		esac &&
+		shift
+	done &&
+	cat <<-EOF
+	From ec7364544f690c560304f5a5de9428ea3b978b26 Mon Sep 17 00:00:00 2001
+	From: A U Thor <author@example.com>
+	Date: Sun, 19 Apr 2020 13:42:07 +0700
+	Subject: [PATCH] =?ISO-8859-1?q?=C4=CB${q_nul_in_subject}=D1=CF=D6?=
+	MIME-Version: 1.0
+	Content-Type: text/plain; charset=ISO-8859-1
+	Content-Transfer-Encoding: quoted-printable
+
+	abc${q_nul_in_body}def
+	---
+	diff --git a/afile b/afile
+	new file mode 100644
+	index 0000000000..e69de29bb2
+	--$space
+	2.26.1
+	EOF
+}
+
 test_expect_success setup '
 	# Note the missing "+++" line:
 	cat >bad-patch.diff <<-\EOF &&
@@ -25,13 +56,27 @@ test_expect_success setup '
 #   fatal: unable to write file '(null)' mode 100644: Bad address
 # Also, it had the unwanted side-effect of deleting f.
 test_expect_success 'try to apply corrupted patch' '
-	test_must_fail git -c advice.amWorkDir=false am bad-patch.diff 2>actual
-'
-
-test_expect_success 'compare diagnostic; ensure file is still here' '
+	test_when_finished "git am --abort" &&
+	test_must_fail git -c advice.amWorkDir=false am bad-patch.diff 2>actual &&
 	echo "error: git diff header lacks filename information (line 4)" >expected &&
 	test_path_is_file f &&
 	test_i18ncmp expected actual
 '
 
+test_expect_success "NUL in commit message's body" '
+	test_when_finished "git am --abort" &&
+	make_mbox_with_nul body >body.patch &&
+	test_must_fail git am body.patch 2>err &&
+	grep "a NUL byte in commit log message not allowed" err
+'
+
+test_expect_success "NUL in commit message's header" "
+	test_when_finished 'git am --abort' &&
+	make_mbox_with_nul subject >subject.patch &&
+	test_must_fail git mailinfo msg patch <subject.patch 2>err &&
+	grep \"a NUL byte in 'Subject' is not allowed\" err &&
+	test_must_fail git am subject.patch 2>err &&
+	grep \"a NUL byte in 'Subject' is not allowed\" err
+"
+
 test_done
diff --git a/third_party/git/t/t4255-am-submodule.sh b/third_party/git/t/t4255-am-submodule.sh
index 0ba8194403..a7ba08f728 100755
--- a/third_party/git/t/t4255-am-submodule.sh
+++ b/third_party/git/t/t4255-am-submodule.sh
@@ -6,17 +6,21 @@ test_description='git am handling submodules'
 . "$TEST_DIRECTORY"/lib-submodule-update.sh
 
 am () {
-	git format-patch --stdout --ignore-submodules=dirty "..$1" | git am -
+	git format-patch --stdout --ignore-submodules=dirty "..$1" >patch &&
+	may_only_be_test_must_fail "$2" &&
+	$2 git am patch
 }
 
-test_submodule_switch "am"
+test_submodule_switch_func "am"
 
 am_3way () {
-	git format-patch --stdout --ignore-submodules=dirty "..$1" | git am --3way -
+	git format-patch --stdout --ignore-submodules=dirty "..$1" >patch &&
+	may_only_be_test_must_fail "$2" &&
+	$2 git am --3way patch
 }
 
 KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES=1
-test_submodule_switch "am_3way"
+test_submodule_switch_func "am_3way"
 
 test_expect_success 'setup diff.submodule' '
 	test_commit one &&
diff --git a/third_party/git/t/t4256-am-format-flowed.sh b/third_party/git/t/t4256-am-format-flowed.sh
index 6340310e9a..2369c4e17a 100755
--- a/third_party/git/t/t4256-am-format-flowed.sh
+++ b/third_party/git/t/t4256-am-format-flowed.sh
@@ -11,7 +11,7 @@ test_expect_success 'setup' '
 '
 
 test_expect_success 'am with format=flowed' '
-	git am <"$TEST_DIRECTORY/t4256/1/patch" >stdout 2>stderr &&
+	git am <"$TEST_DIRECTORY/t4256/1/patch" 2>stderr &&
 	test_i18ngrep "warning: Patch sent with format=flowed" stderr &&
 	test_cmp "$TEST_DIRECTORY/t4256/1/mailinfo.c" mailinfo.c
 '
diff --git a/third_party/git/t/t4300-merge-tree.sh b/third_party/git/t/t4300-merge-tree.sh
index d87cc7d9ef..e59601e5fe 100755
--- a/third_party/git/t/t4300-merge-tree.sh
+++ b/third_party/git/t/t4300-merge-tree.sh
@@ -11,16 +11,16 @@ test_expect_success setup '
 '
 
 test_expect_success 'file add A, !B' '
-	cat >expected <<\EXPECTED &&
+	git reset --hard initial &&
+	test_commit "add-a-not-b" "ONE" "AAA" &&
+	git merge-tree initial initial add-a-not-b >actual &&
+	cat >expected <<EXPECTED &&
 added in remote
-  their  100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE
+  their  100644 $(git rev-parse HEAD:ONE) ONE
 @@ -0,0 +1 @@
 +AAA
 EXPECTED
 
-	git reset --hard initial &&
-	test_commit "add-a-not-b" "ONE" "AAA" &&
-	git merge-tree initial initial add-a-not-b >actual &&
 	test_cmp expected actual
 '
 
@@ -41,10 +41,15 @@ test_expect_success 'file add A, B (same)' '
 '
 
 test_expect_success 'file add A, B (different)' '
-	cat >expected <<\EXPECTED &&
+	git reset --hard initial &&
+	test_commit "add-a-b-diff-A" "ONE" "AAA" &&
+	git reset --hard initial &&
+	test_commit "add-a-b-diff-B" "ONE" "BBB" &&
+	git merge-tree initial add-a-b-diff-A add-a-b-diff-B >actual &&
+	cat >expected <<EXPECTED &&
 added in both
-  our    100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE
-  their  100644 ba629238ca89489f2b350e196ca445e09d8bb834 ONE
+  our    100644 $(git rev-parse add-a-b-diff-A:ONE) ONE
+  their  100644 $(git rev-parse add-a-b-diff-B:ONE) ONE
 @@ -1 +1,5 @@
 +<<<<<<< .our
  AAA
@@ -53,11 +58,6 @@ added in both
 +>>>>>>> .their
 EXPECTED
 
-	git reset --hard initial &&
-	test_commit "add-a-b-diff-A" "ONE" "AAA" &&
-	git reset --hard initial &&
-	test_commit "add-a-b-diff-B" "ONE" "BBB" &&
-	git merge-tree initial add-a-b-diff-A add-a-b-diff-B >actual &&
 	test_cmp expected actual
 '
 
@@ -69,18 +69,18 @@ test_expect_success 'file change A, !B' '
 '
 
 test_expect_success 'file change !A, B' '
-	cat >expected <<\EXPECTED &&
+	git reset --hard initial &&
+	test_commit "change-not-a-b" "initial-file" "BBB" &&
+	git merge-tree initial initial change-not-a-b >actual &&
+	cat >expected <<EXPECTED &&
 merged
-  result 100644 ba629238ca89489f2b350e196ca445e09d8bb834 initial-file
-  our    100644 e79c5e8f964493290a409888d5413a737e8e5dd5 initial-file
+  result 100644 $(git rev-parse change-a-not-b:initial-file) initial-file
+  our    100644 $(git rev-parse initial:initial-file       ) initial-file
 @@ -1 +1 @@
 -initial
 +BBB
 EXPECTED
 
-	git reset --hard initial &&
-	test_commit "change-not-a-b" "initial-file" "BBB" &&
-	git merge-tree initial initial change-not-a-b >actual &&
 	test_cmp expected actual
 '
 
@@ -94,11 +94,16 @@ test_expect_success 'file change A, B (same)' '
 '
 
 test_expect_success 'file change A, B (different)' '
-	cat >expected <<\EXPECTED &&
+	git reset --hard initial &&
+	test_commit "change-a-b-diff-A" "initial-file" "AAA" &&
+	git reset --hard initial &&
+	test_commit "change-a-b-diff-B" "initial-file" "BBB" &&
+	git merge-tree initial change-a-b-diff-A change-a-b-diff-B >actual &&
+	cat >expected <<EXPECTED &&
 changed in both
-  base   100644 e79c5e8f964493290a409888d5413a737e8e5dd5 initial-file
-  our    100644 43d5a8ed6ef6c00ff775008633f95787d088285d initial-file
-  their  100644 ba629238ca89489f2b350e196ca445e09d8bb834 initial-file
+  base   100644 $(git rev-parse initial:initial-file          ) initial-file
+  our    100644 $(git rev-parse change-a-b-diff-A:initial-file) initial-file
+  their  100644 $(git rev-parse change-a-b-diff-B:initial-file) initial-file
 @@ -1 +1,5 @@
 +<<<<<<< .our
  AAA
@@ -107,34 +112,10 @@ changed in both
 +>>>>>>> .their
 EXPECTED
 
-	git reset --hard initial &&
-	test_commit "change-a-b-diff-A" "initial-file" "AAA" &&
-	git reset --hard initial &&
-	test_commit "change-a-b-diff-B" "initial-file" "BBB" &&
-	git merge-tree initial change-a-b-diff-A change-a-b-diff-B >actual &&
 	test_cmp expected actual
 '
 
 test_expect_success 'file change A, B (mixed)' '
-	cat >expected <<\EXPECTED &&
-changed in both
-  base   100644 f4f1f998c7776568c4ff38f516d77fef9399b5a7 ONE
-  our    100644 af14c2c3475337c73759d561ef70b59e5c731176 ONE
-  their  100644 372d761493f524d44d59bd24700c3bdf914c973c ONE
-@@ -7,7 +7,11 @@
- AAA
- AAA
- AAA
-+<<<<<<< .our
- BBB
-+=======
-+CCC
-+>>>>>>> .their
- AAA
- AAA
- AAA
-EXPECTED
-
 	git reset --hard initial &&
 	test_commit "change-a-b-mix-base" "ONE" "
 AAA
@@ -159,6 +140,26 @@ AAA" &&
 		"$(sed -e "1{s/AAA/BBB/;}" -e "10{s/AAA/CCC/;}" <ONE)" &&
 	git merge-tree change-a-b-mix-base change-a-b-mix-A change-a-b-mix-B \
 		>actual &&
+
+	cat >expected <<EXPECTED &&
+changed in both
+  base   100644 $(git rev-parse change-a-b-mix-base:ONE) ONE
+  our    100644 $(git rev-parse change-a-b-mix-A:ONE   ) ONE
+  their  100644 $(git rev-parse change-a-b-mix-B:ONE   ) ONE
+@@ -7,7 +7,11 @@
+ AAA
+ AAA
+ AAA
++<<<<<<< .our
+ BBB
++=======
++CCC
++>>>>>>> .their
+ AAA
+ AAA
+ AAA
+EXPECTED
+
 	test_cmp expected actual
 '
 
@@ -173,20 +174,20 @@ test_expect_success 'file remove A, !B' '
 '
 
 test_expect_success 'file remove !A, B' '
-	cat >expected <<\EXPECTED &&
-removed in remote
-  base   100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE
-  our    100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE
-@@ -1 +0,0 @@
--AAA
-EXPECTED
-
 	git reset --hard initial &&
 	test_commit "rm-not-a-b-base" "ONE" "AAA" &&
 	git rm ONE &&
 	git commit -m "rm-not-a-b" &&
 	git tag "rm-not-a-b" &&
 	git merge-tree rm-a-not-b-base rm-a-not-b-base rm-a-not-b >actual &&
+	cat >expected <<EXPECTED &&
+removed in remote
+  base   100644 $(git rev-parse rm-a-not-b-base:ONE) ONE
+  our    100644 $(git rev-parse rm-a-not-b-base:ONE) ONE
+@@ -1 +0,0 @@
+-AAA
+EXPECTED
+
 	test_cmp expected actual
 '
 
@@ -201,14 +202,6 @@ test_expect_success 'file remove A, B (same)' '
 '
 
 test_expect_success 'file change A, remove B' '
-	cat >expected <<\EXPECTED &&
-removed in remote
-  base   100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE
-  our    100644 ba629238ca89489f2b350e196ca445e09d8bb834 ONE
-@@ -1 +0,0 @@
--BBB
-EXPECTED
-
 	git reset --hard initial &&
 	test_commit "change-a-rm-b-base" "ONE" "AAA" &&
 	test_commit "change-a-rm-b-A" "ONE" "BBB" &&
@@ -218,16 +211,18 @@ EXPECTED
 	git tag "change-a-rm-b-B" &&
 	git merge-tree change-a-rm-b-base change-a-rm-b-A change-a-rm-b-B \
 		>actual &&
+	cat >expected <<EXPECTED &&
+removed in remote
+  base   100644 $(git rev-parse change-a-rm-b-base:ONE) ONE
+  our    100644 $(git rev-parse change-a-rm-b-A:ONE   ) ONE
+@@ -1 +0,0 @@
+-BBB
+EXPECTED
+
 	test_cmp expected actual
 '
 
 test_expect_success 'file remove A, change B' '
-	cat >expected <<\EXPECTED &&
-removed in local
-  base   100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE
-  their  100644 ba629238ca89489f2b350e196ca445e09d8bb834 ONE
-EXPECTED
-
 	git reset --hard initial &&
 	test_commit "rm-a-change-b-base" "ONE" "AAA" &&
 
@@ -238,6 +233,11 @@ EXPECTED
 	test_commit "rm-a-change-b-B" "ONE" "BBB" &&
 	git merge-tree rm-a-change-b-base rm-a-change-b-A rm-a-change-b-B \
 		>actual &&
+	cat >expected <<EXPECTED &&
+removed in local
+  base   100644 $(git rev-parse rm-a-change-b-base:ONE) ONE
+  their  100644 $(git rev-parse rm-a-change-b-B:ONE   ) ONE
+EXPECTED
 	test_cmp expected actual
 '
 
@@ -250,10 +250,17 @@ test_expect_success 'tree add A, B (same)' '
 '
 
 test_expect_success 'tree add A, B (different)' '
-	cat >expect <<-\EOF &&
+	git reset --hard initial &&
+	mkdir sub &&
+	test_commit "add sub/file" "sub/file" "AAA" add-tree-a-b-A &&
+	git reset --hard initial &&
+	mkdir sub &&
+	test_commit "add sub/file" "sub/file" "BBB" add-tree-a-b-B &&
+	git merge-tree initial add-tree-a-b-A add-tree-a-b-B >actual &&
+	cat >expect <<-EOF &&
 	added in both
-	  our    100644 43d5a8ed6ef6c00ff775008633f95787d088285d sub/file
-	  their  100644 ba629238ca89489f2b350e196ca445e09d8bb834 sub/file
+	  our    100644 $(git rev-parse add-tree-a-b-A:sub/file) sub/file
+	  their  100644 $(git rev-parse add-tree-a-b-B:sub/file) sub/file
 	@@ -1 +1,5 @@
 	+<<<<<<< .our
 	 AAA
@@ -261,24 +268,10 @@ test_expect_success 'tree add A, B (different)' '
 	+BBB
 	+>>>>>>> .their
 	EOF
-	git reset --hard initial &&
-	mkdir sub &&
-	test_commit "add sub/file" "sub/file" "AAA" add-tree-a-b-A &&
-	git reset --hard initial &&
-	mkdir sub &&
-	test_commit "add sub/file" "sub/file" "BBB" add-tree-a-b-B &&
-	git merge-tree initial add-tree-a-b-A add-tree-a-b-B >actual &&
 	test_cmp expect actual
 '
 
 test_expect_success 'tree unchanged A, removed B' '
-	cat >expect <<-\EOF &&
-	removed in remote
-	  base   100644 43d5a8ed6ef6c00ff775008633f95787d088285d sub/file
-	  our    100644 43d5a8ed6ef6c00ff775008633f95787d088285d sub/file
-	@@ -1 +0,0 @@
-	-AAA
-	EOF
 	git reset --hard initial &&
 	mkdir sub &&
 	test_commit "add sub/file" "sub/file" "AAA" tree-remove-b-initial &&
@@ -287,6 +280,13 @@ test_expect_success 'tree unchanged A, removed B' '
 	git commit -m "remove sub/file" &&
 	git tag tree-remove-b-B &&
 	git merge-tree tree-remove-b-initial tree-remove-b-initial tree-remove-b-B >actual &&
+	cat >expect <<-EOF &&
+	removed in remote
+	  base   100644 $(git rev-parse tree-remove-b-initial:sub/file) sub/file
+	  our    100644 $(git rev-parse tree-remove-b-initial:sub/file) sub/file
+	@@ -1 +0,0 @@
+	-AAA
+	EOF
 	test_cmp expect actual
 '
 
@@ -296,14 +296,14 @@ test_expect_success 'turn file to tree' '
 	mkdir initial-file &&
 	test_commit "turn-file-to-tree" "initial-file/ONE" "CCC" &&
 	git merge-tree initial initial turn-file-to-tree >actual &&
-	cat >expect <<-\EOF &&
+	cat >expect <<-EOF &&
 	added in remote
-	  their  100644 43aa4fdec31eb92e1fdc2f0ce6ea9ddb7c32bcf7 initial-file/ONE
+	  their  100644 $(git rev-parse turn-file-to-tree:initial-file/ONE) initial-file/ONE
 	@@ -0,0 +1 @@
 	+CCC
 	removed in remote
-	  base   100644 e79c5e8f964493290a409888d5413a737e8e5dd5 initial-file
-	  our    100644 e79c5e8f964493290a409888d5413a737e8e5dd5 initial-file
+	  base   100644 $(git rev-parse initial:initial-file) initial-file
+	  our    100644 $(git rev-parse initial:initial-file) initial-file
 	@@ -1 +0,0 @@
 	-initial
 	EOF
@@ -318,14 +318,14 @@ test_expect_success 'turn tree to file' '
 	rm -fr dir &&
 	test_commit "make-file" "dir" "CCC" &&
 	git merge-tree add-tree add-another-tree make-file >actual &&
-	cat >expect <<-\EOF &&
+	cat >expect <<-EOF &&
 	removed in remote
-	  base   100644 43d5a8ed6ef6c00ff775008633f95787d088285d dir/path
-	  our    100644 43d5a8ed6ef6c00ff775008633f95787d088285d dir/path
+	  base   100644 $(git rev-parse add-tree:dir/path) dir/path
+	  our    100644 $(git rev-parse add-tree:dir/path) dir/path
 	@@ -1 +0,0 @@
 	-AAA
 	added in remote
-	  their  100644 43aa4fdec31eb92e1fdc2f0ce6ea9ddb7c32bcf7 dir
+	  their  100644 $(git rev-parse make-file:dir) dir
 	@@ -0,0 +1 @@
 	+CCC
 	EOF
diff --git a/third_party/git/t/t5000-tar-tree.sh b/third_party/git/t/t5000-tar-tree.sh
index 37655a237c..3ebb0d3b65 100755
--- a/third_party/git/t/t5000-tar-tree.sh
+++ b/third_party/git/t/t5000-tar-tree.sh
@@ -94,6 +94,16 @@ check_tar() {
 	'
 }
 
+check_added() {
+	dir=$1
+	path_in_fs=$2
+	path_in_archive=$3
+
+	test_expect_success " validate extra file $path_in_archive" '
+		diff -r $path_in_fs $dir/$path_in_archive
+	'
+}
+
 test_expect_success 'setup' '
 	test_oid_cache <<-EOF
 	obj sha1:19f9c8273ec45a8938e6999cb59b3ff66739902a
@@ -164,6 +174,25 @@ test_expect_success 'git-archive --prefix=olde-' '
 
 check_tar with_olde-prefix olde-
 
+test_expect_success 'git archive --add-file' '
+	echo untracked >untracked &&
+	git archive --add-file=untracked HEAD >with_untracked.tar
+'
+
+check_tar with_untracked
+check_added with_untracked untracked untracked
+
+test_expect_success 'git archive --add-file twice' '
+	echo untracked >untracked &&
+	git archive --prefix=one/ --add-file=untracked \
+		--prefix=two/ --add-file=untracked \
+		--prefix= HEAD >with_untracked2.tar
+'
+
+check_tar with_untracked2
+check_added with_untracked2 untracked one/untracked
+check_added with_untracked2 untracked two/untracked
+
 test_expect_success 'git archive on large files' '
     test_config core.bigfilethreshold 1 &&
     git archive HEAD >b3.tar &&
diff --git a/third_party/git/t/t5003-archive-zip.sh b/third_party/git/t/t5003-archive-zip.sh
index 106eddbd85..1e6d18b140 100755
--- a/third_party/git/t/t5003-archive-zip.sh
+++ b/third_party/git/t/t5003-archive-zip.sh
@@ -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,39 +39,49 @@ 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
 	"
 }
 
+check_added() {
+	dir=$1
+	path_in_fs=$2
+	path_in_archive=$3
+
+	test_expect_success UNZIP " validate extra file $path_in_archive" '
+		diff -r $path_in_fs $dir/$path_in_archive
+	'
+}
+
 test_expect_success \
     'populate workdir' \
     'mkdir a &&
@@ -188,4 +198,22 @@ test_expect_success 'git archive --format=zip on large files' '
 
 check_zip large-compressed
 
+test_expect_success 'git archive --format=zip --add-file' '
+	echo untracked >untracked &&
+	git archive --format=zip --add-file=untracked HEAD >with_untracked.zip
+'
+
+check_zip with_untracked
+check_added with_untracked untracked untracked
+
+test_expect_success 'git archive --format=zip --add-file twice' '
+	echo untracked >untracked &&
+	git archive --format=zip --prefix=one/ --add-file=untracked \
+		--prefix=two/ --add-file=untracked \
+		--prefix= HEAD >with_untracked2.zip
+'
+check_zip with_untracked2
+check_added with_untracked2 untracked one/untracked
+check_added with_untracked2 untracked two/untracked
+
 test_done
diff --git a/third_party/git/t/t5004-archive-corner-cases.sh b/third_party/git/t/t5004-archive-corner-cases.sh
index 271eb5a1fd..3e7b23cb32 100755
--- a/third_party/git/t/t5004-archive-corner-cases.sh
+++ b/third_party/git/t/t5004-archive-corner-cases.sh
@@ -204,4 +204,23 @@ test_expect_success EXPENSIVE,LONG_IS_64BIT,UNZIP,UNZIP_ZIP64_SUPPORT,ZIPINFO \
 	grep $size big.lst
 '
 
+build_tree() {
+	perl -e '
+		my $hash = $ARGV[0];
+		foreach my $order (2..6) {
+			$first = 10 ** $order;
+			foreach my $i (-13..-9) {
+				my $name = "a" x ($first + $i);
+				print "100644 blob $hash\t$name\n"
+			}
+		}
+	' "$1"
+}
+
+test_expect_success 'tar archive with long paths' '
+	blob=$(echo foo | git hash-object -w --stdin) &&
+	tree=$(build_tree $blob | git mktree) &&
+	git archive -o long_paths.tar $tree
+'
+
 test_done
diff --git a/third_party/git/t/t5100-mailinfo.sh b/third_party/git/t/t5100-mailinfo.sh
index 9690dcad4f..147e616533 100755
--- a/third_party/git/t/t5100-mailinfo.sh
+++ b/third_party/git/t/t5100-mailinfo.sh
@@ -213,4 +213,19 @@ test_expect_failure 'mailinfo -b separated double [PATCH]' '
 	test z"$subj" = z"Subject: [other] message"
 '
 
+test_expect_success 'mailinfo handles unusual header whitespace' '
+	git mailinfo /dev/null /dev/null >actual <<-\EOF &&
+	From:Real Name <user@example.com>
+	Subject:    extra spaces
+	EOF
+
+	cat >expect <<-\EOF &&
+	Author: Real Name
+	Email: user@example.com
+	Subject: extra spaces
+
+	EOF
+	test_cmp expect actual
+'
+
 test_done
diff --git a/third_party/git/t/t5150-request-pull.sh b/third_party/git/t/t5150-request-pull.sh
index 852dcd913f..c1811ea0f4 100755
--- a/third_party/git/t/t5150-request-pull.sh
+++ b/third_party/git/t/t5150-request-pull.sh
@@ -4,6 +4,12 @@ test_description='Test workflows involving pull request.'
 
 . ./test-lib.sh
 
+if ! test_have_prereq PERL
+then
+	skip_all='skipping request-pull tests, perl not available'
+	test_done
+fi
+
 test_expect_success 'setup' '
 
 	git init --bare upstream.git &&
@@ -144,7 +150,6 @@ test_expect_success 'pull request after push' '
 		git request-pull initial origin master:for-upstream >../request
 	) &&
 	sed -nf read-request.sed <request >digest &&
-	cat digest &&
 	{
 		read task &&
 		read repository &&
@@ -173,7 +178,6 @@ test_expect_success 'request asks HEAD to be pulled' '
 		git request-pull initial "$downstream_url" >../request
 	) &&
 	sed -nf read-request.sed <request >digest &&
-	cat digest &&
 	{
 		read task &&
 		read repository &&
diff --git a/third_party/git/t/t5300-pack-object.sh b/third_party/git/t/t5300-pack-object.sh
index 410a09b0dd..392201cabd 100755
--- a/third_party/git/t/t5300-pack-object.sh
+++ b/third_party/git/t/t5300-pack-object.sh
@@ -412,18 +412,18 @@ test_expect_success 'set up pack for non-repo tests' '
 '
 
 test_expect_success 'index-pack --stdin complains of non-repo' '
-	nongit test_must_fail git index-pack --stdin <foo.pack &&
+	nongit test_must_fail git index-pack --object-format=$(test_oid algo) --stdin <foo.pack &&
 	test_path_is_missing non-repo/.git
 '
 
 test_expect_success 'index-pack <pack> works in non-repo' '
-	nongit git index-pack ../foo.pack &&
+	nongit git index-pack --object-format=$(test_oid algo) ../foo.pack &&
 	test_path_is_file foo.idx
 '
 
 test_expect_success 'index-pack --strict <pack> works in non-repo' '
 	rm -f foo.idx &&
-	nongit git index-pack --strict ../foo.pack &&
+	nongit git index-pack --strict --object-format=$(test_oid algo) ../foo.pack &&
 	test_path_is_file foo.idx
 '
 
@@ -496,4 +496,40 @@ test_expect_success 'make sure index-pack detects the SHA1 collision (large blob
 	)
 '
 
+test_expect_success 'prefetch objects' '
+	rm -rf server client &&
+
+	git init server &&
+	test_config -C server uploadpack.allowanysha1inwant 1 &&
+	test_config -C server uploadpack.allowfilter 1 &&
+	test_config -C server protocol.version 2 &&
+
+	echo one >server/one &&
+	git -C server add one &&
+	git -C server commit -m one &&
+	git -C server branch one_branch &&
+
+	echo two_a >server/two_a &&
+	echo two_b >server/two_b &&
+	git -C server add two_a two_b &&
+	git -C server commit -m two &&
+
+	echo three >server/three &&
+	git -C server add three &&
+	git -C server commit -m three &&
+	git -C server branch three_branch &&
+
+	# Clone, fetch "two" with blobs excluded, and re-push it. This requires
+	# the client to have the blobs of "two" - verify that these are
+	# prefetched in one batch.
+	git clone --filter=blob:none --single-branch -b one_branch \
+		"file://$(pwd)/server" client &&
+	test_config -C client protocol.version 2 &&
+	TWO=$(git -C server rev-parse three_branch^) &&
+	git -C client fetch --filter=blob:none origin "$TWO" &&
+	GIT_TRACE_PACKET=$(pwd)/trace git -C client push origin "$TWO":refs/heads/two_branch &&
+	grep "fetch> done" trace >donelines &&
+	test_line_count = 1 donelines
+'
+
 test_done
diff --git a/third_party/git/t/t5302-pack-index.sh b/third_party/git/t/t5302-pack-index.sh
index 91d51b35f9..7c9d687367 100755
--- a/third_party/git/t/t5302-pack-index.sh
+++ b/third_party/git/t/t5302-pack-index.sh
@@ -6,64 +6,65 @@
 test_description='pack index with 64-bit offsets and object CRC'
 . ./test-lib.sh
 
-test_expect_success \
-    'setup' \
-    'rm -rf .git &&
-     git init &&
-     git config pack.threads 1 &&
-     i=1 &&
-     while test $i -le 100
-     do
-         iii=$(printf '%03i' $i)
-	 test-tool genrandom "bar" 200 > wide_delta_$iii &&
-	 test-tool genrandom "baz $iii" 50 >> wide_delta_$iii &&
-	 test-tool genrandom "foo"$i 100 > deep_delta_$iii &&
-	 test-tool genrandom "foo"$(expr $i + 1) 100 >> deep_delta_$iii &&
-	 test-tool genrandom "foo"$(expr $i + 2) 100 >> deep_delta_$iii &&
-         echo $iii >file_$iii &&
-	 test-tool genrandom "$iii" 8192 >>file_$iii &&
-         git update-index --add file_$iii deep_delta_$iii wide_delta_$iii &&
-         i=$(expr $i + 1) || return 1
-     done &&
-     { echo 101 && test-tool genrandom 100 8192; } >file_101 &&
-     git update-index --add file_101 &&
-     tree=$(git write-tree) &&
-     commit=$(git commit-tree $tree </dev/null) && {
-	 echo $tree &&
-	 git ls-tree $tree | sed -e "s/.* \\([0-9a-f]*\\)	.*/\\1/"
-     } >obj-list &&
-     git update-ref HEAD $commit'
+test_expect_success 'setup' '
+	rawsz=$(test_oid rawsz) &&
+	rm -rf .git &&
+	git init &&
+	git config pack.threads 1 &&
+	i=1 &&
+	while test $i -le 100
+	do
+		iii=$(printf "%03i" $i)
+		test-tool genrandom "bar" 200 > wide_delta_$iii &&
+		test-tool genrandom "baz $iii" 50 >> wide_delta_$iii &&
+		test-tool genrandom "foo"$i 100 > deep_delta_$iii &&
+		test-tool genrandom "foo"$(expr $i + 1) 100 >> deep_delta_$iii &&
+		test-tool genrandom "foo"$(expr $i + 2) 100 >> deep_delta_$iii &&
+		echo $iii >file_$iii &&
+		test-tool genrandom "$iii" 8192 >>file_$iii &&
+		git update-index --add file_$iii deep_delta_$iii wide_delta_$iii &&
+		i=$(expr $i + 1) || return 1
+	done &&
+	{ echo 101 && test-tool genrandom 100 8192; } >file_101 &&
+	git update-index --add file_101 &&
+	tree=$(git write-tree) &&
+	commit=$(git commit-tree $tree </dev/null) && {
+		echo $tree &&
+		git ls-tree $tree | sed -e "s/.* \\([0-9a-f]*\\)	.*/\\1/"
+	} >obj-list &&
+	git update-ref HEAD $commit
+'
 
-test_expect_success \
-    'pack-objects with index version 1' \
-    'pack1=$(git pack-objects --index-version=1 test-1 <obj-list) &&
-     git verify-pack -v "test-1-${pack1}.pack"'
+test_expect_success 'pack-objects with index version 1' '
+	pack1=$(git pack-objects --index-version=1 test-1 <obj-list) &&
+	git verify-pack -v "test-1-${pack1}.pack"
+'
 
-test_expect_success \
-    'pack-objects with index version 2' \
-    'pack2=$(git pack-objects --index-version=2 test-2 <obj-list) &&
-     git verify-pack -v "test-2-${pack2}.pack"'
+test_expect_success 'pack-objects with index version 2' '
+	pack2=$(git pack-objects --index-version=2 test-2 <obj-list) &&
+	git verify-pack -v "test-2-${pack2}.pack"
+'
 
-test_expect_success \
-    'both packs should be identical' \
-    'cmp "test-1-${pack1}.pack" "test-2-${pack2}.pack"'
+test_expect_success 'both packs should be identical' '
+	cmp "test-1-${pack1}.pack" "test-2-${pack2}.pack"
+'
 
-test_expect_success \
-    'index v1 and index v2 should be different' \
-    '! cmp "test-1-${pack1}.idx" "test-2-${pack2}.idx"'
+test_expect_success 'index v1 and index v2 should be different' '
+	! cmp "test-1-${pack1}.idx" "test-2-${pack2}.idx"
+'
 
-test_expect_success \
-    'index-pack with index version 1' \
-    'git index-pack --index-version=1 -o 1.idx "test-1-${pack1}.pack"'
+test_expect_success 'index-pack with index version 1' '
+	git index-pack --index-version=1 -o 1.idx "test-1-${pack1}.pack"
+'
 
-test_expect_success \
-    'index-pack with index version 2' \
-    'git index-pack --index-version=2 -o 2.idx "test-1-${pack1}.pack"'
+test_expect_success 'index-pack with index version 2' '
+	git index-pack --index-version=2 -o 2.idx "test-1-${pack1}.pack"
+'
 
-test_expect_success \
-    'index-pack results should match pack-objects ones' \
-    'cmp "test-1-${pack1}.idx" "1.idx" &&
-     cmp "test-2-${pack2}.idx" "2.idx"'
+test_expect_success 'index-pack results should match pack-objects ones' '
+	cmp "test-1-${pack1}.idx" "1.idx" &&
+	cmp "test-2-${pack2}.idx" "2.idx"
+'
 
 test_expect_success 'index-pack --verify on index version 1' '
 	git index-pack --verify "test-1-${pack1}.pack"
@@ -73,13 +74,13 @@ test_expect_success 'index-pack --verify on index version 2' '
 	git index-pack --verify "test-2-${pack2}.pack"
 '
 
-test_expect_success \
-    'pack-objects --index-version=2, is not accepted' \
-    'test_must_fail git pack-objects --index-version=2, test-3 <obj-list'
+test_expect_success 'pack-objects --index-version=2, is not accepted' '
+	test_must_fail git pack-objects --index-version=2, test-3 <obj-list
+'
 
-test_expect_success \
-    'index v2: force some 64-bit offsets with pack-objects' \
-    'pack3=$(git pack-objects --index-version=2,0x40000 test-3 <obj-list)'
+test_expect_success 'index v2: force some 64-bit offsets with pack-objects' '
+	pack3=$(git pack-objects --index-version=2,0x40000 test-3 <obj-list)
+'
 
 if msg=$(git verify-pack -v "test-3-${pack3}.pack" 2>&1) ||
 	! (echo "$msg" | grep "pack too large .* off_t")
@@ -89,21 +90,21 @@ else
 	say "# skipping tests concerning 64-bit offsets"
 fi
 
-test_expect_success OFF64_T \
-    'index v2: verify a pack with some 64-bit offsets' \
-    'git verify-pack -v "test-3-${pack3}.pack"'
+test_expect_success OFF64_T 'index v2: verify a pack with some 64-bit offsets' '
+	git verify-pack -v "test-3-${pack3}.pack"
+'
 
-test_expect_success OFF64_T \
-    '64-bit offsets: should be different from previous index v2 results' \
-    '! cmp "test-2-${pack2}.idx" "test-3-${pack3}.idx"'
+test_expect_success OFF64_T '64-bit offsets: should be different from previous index v2 results' '
+	! cmp "test-2-${pack2}.idx" "test-3-${pack3}.idx"
+'
 
-test_expect_success OFF64_T \
-    'index v2: force some 64-bit offsets with index-pack' \
-    'git index-pack --index-version=2,0x40000 -o 3.idx "test-1-${pack1}.pack"'
+test_expect_success OFF64_T 'index v2: force some 64-bit offsets with index-pack' '
+	git index-pack --index-version=2,0x40000 -o 3.idx "test-1-${pack1}.pack"
+'
 
-test_expect_success OFF64_T \
-    '64-bit offsets: index-pack result should match pack-objects one' \
-    'cmp "test-3-${pack3}.idx" "3.idx"'
+test_expect_success OFF64_T '64-bit offsets: index-pack result should match pack-objects one' '
+	cmp "test-3-${pack3}.idx" "3.idx"
+'
 
 test_expect_success OFF64_T 'index-pack --verify on 64-bit offset v2 (cheat)' '
 	# This cheats by knowing which lower offset should still be encoded
@@ -118,134 +119,143 @@ test_expect_success OFF64_T 'index-pack --verify on 64-bit offset v2' '
 # returns the object number for given object in given pack index
 index_obj_nr()
 {
-    idx_file=$1
-    object_sha1=$2
-    nr=0
-    git show-index < $idx_file |
-    while read offs sha1 extra
-    do
-      nr=$(($nr + 1))
-      test "$sha1" = "$object_sha1" || continue
-      echo "$(($nr - 1))"
-      break
-    done
+	idx_file=$1
+	object_sha1=$2
+	nr=0
+	git show-index < $idx_file |
+	while read offs sha1 extra
+	do
+	  nr=$(($nr + 1))
+	  test "$sha1" = "$object_sha1" || continue
+	  echo "$(($nr - 1))"
+	  break
+	done
 }
 
 # returns the pack offset for given object as found in given pack index
 index_obj_offset()
 {
-    idx_file=$1
-    object_sha1=$2
-    git show-index < $idx_file | grep $object_sha1 |
-    ( read offs extra && echo "$offs" )
+	idx_file=$1
+	object_sha1=$2
+	git show-index < $idx_file | grep $object_sha1 |
+	( read offs extra && echo "$offs" )
 }
 
-test_expect_success \
-    '[index v1] 1) stream pack to repository' \
-    'git index-pack --index-version=1 --stdin < "test-1-${pack1}.pack" &&
-     git prune-packed &&
-     git count-objects | ( read nr rest && test "$nr" -eq 1 ) &&
-     cmp "test-1-${pack1}.pack" ".git/objects/pack/pack-${pack1}.pack" &&
-     cmp "test-1-${pack1}.idx"  ".git/objects/pack/pack-${pack1}.idx"'
+test_expect_success '[index v1] 1) stream pack to repository' '
+	git index-pack --index-version=1 --stdin < "test-1-${pack1}.pack" &&
+	git prune-packed &&
+	git count-objects | ( read nr rest && test "$nr" -eq 1 ) &&
+	cmp "test-1-${pack1}.pack" ".git/objects/pack/pack-${pack1}.pack" &&
+	cmp "test-1-${pack1}.idx"	".git/objects/pack/pack-${pack1}.idx"
+'
 
 test_expect_success \
-    '[index v1] 2) create a stealth corruption in a delta base reference' \
-    '# This test assumes file_101 is a delta smaller than 16 bytes.
-     # It should be against file_100 but we substitute its base for file_099
-     sha1_101=$(git hash-object file_101) &&
-     sha1_099=$(git hash-object file_099) &&
-     offs_101=$(index_obj_offset 1.idx $sha1_101) &&
-     nr_099=$(index_obj_nr 1.idx $sha1_099) &&
-     chmod +w ".git/objects/pack/pack-${pack1}.pack" &&
-     dd of=".git/objects/pack/pack-${pack1}.pack" seek=$(($offs_101 + 1)) \
-        if=".git/objects/pack/pack-${pack1}.idx" \
-        skip=$((4 + 256 * 4 + $nr_099 * 24)) \
-        bs=1 count=20 conv=notrunc &&
-     git cat-file blob $sha1_101 > file_101_foo1'
+	'[index v1] 2) create a stealth corruption in a delta base reference' '
+	# This test assumes file_101 is a delta smaller than 16 bytes.
+	# It should be against file_100 but we substitute its base for file_099
+	sha1_101=$(git hash-object file_101) &&
+	sha1_099=$(git hash-object file_099) &&
+	offs_101=$(index_obj_offset 1.idx $sha1_101) &&
+	nr_099=$(index_obj_nr 1.idx $sha1_099) &&
+	chmod +w ".git/objects/pack/pack-${pack1}.pack" &&
+	recordsz=$((rawsz + 4)) &&
+	dd of=".git/objects/pack/pack-${pack1}.pack" seek=$(($offs_101 + 1)) \
+	       if=".git/objects/pack/pack-${pack1}.idx" \
+	       skip=$((4 + 256 * 4 + $nr_099 * recordsz)) \
+	       bs=1 count=$rawsz conv=notrunc &&
+	git cat-file blob $sha1_101 > file_101_foo1
+'
 
 test_expect_success \
-    '[index v1] 3) corrupted delta happily returned wrong data' \
-    'test -f file_101_foo1 && ! cmp file_101 file_101_foo1'
+	'[index v1] 3) corrupted delta happily returned wrong data' '
+	test -f file_101_foo1 && ! cmp file_101 file_101_foo1
+'
 
 test_expect_success \
-    '[index v1] 4) confirm that the pack is actually corrupted' \
-    'test_must_fail git fsck --full $commit'
+	'[index v1] 4) confirm that the pack is actually corrupted' '
+	test_must_fail git fsck --full $commit
+'
 
 test_expect_success \
-    '[index v1] 5) pack-objects happily reuses corrupted data' \
-    'pack4=$(git pack-objects test-4 <obj-list) &&
-     test -f "test-4-${pack4}.pack"'
+	'[index v1] 5) pack-objects happily reuses corrupted data' '
+	pack4=$(git pack-objects test-4 <obj-list) &&
+	test -f "test-4-${pack4}.pack"
+'
 
-test_expect_success \
-    '[index v1] 6) newly created pack is BAD !' \
-    'test_must_fail git verify-pack -v "test-4-${pack4}.pack"'
+test_expect_success '[index v1] 6) newly created pack is BAD !' '
+	test_must_fail git verify-pack -v "test-4-${pack4}.pack"
+'
 
-test_expect_success \
-    '[index v2] 1) stream pack to repository' \
-    'rm -f .git/objects/pack/* &&
-     git index-pack --index-version=2 --stdin < "test-1-${pack1}.pack" &&
-     git prune-packed &&
-     git count-objects | ( read nr rest && test "$nr" -eq 1 ) &&
-     cmp "test-1-${pack1}.pack" ".git/objects/pack/pack-${pack1}.pack" &&
-     cmp "test-2-${pack1}.idx"  ".git/objects/pack/pack-${pack1}.idx"'
+test_expect_success '[index v2] 1) stream pack to repository' '
+	rm -f .git/objects/pack/* &&
+	git index-pack --index-version=2 --stdin < "test-1-${pack1}.pack" &&
+	git prune-packed &&
+	git count-objects | ( read nr rest && test "$nr" -eq 1 ) &&
+	cmp "test-1-${pack1}.pack" ".git/objects/pack/pack-${pack1}.pack" &&
+	cmp "test-2-${pack1}.idx"	".git/objects/pack/pack-${pack1}.idx"
+'
 
 test_expect_success \
-    '[index v2] 2) create a stealth corruption in a delta base reference' \
-    '# This test assumes file_101 is a delta smaller than 16 bytes.
-     # It should be against file_100 but we substitute its base for file_099
-     sha1_101=$(git hash-object file_101) &&
-     sha1_099=$(git hash-object file_099) &&
-     offs_101=$(index_obj_offset 1.idx $sha1_101) &&
-     nr_099=$(index_obj_nr 1.idx $sha1_099) &&
-     chmod +w ".git/objects/pack/pack-${pack1}.pack" &&
-     dd of=".git/objects/pack/pack-${pack1}.pack" seek=$(($offs_101 + 1)) \
-        if=".git/objects/pack/pack-${pack1}.idx" \
-        skip=$((8 + 256 * 4 + $nr_099 * 20)) \
-        bs=1 count=20 conv=notrunc &&
-     git cat-file blob $sha1_101 > file_101_foo2'
+	'[index v2] 2) create a stealth corruption in a delta base reference' '
+	# This test assumes file_101 is a delta smaller than 16 bytes.
+	# It should be against file_100 but we substitute its base for file_099
+	sha1_101=$(git hash-object file_101) &&
+	sha1_099=$(git hash-object file_099) &&
+	offs_101=$(index_obj_offset 1.idx $sha1_101) &&
+	nr_099=$(index_obj_nr 1.idx $sha1_099) &&
+	chmod +w ".git/objects/pack/pack-${pack1}.pack" &&
+	dd of=".git/objects/pack/pack-${pack1}.pack" seek=$(($offs_101 + 1)) \
+		if=".git/objects/pack/pack-${pack1}.idx" \
+		skip=$((8 + 256 * 4 + $nr_099 * rawsz)) \
+		bs=1 count=$rawsz conv=notrunc &&
+	git cat-file blob $sha1_101 > file_101_foo2
+'
 
 test_expect_success \
-    '[index v2] 3) corrupted delta happily returned wrong data' \
-    'test -f file_101_foo2 && ! cmp file_101 file_101_foo2'
+	'[index v2] 3) corrupted delta happily returned wrong data' '
+	test -f file_101_foo2 && ! cmp file_101 file_101_foo2
+'
 
 test_expect_success \
-    '[index v2] 4) confirm that the pack is actually corrupted' \
-    'test_must_fail git fsck --full $commit'
+	'[index v2] 4) confirm that the pack is actually corrupted' '
+	test_must_fail git fsck --full $commit
+'
 
 test_expect_success \
-    '[index v2] 5) pack-objects refuses to reuse corrupted data' \
-    'test_must_fail git pack-objects test-5 <obj-list &&
-     test_must_fail git pack-objects --no-reuse-object test-6 <obj-list'
+	'[index v2] 5) pack-objects refuses to reuse corrupted data' '
+	test_must_fail git pack-objects test-5 <obj-list &&
+	test_must_fail git pack-objects --no-reuse-object test-6 <obj-list
+'
 
 test_expect_success \
-    '[index v2] 6) verify-pack detects CRC mismatch' \
-    'rm -f .git/objects/pack/* &&
-     git index-pack --index-version=2 --stdin < "test-1-${pack1}.pack" &&
-     git verify-pack ".git/objects/pack/pack-${pack1}.pack" &&
-     obj=$(git hash-object file_001) &&
-     nr=$(index_obj_nr ".git/objects/pack/pack-${pack1}.idx" $obj) &&
-     chmod +w ".git/objects/pack/pack-${pack1}.idx" &&
-     printf xxxx | dd of=".git/objects/pack/pack-${pack1}.idx" conv=notrunc \
-        bs=1 count=4 seek=$((8 + 256 * 4 + $(wc -l <obj-list) * 20 + $nr * 4)) &&
-     ( while read obj
-       do git cat-file -p $obj >/dev/null || exit 1
-       done <obj-list ) &&
-     test_must_fail git verify-pack ".git/objects/pack/pack-${pack1}.pack"
+	'[index v2] 6) verify-pack detects CRC mismatch' '
+	rm -f .git/objects/pack/* &&
+	git index-pack --index-version=2 --stdin < "test-1-${pack1}.pack" &&
+	git verify-pack ".git/objects/pack/pack-${pack1}.pack" &&
+	obj=$(git hash-object file_001) &&
+	nr=$(index_obj_nr ".git/objects/pack/pack-${pack1}.idx" $obj) &&
+	chmod +w ".git/objects/pack/pack-${pack1}.idx" &&
+	printf xxxx | dd of=".git/objects/pack/pack-${pack1}.idx" conv=notrunc \
+		bs=1 count=4 seek=$((8 + 256 * 4 + $(wc -l <obj-list) * rawsz + $nr * 4)) &&
+	 ( while read obj
+	   do git cat-file -p $obj >/dev/null || exit 1
+	   done <obj-list ) &&
+	test_must_fail git verify-pack ".git/objects/pack/pack-${pack1}.pack"
 '
 
 test_expect_success 'running index-pack in the object store' '
-    rm -f .git/objects/pack/* &&
-    cp test-1-${pack1}.pack .git/objects/pack/pack-${pack1}.pack &&
-    (
-	cd .git/objects/pack &&
-	git index-pack pack-${pack1}.pack
-    ) &&
-    test -f .git/objects/pack/pack-${pack1}.idx
+	rm -f .git/objects/pack/* &&
+	cp test-1-${pack1}.pack .git/objects/pack/pack-${pack1}.pack &&
+	(
+		cd .git/objects/pack &&
+		git index-pack pack-${pack1}.pack
+	) &&
+	test -f .git/objects/pack/pack-${pack1}.idx
 '
 
 test_expect_success 'index-pack --strict warns upon missing tagger in tag' '
-    sha=$(git rev-parse HEAD) &&
-    cat >wrong-tag <<EOF &&
+	sha=$(git rev-parse HEAD) &&
+	cat >wrong-tag <<EOF &&
 object $sha
 type commit
 tag guten tag
@@ -253,18 +263,25 @@ tag guten tag
 This is an invalid tag.
 EOF
 
-    tag=$(git hash-object -t tag -w --stdin <wrong-tag) &&
-    pack1=$(echo $tag $sha | git pack-objects tag-test) &&
-    echo remove tag object &&
-    thirtyeight=${tag#??} &&
-    rm -f .git/objects/${tag%$thirtyeight}/$thirtyeight &&
-    git index-pack --strict tag-test-${pack1}.pack 2>err &&
-    grep "^warning:.* expected .tagger. line" err
+	tag=$(git hash-object -t tag -w --stdin <wrong-tag) &&
+	pack1=$(echo $tag $sha | git pack-objects tag-test) &&
+	echo remove tag object &&
+	thirtyeight=${tag#??} &&
+	rm -f .git/objects/${tag%$thirtyeight}/$thirtyeight &&
+	git index-pack --strict tag-test-${pack1}.pack 2>err &&
+	grep "^warning:.* expected .tagger. line" err
 '
 
 test_expect_success 'index-pack --fsck-objects also warns upon missing tagger in tag' '
-    git index-pack --fsck-objects tag-test-${pack1}.pack 2>err &&
-    grep "^warning:.* expected .tagger. line" err
+	git index-pack --fsck-objects tag-test-${pack1}.pack 2>err &&
+	grep "^warning:.* expected .tagger. line" err
+'
+
+test_expect_success 'index-pack -v --stdin produces progress for both phases' '
+	pack=$(git pack-objects --all pack </dev/null) &&
+	GIT_PROGRESS_DELAY=0 git index-pack -v --stdin <pack-$pack.pack 2>err &&
+	test_i18ngrep "Receiving objects" err &&
+	test_i18ngrep "Resolving deltas" err
 '
 
 test_done
diff --git a/third_party/git/t/t5307-pack-missing-commit.sh b/third_party/git/t/t5307-pack-missing-commit.sh
index dacb440b27..f4338abb78 100755
--- a/third_party/git/t/t5307-pack-missing-commit.sh
+++ b/third_party/git/t/t5307-pack-missing-commit.sh
@@ -24,11 +24,11 @@ test_expect_success 'check corruption' '
 '
 
 test_expect_success 'rev-list notices corruption (1)' '
-	test_must_fail env GIT_TEST_COMMIT_GRAPH=0 git rev-list HEAD
+	test_must_fail env GIT_TEST_COMMIT_GRAPH=0 git -c core.commitGraph=false rev-list HEAD
 '
 
 test_expect_success 'rev-list notices corruption (2)' '
-	test_must_fail env GIT_TEST_COMMIT_GRAPH=0 git rev-list --objects HEAD
+	test_must_fail env GIT_TEST_COMMIT_GRAPH=0 git -c core.commitGraph=false rev-list --objects HEAD
 '
 
 test_expect_success 'pack-objects notices corruption' '
diff --git a/third_party/git/t/t5308-pack-detect-duplicates.sh b/third_party/git/t/t5308-pack-detect-duplicates.sh
index 6845c1f3c3..693b2411c8 100755
--- a/third_party/git/t/t5308-pack-detect-duplicates.sh
+++ b/third_party/git/t/t5308-pack-detect-duplicates.sh
@@ -4,23 +4,27 @@ test_description='handling of duplicate objects in incoming packfiles'
 . ./test-lib.sh
 . "$TEST_DIRECTORY"/lib-pack.sh
 
-if ! test_have_prereq SHA1
-then
-       skip_all='not using SHA-1 for objects'
-       test_done
-fi
+test_expect_success 'setup' '
+	test_oid_cache <<-EOF
+	lo_oid sha1:e68fe8129b546b101aee9510c5328e7f21ca1d18
+	lo_oid sha256:471819e8c52bf11513f100b2810a8aa0622d5cd3d1c913758a071dd4b3bad8fe
+
+	missing_oid sha1:e69d000000000000000000000000000000000000
+	missing_oid sha256:4720000000000000000000000000000000000000000000000000000000000000
+	EOF
+'
 
 # The sha1s we have in our pack. It's important that these have the same
 # starting byte, so that they end up in the same fanout section of the index.
 # That lets us make sure we are exercising the binary search with both sets.
-LO_SHA1=e68fe8129b546b101aee9510c5328e7f21ca1d18
-HI_SHA1=e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
+LO_SHA1=$(test_oid lo_oid)
+HI_SHA1=$EMPTY_BLOB
 
 # And here's a "missing sha1" which will produce failed lookups. It must also
 # be in the same fanout section, and should be between the two (so that during
 # our binary search, we are sure to end up looking at one or the other of the
 # duplicate runs).
-MISSING_SHA1='e69d000000000000000000000000000000000000'
+MISSING_SHA1=$(test_oid missing_oid)
 
 # git will never intentionally create packfiles with
 # duplicate objects, so we have to construct them by hand.
diff --git a/third_party/git/t/t5309-pack-delta-cycles.sh b/third_party/git/t/t5309-pack-delta-cycles.sh
index 491556dad9..55b787630f 100755
--- a/third_party/git/t/t5309-pack-delta-cycles.sh
+++ b/third_party/git/t/t5309-pack-delta-cycles.sh
@@ -4,15 +4,9 @@ test_description='test index-pack handling of delta cycles in packfiles'
 . ./test-lib.sh
 . "$TEST_DIRECTORY"/lib-pack.sh
 
-if ! test_have_prereq SHA1
-then
-       skip_all='not using SHA-1 for objects'
-       test_done
-fi
-
 # Two similar-ish objects that we have computed deltas between.
-A=01d7713666f4de822776c7622c10f1b07de280dc
-B=e68fe8129b546b101aee9510c5328e7f21ca1d18
+A=$(test_oid packlib_7_0)
+B=$(test_oid packlib_7_76)
 
 # double-check our hand-constucted packs
 test_expect_success 'index-pack works with a single delta (A->B)' '
@@ -62,13 +56,13 @@ test_expect_success 'index-pack detects REF_DELTA cycles' '
 	test_must_fail git index-pack --fix-thin --stdin <cycle.pack
 '
 
-test_expect_failure 'failover to an object in another pack' '
+test_expect_success 'failover to an object in another pack' '
 	clear_packs &&
 	git index-pack --stdin <ab.pack &&
-	git index-pack --stdin --fix-thin <cycle.pack
+	test_must_fail git index-pack --stdin --fix-thin <cycle.pack
 '
 
-test_expect_failure 'failover to a duplicate object in the same pack' '
+test_expect_success 'failover to a duplicate object in the same pack' '
 	clear_packs &&
 	{
 		pack_header 3 &&
@@ -77,7 +71,7 @@ test_expect_failure 'failover to a duplicate object in the same pack' '
 		pack_obj $A
 	} >recoverable.pack &&
 	pack_trailer recoverable.pack &&
-	git index-pack --fix-thin --stdin <recoverable.pack
+	test_must_fail git index-pack --fix-thin --stdin <recoverable.pack
 '
 
 test_done
diff --git a/third_party/git/t/t5310-pack-bitmaps.sh b/third_party/git/t/t5310-pack-bitmaps.sh
index 6640329ebf..8318781d2b 100755
--- a/third_party/git/t/t5310-pack-bitmaps.sh
+++ b/third_party/git/t/t5310-pack-bitmaps.sh
@@ -74,16 +74,24 @@ rev_list_tests() {
 		test_cmp expect actual
 	'
 
-	test_expect_success "enumerate --objects ($state)" '
-		git rev-list --objects --use-bitmap-index HEAD >tmp &&
-		cut -d" " -f1 <tmp >tmp2 &&
-		sort <tmp2 >actual &&
-		git rev-list --objects HEAD >tmp &&
-		cut -d" " -f1 <tmp >tmp2 &&
-		sort <tmp2 >expect &&
+	test_expect_success "counting objects via bitmap ($state)" '
+		git rev-list --count --objects HEAD >expect &&
+		git rev-list --use-bitmap-index --count --objects HEAD >actual &&
 		test_cmp expect actual
 	'
 
+	test_expect_success "enumerate commits ($state)" '
+		git rev-list --use-bitmap-index HEAD >actual &&
+		git rev-list HEAD >expect &&
+		test_bitmap_traversal --no-confirm-bitmaps expect actual
+	'
+
+	test_expect_success "enumerate --objects ($state)" '
+		git rev-list --objects --use-bitmap-index HEAD >actual &&
+		git rev-list --objects HEAD >expect &&
+		test_bitmap_traversal expect actual
+	'
+
 	test_expect_success "bitmap --objects handles non-commit objects ($state)" '
 		git rev-list --objects --use-bitmap-index HEAD tagged-blob >actual &&
 		grep $blob actual
@@ -99,6 +107,20 @@ test_expect_success 'clone from bitmapped repository' '
 	test_cmp expect actual
 '
 
+test_expect_success 'partial clone from bitmapped repository' '
+	test_config uploadpack.allowfilter true &&
+	git clone --no-local --bare --filter=blob:none . partial-clone.git &&
+	(
+		cd partial-clone.git &&
+		pack=$(echo objects/pack/*.pack) &&
+		git verify-pack -v "$pack" >have &&
+		awk "/blob/ { print \$1 }" <have >blobs &&
+		# we expect this single blob because of the direct ref
+		git rev-parse refs/tags/tagged-blob >expect &&
+		test_cmp expect blobs
+	)
+'
+
 test_expect_success 'setup further non-bitmapped commits' '
 	test_commit_bulk --id=further 10
 '
diff --git a/third_party/git/t/t5313-pack-bounds-checks.sh b/third_party/git/t/t5313-pack-bounds-checks.sh
index f1708d415e..535313e4dc 100755
--- a/third_party/git/t/t5313-pack-bounds-checks.sh
+++ b/third_party/git/t/t5313-pack-bounds-checks.sh
@@ -38,16 +38,26 @@ munge () {
 # for the initial, and another ofs(4*nr) past that for the extended.
 #
 ofs_table () {
-	echo $((4 + 4 + 4*256 + 20*$1 + 4*$1))
+	echo $((4 + 4 + 4*256 + $(test_oid rawsz)*$1 + 4*$1))
 }
 extended_table () {
 	echo $(($(ofs_table "$1") + 4*$1))
 }
 
+test_expect_success 'setup' '
+	test_oid_cache <<-EOF
+	oid000 sha1:1485
+	oid000 sha256:4222
+
+	oidfff sha1:74
+	oidfff sha256:1350
+	EOF
+'
+
 test_expect_success 'set up base packfile and variables' '
 	# the hash of this content starts with ff, which
 	# makes some later computations much simpler
-	echo 74 >file &&
+	echo $(test_oid oidfff) >file &&
 	git add file &&
 	git commit -m base &&
 	git repack -ad &&
@@ -140,10 +150,10 @@ test_expect_success 'bogus offset inside v2 extended table' '
 	# an extended table (if the first object were larger than 2^31).
 	#
 	# Note that the value is important here. We want $object as
-	# the second entry in sorted-sha1 order. The sha1 of 1485 starts
+	# the second entry in sorted-hash order. The hash of this object starts
 	# with "000", which sorts before that of $object (which starts
 	# with "fff").
-	second=$(echo 1485 | git hash-object -w --stdin) &&
+	second=$(test_oid oid000 | git hash-object -w --stdin) &&
 	do_pack "$object $second" --index-version=2 &&
 
 	# We have to make extra room for the table, so we cannot
diff --git a/third_party/git/t/t5314-pack-cycle-detection.sh b/third_party/git/t/t5314-pack-cycle-detection.sh
index e525466de0..0aec8619e2 100755
--- a/third_party/git/t/t5314-pack-cycle-detection.sh
+++ b/third_party/git/t/t5314-pack-cycle-detection.sh
@@ -53,7 +53,7 @@ immediately after the lookup for "dummy".
 
 
 
-# Create a pack containing the the tree $1 and blob $1:file, with
+# Create a pack containing the tree $1 and blob $1:file, with
 # the latter stored as a delta against $2:file.
 #
 # We convince pack-objects to make the delta in the direction of our choosing
diff --git a/third_party/git/t/t5317-pack-objects-filter-objects.sh b/third_party/git/t/t5317-pack-objects-filter-objects.sh
index 2d2f5d0229..dc0446574b 100755
--- a/third_party/git/t/t5317-pack-objects-filter-objects.sh
+++ b/third_party/git/t/t5317-pack-objects-filter-objects.sh
@@ -45,12 +45,7 @@ test_expect_success 'verify blob:none packfile has no blobs' '
 	git -C r1 index-pack ../filter.pack &&
 
 	git -C r1 verify-pack -v ../filter.pack >verify_result &&
-	grep blob verify_result |
-	awk -f print_1.awk |
-	sort >observed &&
-
-	nr=$(wc -l <observed) &&
-	test 0 -eq $nr
+	! grep blob verify_result
 '
 
 test_expect_success 'verify normal and blob:none packfiles have same commits/trees' '
@@ -72,7 +67,8 @@ test_expect_success 'get an error for missing tree object' '
 	echo foo >r5/foo &&
 	git -C r5 add foo &&
 	git -C r5 commit -m "foo" &&
-	del=$(git -C r5 rev-parse HEAD^{tree} | sed "s|..|&/|") &&
+	git -C r5 rev-parse HEAD^{tree} >tree &&
+	del=$(sed "s|..|&/|" tree) &&
 	rm r5/.git/objects/$del &&
 	test_must_fail git -C r5 pack-objects --revs --stdout 2>bad_tree <<-EOF &&
 	HEAD
@@ -148,12 +144,7 @@ test_expect_success 'verify blob:limit=500 omits all blobs' '
 	git -C r2 index-pack ../filter.pack &&
 
 	git -C r2 verify-pack -v ../filter.pack >verify_result &&
-	grep blob verify_result |
-	awk -f print_1.awk |
-	sort >observed &&
-
-	nr=$(wc -l <observed) &&
-	test 0 -eq $nr
+	! grep blob verify_result
 '
 
 test_expect_success 'verify blob:limit=1000' '
@@ -163,12 +154,7 @@ test_expect_success 'verify blob:limit=1000' '
 	git -C r2 index-pack ../filter.pack &&
 
 	git -C r2 verify-pack -v ../filter.pack >verify_result &&
-	grep blob verify_result |
-	awk -f print_1.awk |
-	sort >observed &&
-
-	nr=$(wc -l <observed) &&
-	test 0 -eq $nr
+	! grep blob verify_result
 '
 
 test_expect_success 'verify blob:limit=1001' '
@@ -230,10 +216,9 @@ test_expect_success 'verify explicitly specifying oversized blob in input' '
 	awk -f print_2.awk ls_files_result |
 	sort >expected &&
 
-	git -C r2 pack-objects --revs --stdout --filter=blob:limit=1k >filter.pack <<-EOF &&
-	HEAD
-	$(git -C r2 rev-parse HEAD:large.10000)
-	EOF
+	echo HEAD >objects &&
+	git -C r2 rev-parse HEAD:large.10000 >>objects &&
+	git -C r2 pack-objects --revs --stdout --filter=blob:limit=1k <objects >filter.pack &&
 	git -C r2 index-pack ../filter.pack &&
 
 	git -C r2 verify-pack -v ../filter.pack >verify_result &&
@@ -377,7 +362,8 @@ test_expect_success 'verify sparse:oid=OID' '
 	awk -f print_2.awk ls_files_result |
 	sort >expected &&
 
-	oid=$(git -C r4 ls-files -s pattern | awk -f print_2.awk) &&
+	git -C r4 ls-files -s pattern >staged &&
+	oid=$(awk -f print_2.awk staged) &&
 	git -C r4 pack-objects --revs --stdout --filter=sparse:oid=$oid >filter.pack <<-EOF &&
 	HEAD
 	EOF
diff --git a/third_party/git/t/t5318-commit-graph.sh b/third_party/git/t/t5318-commit-graph.sh
index 22cb9d6643..2ed0c1544d 100755
--- a/third_party/git/t/t5318-commit-graph.sh
+++ b/third_party/git/t/t5318-commit-graph.sh
@@ -3,13 +3,23 @@
 test_description='commit graph'
 . ./test-lib.sh
 
+GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS=0
+
 test_expect_success 'setup full repo' '
 	mkdir full &&
 	cd "$TRASH_DIRECTORY/full" &&
 	git init &&
 	git config core.commitGraph true &&
 	objdir=".git/objects" &&
-	test_oid_init
+
+	test_oid_cache <<-EOF
+	oid_version sha1:1
+	oid_version sha256:2
+	EOF
+'
+
+test_expect_success POSIXPERM 'tweak umask for modebit tests' '
+	umask 022
 '
 
 test_expect_success 'verify graph with no graph file' '
@@ -19,15 +29,14 @@ test_expect_success 'verify graph with no graph file' '
 
 test_expect_success 'write graph with no packs' '
 	cd "$TRASH_DIRECTORY/full" &&
-	git commit-graph write --object-dir . &&
-	test_path_is_missing info/commit-graph
+	git commit-graph write --object-dir $objdir &&
+	test_path_is_missing $objdir/info/commit-graph
 '
 
-test_expect_success 'close with correct error on bad input' '
+test_expect_success 'exit with correct error on bad input to --stdin-packs' '
 	cd "$TRASH_DIRECTORY/full" &&
 	echo doesnotexist >in &&
-	{ git commit-graph write --stdin-packs <in 2>stderr; ret=$?; } &&
-	test "$ret" = 1 &&
+	test_expect_code 1 git commit-graph write --stdin-packs <in 2>stderr &&
 	test_i18ngrep "error adding pack" stderr
 '
 
@@ -73,14 +82,30 @@ graph_read_expect() {
 		NUM_CHUNKS=$((3 + $(echo "$2" | wc -w)))
 	fi
 	cat >expect <<- EOF
-	header: 43475048 1 1 $NUM_CHUNKS 0
+	header: 43475048 1 $(test_oid oid_version) $NUM_CHUNKS 0
 	num_commits: $1
 	chunks: oid_fanout oid_lookup commit_metadata$OPTIONAL
 	EOF
-	git commit-graph read >output &&
+	test-tool read-graph >output &&
 	test_cmp expect output
 }
 
+test_expect_success 'exit with correct error on bad input to --stdin-commits' '
+	cd "$TRASH_DIRECTORY/full" &&
+	# invalid, non-hex OID
+	echo HEAD >in &&
+	test_expect_code 1 git commit-graph write --stdin-commits <in 2>stderr &&
+	test_i18ngrep "unexpected non-hex object ID: HEAD" stderr &&
+	# non-existent OID
+	echo $ZERO_OID >in &&
+	test_expect_code 1 git commit-graph write --stdin-commits <in 2>stderr &&
+	test_i18ngrep "invalid object" stderr &&
+	# valid commit and tree OID
+	git rev-parse HEAD HEAD^{tree} >in &&
+	git commit-graph write --stdin-commits <in &&
+	graph_read_expect 3
+'
+
 test_expect_success 'write graph' '
 	cd "$TRASH_DIRECTORY/full" &&
 	git commit-graph write &&
@@ -88,6 +113,13 @@ test_expect_success 'write graph' '
 	graph_read_expect "3"
 '
 
+test_expect_success POSIXPERM 'write graph has correct permissions' '
+	test_path_is_file $objdir/info/commit-graph &&
+	echo "-r--r--r--" >expect &&
+	test_modebits $objdir/info/commit-graph >actual &&
+	test_cmp expect actual
+'
+
 graph_git_behavior 'graph exists' full commits/3 commits/1
 
 test_expect_success 'Add more commits' '
@@ -116,6 +148,63 @@ test_expect_success 'Add more commits' '
 	git repack
 '
 
+test_expect_success 'commit-graph write progress off for redirected stderr' '
+	cd "$TRASH_DIRECTORY/full" &&
+	git commit-graph write 2>err &&
+	test_must_be_empty err
+'
+
+test_expect_success 'commit-graph write force progress on for stderr' '
+	cd "$TRASH_DIRECTORY/full" &&
+	GIT_PROGRESS_DELAY=0 git commit-graph write --progress 2>err &&
+	test_file_not_empty err
+'
+
+test_expect_success 'commit-graph write with the --no-progress option' '
+	cd "$TRASH_DIRECTORY/full" &&
+	git commit-graph write --no-progress 2>err &&
+	test_must_be_empty err
+'
+
+test_expect_success 'commit-graph write --stdin-commits progress off for redirected stderr' '
+	cd "$TRASH_DIRECTORY/full" &&
+	git rev-parse commits/5 >in &&
+	git commit-graph write --stdin-commits <in 2>err &&
+	test_must_be_empty err
+'
+
+test_expect_success 'commit-graph write --stdin-commits force progress on for stderr' '
+	cd "$TRASH_DIRECTORY/full" &&
+	git rev-parse commits/5 >in &&
+	GIT_PROGRESS_DELAY=0 git commit-graph write --stdin-commits --progress <in 2>err &&
+	test_i18ngrep "Collecting commits from input" err
+'
+
+test_expect_success 'commit-graph write --stdin-commits with the --no-progress option' '
+	cd "$TRASH_DIRECTORY/full" &&
+	git rev-parse commits/5 >in &&
+	git commit-graph write --stdin-commits --no-progress <in 2>err &&
+	test_must_be_empty err
+'
+
+test_expect_success 'commit-graph verify progress off for redirected stderr' '
+	cd "$TRASH_DIRECTORY/full" &&
+	git commit-graph verify 2>err &&
+	test_must_be_empty err
+'
+
+test_expect_success 'commit-graph verify force progress on for stderr' '
+	cd "$TRASH_DIRECTORY/full" &&
+	GIT_PROGRESS_DELAY=0 git commit-graph verify --progress 2>err &&
+	test_file_not_empty err
+'
+
+test_expect_success 'commit-graph verify with the --no-progress option' '
+	cd "$TRASH_DIRECTORY/full" &&
+	git commit-graph verify --no-progress 2>err &&
+	test_must_be_empty err
+'
+
 # Current graph structure:
 #
 #   __M3___
@@ -328,6 +417,35 @@ test_expect_success 'replace-objects invalidates commit-graph' '
 	)
 '
 
+test_expect_success 'warn on improper hash version' '
+	git init --object-format=sha1 sha1 &&
+	(
+		cd sha1 &&
+		test_commit 1 &&
+		git commit-graph write --reachable &&
+		mv .git/objects/info/commit-graph ../cg-sha1
+	) &&
+	git init --object-format=sha256 sha256 &&
+	(
+		cd sha256 &&
+		test_commit 1 &&
+		git commit-graph write --reachable &&
+		mv .git/objects/info/commit-graph ../cg-sha256
+	) &&
+	(
+		cd sha1 &&
+		mv ../cg-sha256 .git/objects/info/commit-graph &&
+		git log -1 2>err &&
+		test_i18ngrep "commit-graph hash version 2 does not match version 1" err
+	) &&
+	(
+		cd sha256 &&
+		mv ../cg-sha1 .git/objects/info/commit-graph &&
+		git log -1 2>err &&
+		test_i18ngrep "commit-graph hash version 1 does not match version 2" err
+	)
+'
+
 # the verify tests below expect the commit-graph to contain
 # exactly the commits reachable from the commits/8 branch.
 # If the file changes the set of commits in the list, then the
@@ -377,7 +495,8 @@ GRAPH_BYTE_FOOTER=$(($GRAPH_OCTOPUS_DATA_OFFSET + 4 * $NUM_OCTOPUS_EDGES))
 corrupt_graph_setup() {
 	cd "$TRASH_DIRECTORY/full" &&
 	test_when_finished mv commit-graph-backup $objdir/info/commit-graph &&
-	cp $objdir/info/commit-graph commit-graph-backup
+	cp $objdir/info/commit-graph commit-graph-backup &&
+	chmod u+w $objdir/info/commit-graph
 }
 
 corrupt_graph_verify() {
@@ -390,7 +509,8 @@ corrupt_graph_verify() {
 		cp $objdir/info/commit-graph commit-graph-pre-write-test
 	fi &&
 	git status --short &&
-	GIT_TEST_COMMIT_GRAPH_DIE_ON_LOAD=true git commit-graph write &&
+	GIT_TEST_COMMIT_GRAPH_DIE_ON_PARSE=true git commit-graph write &&
+	chmod u+w $objdir/info/commit-graph &&
 	git commit-graph verify
 }
 
@@ -437,12 +557,12 @@ test_expect_success 'detect bad version' '
 '
 
 test_expect_success 'detect bad hash version' '
-	corrupt_graph_and_verify $GRAPH_BYTE_HASH "\02" \
+	corrupt_graph_and_verify $GRAPH_BYTE_HASH "\03" \
 		"hash version"
 '
 
 test_expect_success 'detect low chunk count' '
-	corrupt_graph_and_verify $GRAPH_BYTE_CHUNK_COUNT "\02" \
+	corrupt_graph_and_verify $GRAPH_BYTE_CHUNK_COUNT "\01" \
 		"missing the .* chunk"
 '
 
@@ -528,7 +648,8 @@ test_expect_success 'detect invalid checksum hash' '
 
 test_expect_success 'detect incorrect chunk count' '
 	corrupt_graph_and_verify $GRAPH_BYTE_CHUNK_COUNT "\377" \
-		"chunk lookup table entry missing" $GRAPH_CHUNK_LOOKUP_OFFSET
+		"commit-graph file is too small to hold [0-9]* chunks" \
+		$GRAPH_CHUNK_LOOKUP_OFFSET
 '
 
 test_expect_success 'git fsck (checks commit-graph)' '
@@ -577,4 +698,47 @@ test_expect_success 'get_commit_tree_in_graph works for non-the_repository' '
 	test_cmp expect actual
 '
 
+test_expect_success 'corrupt commit-graph write (broken parent)' '
+	rm -rf repo &&
+	git init repo &&
+	(
+		cd repo &&
+		empty="$(git mktree </dev/null)" &&
+		cat >broken <<-EOF &&
+		tree $empty
+		parent $ZERO_OID
+		author whatever <whatever@example.com> 1234 -0000
+		committer whatever <whatever@example.com> 1234 -0000
+
+		broken commit
+		EOF
+		broken="$(git hash-object -w -t commit --literally broken)" &&
+		git commit-tree -p "$broken" -m "good commit" "$empty" >good &&
+		test_must_fail git commit-graph write --stdin-commits \
+			<good 2>test_err &&
+		test_i18ngrep "unable to parse commit" test_err
+	)
+'
+
+test_expect_success 'corrupt commit-graph write (missing tree)' '
+	rm -rf repo &&
+	git init repo &&
+	(
+		cd repo &&
+		tree="$(git mktree </dev/null)" &&
+		cat >broken <<-EOF &&
+		parent $ZERO_OID
+		author whatever <whatever@example.com> 1234 -0000
+		committer whatever <whatever@example.com> 1234 -0000
+
+		broken commit
+		EOF
+		broken="$(git hash-object -w -t commit --literally broken)" &&
+		git commit-tree -p "$broken" -m "good" "$tree" >good &&
+		test_must_fail git commit-graph write --stdin-commits \
+			<good 2>test_err &&
+		test_i18ngrep "unable to parse commit" test_err
+	)
+'
+
 test_done
diff --git a/third_party/git/t/t5319-multi-pack-index.sh b/third_party/git/t/t5319-multi-pack-index.sh
index c72ca04399..f340b376bc 100755
--- a/third_party/git/t/t5319-multi-pack-index.sh
+++ b/third_party/git/t/t5319-multi-pack-index.sh
@@ -5,6 +5,8 @@ test_description='multi-pack-indexes'
 
 objdir=.git/objects
 
+HASH_LEN=$(test_oid rawsz)
+
 midx_read_expect () {
 	NUM_PACKS=$1
 	NUM_OBJECTS=$2
@@ -13,7 +15,7 @@ midx_read_expect () {
 	EXTRA_CHUNKS="$5"
 	{
 		cat <<-EOF &&
-		header: 4d494458 1 $NUM_CHUNKS $NUM_PACKS
+		header: 4d494458 1 $HASH_LEN $NUM_CHUNKS $NUM_PACKS
 		chunks: pack-names oid-fanout oid-lookup object-offsets$EXTRA_CHUNKS
 		num_objects: $NUM_OBJECTS
 		packs:
@@ -28,10 +30,28 @@ midx_read_expect () {
 	test_cmp expect actual
 }
 
-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 'setup' '
+	test_oid_cache <<-EOF
+	idxoff sha1:2999
+	idxoff sha256:3739
+
+	packnameoff sha1:652
+	packnameoff sha256:940
+
+	fanoutoff sha1:1
+	fanoutoff sha256:3
+	EOF
+'
+
+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 SHA1 '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 () {
@@ -147,6 +167,21 @@ test_expect_success 'write midx with two packs' '
 
 compare_results_with_midx "two packs"
 
+test_expect_success 'write progress off for redirected stderr' '
+	git multi-pack-index --object-dir=$objdir write 2>err &&
+	test_line_count = 0 err
+'
+
+test_expect_success 'write force progress on for stderr' '
+	git multi-pack-index --object-dir=$objdir --progress write 2>err &&
+	test_file_not_empty err
+'
+
+test_expect_success 'write with the --no-progress option' '
+	git multi-pack-index --object-dir=$objdir --no-progress write 2>err &&
+	test_line_count = 0 err
+'
+
 test_expect_success 'add more packs' '
 	for j in $(test_seq 11 20)
 	do
@@ -165,10 +200,59 @@ test_expect_success 'write midx with twelve packs' '
 
 compare_results_with_midx "twelve packs"
 
+test_expect_success 'warn on improper hash version' '
+	git init --object-format=sha1 sha1 &&
+	(
+		cd sha1 &&
+		git config core.multiPackIndex true &&
+		test_commit 1 &&
+		git repack -a &&
+		git multi-pack-index write &&
+		mv .git/objects/pack/multi-pack-index ../mpi-sha1
+	) &&
+	git init --object-format=sha256 sha256 &&
+	(
+		cd sha256 &&
+		git config core.multiPackIndex true &&
+		test_commit 1 &&
+		git repack -a &&
+		git multi-pack-index write &&
+		mv .git/objects/pack/multi-pack-index ../mpi-sha256
+	) &&
+	(
+		cd sha1 &&
+		mv ../mpi-sha256 .git/objects/pack/multi-pack-index &&
+		git log -1 2>err &&
+		test_i18ngrep "multi-pack-index hash version 2 does not match version 1" err
+	) &&
+	(
+		cd sha256 &&
+		mv ../mpi-sha1 .git/objects/pack/multi-pack-index &&
+		git log -1 2>err &&
+		test_i18ngrep "multi-pack-index hash version 1 does not match version 2" err
+	)
+'
+
+
 test_expect_success 'verify multi-pack-index success' '
 	git multi-pack-index verify --object-dir=$objdir
 '
 
+test_expect_success 'verify progress off for redirected stderr' '
+	git multi-pack-index verify --object-dir=$objdir 2>err &&
+	test_line_count = 0 err
+'
+
+test_expect_success 'verify force progress on for stderr' '
+	git multi-pack-index verify --object-dir=$objdir --progress 2>err &&
+	test_file_not_empty err
+'
+
+test_expect_success 'verify with the --no-progress option' '
+	git multi-pack-index verify --object-dir=$objdir --no-progress 2>err &&
+	test_line_count = 0 err
+'
+
 # usage: corrupt_midx_and_verify <pos> <data> <objdir> <string>
 corrupt_midx_and_verify() {
 	POS=$1 &&
@@ -195,7 +279,6 @@ test_expect_success 'verify bad signature' '
 		"multi-pack-index signature"
 '
 
-HASH_LEN=20
 NUM_OBJECTS=74
 MIDX_BYTE_VERSION=4
 MIDX_BYTE_OID_VERSION=5
@@ -208,9 +291,9 @@ MIDX_CHUNK_LOOKUP_WIDTH=12
 MIDX_OFFSET_PACKNAMES=$(($MIDX_HEADER_SIZE + \
 			 $MIDX_NUM_CHUNKS * $MIDX_CHUNK_LOOKUP_WIDTH))
 MIDX_BYTE_PACKNAME_ORDER=$(($MIDX_OFFSET_PACKNAMES + 2))
-MIDX_OFFSET_OID_FANOUT=$(($MIDX_OFFSET_PACKNAMES + 652))
+MIDX_OFFSET_OID_FANOUT=$(($MIDX_OFFSET_PACKNAMES + $(test_oid packnameoff)))
 MIDX_OID_FANOUT_WIDTH=4
-MIDX_BYTE_OID_FANOUT_ORDER=$((MIDX_OFFSET_OID_FANOUT + 250 * $MIDX_OID_FANOUT_WIDTH + 1))
+MIDX_BYTE_OID_FANOUT_ORDER=$((MIDX_OFFSET_OID_FANOUT + 250 * $MIDX_OID_FANOUT_WIDTH + $(test_oid fanoutoff)))
 MIDX_OFFSET_OID_LOOKUP=$(($MIDX_OFFSET_OID_FANOUT + 256 * $MIDX_OID_FANOUT_WIDTH))
 MIDX_BYTE_OID_LOOKUP=$(($MIDX_OFFSET_OID_LOOKUP + 16 * $HASH_LEN))
 MIDX_OFFSET_OBJECT_OFFSETS=$(($MIDX_OFFSET_OID_LOOKUP + $NUM_OBJECTS * $HASH_LEN))
@@ -224,7 +307,7 @@ test_expect_success 'verify bad version' '
 '
 
 test_expect_success 'verify bad OID version' '
-	corrupt_midx_and_verify $MIDX_BYTE_OID_VERSION "\02" $objdir \
+	corrupt_midx_and_verify $MIDX_BYTE_OID_VERSION "\03" $objdir \
 		"hash version"
 '
 
@@ -274,22 +357,77 @@ test_expect_success 'verify incorrect pack-int-id' '
 '
 
 test_expect_success 'verify incorrect offset' '
-	corrupt_midx_and_verify $MIDX_BYTE_OFFSET "\07" $objdir \
+	corrupt_midx_and_verify $MIDX_BYTE_OFFSET "\377" $objdir \
 		"incorrect object offset"
 '
 
 test_expect_success 'git-fsck incorrect offset' '
-	corrupt_midx_and_verify $MIDX_BYTE_OFFSET "\07" $objdir \
+	corrupt_midx_and_verify $MIDX_BYTE_OFFSET "\377" $objdir \
 		"incorrect object offset" \
 		"git -c core.multipackindex=true fsck"
 '
 
-test_expect_success 'repack removes multi-pack-index' '
+test_expect_success 'repack progress off for redirected stderr' '
+	git multi-pack-index --object-dir=$objdir repack 2>err &&
+	test_line_count = 0 err
+'
+
+test_expect_success 'repack force progress on for stderr' '
+	git multi-pack-index --object-dir=$objdir --progress repack 2>err &&
+	test_file_not_empty err
+'
+
+test_expect_success 'repack with the --no-progress option' '
+	git multi-pack-index --object-dir=$objdir --no-progress repack 2>err &&
+	test_line_count = 0 err
+'
+
+test_expect_success 'repack removes multi-pack-index when deleting packs' '
 	test_path_is_file $objdir/pack/multi-pack-index &&
-	GIT_TEST_MULTI_PACK_INDEX=0 git repack -adf &&
+	# Set GIT_TEST_MULTI_PACK_INDEX to 0 to avoid writing a new
+	# multi-pack-index after repacking, but set "core.multiPackIndex" to
+	# true so that "git repack" can read the existing MIDX.
+	GIT_TEST_MULTI_PACK_INDEX=0 git -c core.multiPackIndex repack -adf &&
 	test_path_is_missing $objdir/pack/multi-pack-index
 '
 
+test_expect_success 'repack preserves multi-pack-index when creating packs' '
+	git init preserve &&
+	test_when_finished "rm -fr preserve" &&
+	(
+		cd preserve &&
+		packdir=.git/objects/pack &&
+		midx=$packdir/multi-pack-index &&
+
+		test_commit 1 &&
+		pack1=$(git pack-objects --all $packdir/pack) &&
+		touch $packdir/pack-$pack1.keep &&
+		test_commit 2 &&
+		pack2=$(git pack-objects --revs $packdir/pack) &&
+		touch $packdir/pack-$pack2.keep &&
+
+		git multi-pack-index write &&
+		cp $midx $midx.bak &&
+
+		cat >pack-input <<-EOF &&
+		HEAD
+		^HEAD~1
+		EOF
+		test_commit 3 &&
+		pack3=$(git pack-objects --revs $packdir/pack <pack-input) &&
+		test_commit 4 &&
+		pack4=$(git pack-objects --revs $packdir/pack <pack-input) &&
+
+		GIT_TEST_MULTI_PACK_INDEX=0 git -c core.multiPackIndex repack -ad &&
+		ls -la $packdir &&
+		test_path_is_file $packdir/pack-$pack1.pack &&
+		test_path_is_file $packdir/pack-$pack2.pack &&
+		test_path_is_missing $packdir/pack-$pack3.pack &&
+		test_path_is_missing $packdir/pack-$pack4.pack &&
+		test_cmp_bin $midx.bak $midx
+	)
+'
+
 compare_results_with_midx "after repack"
 
 test_expect_success 'multi-pack-index and pack-bitmap' '
@@ -342,7 +480,7 @@ test_expect_success 'force some 64-bit offsets with pack-objects' '
 	pack64=$(git pack-objects --index-version=2,0x40 objects64/pack/test-64 <obj-list) &&
 	idx64=objects64/pack/test-64-$pack64.idx &&
 	chmod u+w $idx64 &&
-	corrupt_data $idx64 2999 "\02" &&
+	corrupt_data $idx64 $(test_oid idxoff) "\02" &&
 	midx64=$(git multi-pack-index --object-dir=objects64 write) &&
 	midx_read_expect 1 63 5 objects64 " large-offsets"
 '
@@ -413,6 +551,30 @@ test_expect_success 'expire does not remove any packs' '
 	)
 '
 
+test_expect_success 'expire progress off for redirected stderr' '
+	(
+		cd dup &&
+		git multi-pack-index expire 2>err &&
+		test_line_count = 0 err
+	)
+'
+
+test_expect_success 'expire force progress on for stderr' '
+	(
+		cd dup &&
+		git multi-pack-index --progress expire 2>err &&
+		test_file_not_empty err
+	)
+'
+
+test_expect_success 'expire with the --no-progress option' '
+	(
+		cd dup &&
+		git multi-pack-index --no-progress expire 2>err &&
+		test_line_count = 0 err
+	)
+'
+
 test_expect_success 'expire removes unreferenced packs' '
 	(
 		cd dup &&
@@ -438,10 +600,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 &&
@@ -450,6 +612,33 @@ test_expect_success 'repack with minimum size does not alter existing packs' '
 	)
 '
 
+test_expect_success 'repack respects repack.packKeptObjects=false' '
+	test_when_finished rm -f dup/.git/objects/pack/*keep &&
+	(
+		cd dup &&
+		ls .git/objects/pack/*idx >idx-list &&
+		test_line_count = 5 idx-list &&
+		ls .git/objects/pack/*.pack | sed "s/\.pack/.keep/" >keep-list &&
+		test_line_count = 5 keep-list &&
+		for keep in $(cat keep-list)
+		do
+			touch $keep || return 1
+		done &&
+		git multi-pack-index repack --batch-size=0 &&
+		ls .git/objects/pack/*idx >idx-list &&
+		test_line_count = 5 idx-list &&
+		test-tool read-midx .git/objects | grep idx >midx-list &&
+		test_line_count = 5 midx-list &&
+		THIRD_SMALLEST_SIZE=$(test-tool path-utils file-size .git/objects/pack/*pack | sort -n | sed -n 3p) &&
+		BATCH_SIZE=$((THIRD_SMALLEST_SIZE + 1)) &&
+		git multi-pack-index repack --batch-size=$BATCH_SIZE &&
+		ls .git/objects/pack/*idx >idx-list &&
+		test_line_count = 5 idx-list &&
+		test-tool read-midx .git/objects | grep idx >midx-list &&
+		test_line_count = 5 midx-list
+	)
+'
+
 test_expect_success 'repack creates a new pack' '
 	(
 		cd dup &&
@@ -528,6 +717,7 @@ test_expect_success 'expire respects .keep files' '
 '
 
 test_expect_success 'repack --batch-size=0 repacks everything' '
+	cp -r dup dup2 &&
 	(
 		cd dup &&
 		rm .git/objects/pack/*.keep &&
@@ -547,4 +737,21 @@ test_expect_success 'repack --batch-size=0 repacks everything' '
 	)
 '
 
+test_expect_success 'repack --batch-size=<large> repacks everything' '
+	(
+		cd dup2 &&
+		rm .git/objects/pack/*.keep &&
+		ls .git/objects/pack/*idx >idx-list &&
+		test_line_count = 2 idx-list &&
+		git multi-pack-index repack --batch-size=2000000 &&
+		ls .git/objects/pack/*idx >idx-list &&
+		test_line_count = 3 idx-list &&
+		test-tool read-midx .git/objects | grep idx >midx-list &&
+		test_line_count = 3 midx-list &&
+		git multi-pack-index expire &&
+		ls -al .git/objects/pack/*idx >idx-list &&
+		test_line_count = 1 idx-list
+	)
+'
+
 test_done
diff --git a/third_party/git/t/t5319/no-objects.midx b/third_party/git/t/t5319/no-objects.midx
new file mode 100644
index 0000000000..e466b8e086
--- /dev/null
+++ b/third_party/git/t/t5319/no-objects.midx
Binary files differdiff --git a/third_party/git/t/t5321-pack-large-objects.sh b/third_party/git/t/t5321-pack-large-objects.sh
index a75eab87d3..8a56d98a0e 100755
--- a/third_party/git/t/t5321-pack-large-objects.sh
+++ b/third_party/git/t/t5321-pack-large-objects.sh
@@ -10,8 +10,8 @@ test_description='git pack-object with "large" deltas
 . "$TEST_DIRECTORY"/lib-pack.sh
 
 # Two similar-ish objects that we have computed deltas between.
-A=01d7713666f4de822776c7622c10f1b07de280dc
-B=e68fe8129b546b101aee9510c5328e7f21ca1d18
+A=$(test_oid packlib_7_0)
+B=$(test_oid packlib_7_76)
 
 test_expect_success 'setup' '
 	clear_packs &&
diff --git a/third_party/git/t/t5322-pack-objects-sparse.sh b/third_party/git/t/t5322-pack-objects-sparse.sh
index 7124b5581a..a581eaf529 100755
--- a/third_party/git/t/t5322-pack-objects-sparse.sh
+++ b/third_party/git/t/t5322-pack-objects-sparse.sh
@@ -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
diff --git a/third_party/git/t/t5324-split-commit-graph.sh b/third_party/git/t/t5324-split-commit-graph.sh
index 99f4ef4c19..c334ee9155 100755
--- a/third_party/git/t/t5324-split-commit-graph.sh
+++ b/third_party/git/t/t5324-split-commit-graph.sh
@@ -4,13 +4,24 @@ test_description='split commit graph'
 . ./test-lib.sh
 
 GIT_TEST_COMMIT_GRAPH=0
+GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS=0
 
 test_expect_success 'setup repo' '
 	git init &&
 	git config core.commitGraph true &&
+	git config gc.writeCommitGraph false &&
 	infodir=".git/objects/info" &&
 	graphdir="$infodir/commit-graphs" &&
-	test_oid_init
+	test_oid_cache <<-EOM
+	shallow sha1:1760
+	shallow sha256:2064
+
+	base sha1:1376
+	base sha256:1496
+
+	oid_version sha1:1
+	oid_version sha256:2
+	EOM
 '
 
 graph_read_expect() {
@@ -20,14 +31,18 @@ graph_read_expect() {
 		NUM_BASE=$2
 	fi
 	cat >expect <<- EOF
-	header: 43475048 1 1 3 $NUM_BASE
+	header: 43475048 1 $(test_oid oid_version) 3 $NUM_BASE
 	num_commits: $1
 	chunks: oid_fanout oid_lookup commit_metadata
 	EOF
-	git commit-graph read >output &&
+	test-tool read-graph >output &&
 	test_cmp expect output
 }
 
+test_expect_success POSIXPERM 'tweak umask for modebit tests' '
+	umask 022
+'
+
 test_expect_success 'create commits and write commit-graph' '
 	for i in $(test_seq 3)
 	do
@@ -202,8 +217,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
 	) &&
@@ -247,7 +268,7 @@ test_expect_success 'verify hashes along chain, even in shallow' '
 		cd verify &&
 		git commit-graph verify &&
 		base_file=$graphdir/graph-$(head -n 1 $graphdir/commit-graph-chain).graph &&
-		corrupt_file "$base_file" 1760 "\01" &&
+		corrupt_file "$base_file" $(test_oid shallow) "\01" &&
 		test_must_fail git commit-graph verify --shallow 2>test_err &&
 		grep -v "^+" test_err >err &&
 		test_i18ngrep "incorrect checksum" err
@@ -274,7 +295,7 @@ test_expect_success 'warn on base graph chunk incorrect' '
 		cd base-chunk &&
 		git commit-graph verify &&
 		base_file=$graphdir/graph-$(tail -n 1 $graphdir/commit-graph-chain).graph &&
-		corrupt_file "$base_file" 1376 "\01" &&
+		corrupt_file "$base_file" $(test_oid base) "\01" &&
 		git commit-graph verify --shallow 2>test_err &&
 		grep -v "^+" test_err >err &&
 		test_i18ngrep "commit-graph chain does not match" err
@@ -319,7 +340,7 @@ test_expect_success 'add octopus merge' '
 	git merge commits/3 commits/4 &&
 	git branch merge/octopus &&
 	git commit-graph write --reachable --split &&
-	git commit-graph verify 2>err &&
+	git commit-graph verify --progress 2>err &&
 	test_line_count = 3 err &&
 	test_i18ngrep ! warning err &&
 	test_line_count = 3 $graphdir/commit-graph-chain
@@ -334,6 +355,7 @@ test_expect_success 'split across alternate where alternate is not split' '
 	git clone --no-hardlinks . alt-split &&
 	(
 		cd alt-split &&
+		rm -f .git/objects/info/commit-graph &&
 		echo "$(pwd)"/../.git/objects >.git/objects/info/alternates &&
 		test_commit 18 &&
 		git commit-graph write --reachable --split &&
@@ -342,4 +364,80 @@ test_expect_success 'split across alternate where alternate is not split' '
 	test_cmp commit-graph .git/objects/info/commit-graph
 '
 
+test_expect_success '--split=no-merge always writes an incremental' '
+	test_when_finished rm -rf a b &&
+	rm -rf $graphdir $infodir/commit-graph &&
+	git reset --hard commits/2 &&
+	git rev-list HEAD~1 >a &&
+	git rev-list HEAD >b &&
+	git commit-graph write --split --stdin-commits <a &&
+	git commit-graph write --split=no-merge --stdin-commits <b &&
+	test_line_count = 2 $graphdir/commit-graph-chain
+'
+
+test_expect_success '--split=replace replaces the chain' '
+	rm -rf $graphdir $infodir/commit-graph &&
+	git reset --hard commits/3 &&
+	git rev-list -1 HEAD~2 >a &&
+	git rev-list -1 HEAD~1 >b &&
+	git rev-list -1 HEAD >c &&
+	git commit-graph write --split=no-merge --stdin-commits <a &&
+	git commit-graph write --split=no-merge --stdin-commits <b &&
+	git commit-graph write --split=no-merge --stdin-commits <c &&
+	test_line_count = 3 $graphdir/commit-graph-chain &&
+	git commit-graph write --stdin-commits --split=replace <b &&
+	test_path_is_missing $infodir/commit-graph &&
+	test_path_is_file $graphdir/commit-graph-chain &&
+	ls $graphdir/graph-*.graph >graph-files &&
+	test_line_count = 1 graph-files &&
+	verify_chain_files_exist $graphdir &&
+	graph_read_expect 2
+'
+
+test_expect_success ULIMIT_FILE_DESCRIPTORS 'handles file descriptor exhaustion' '
+	git init ulimit &&
+	(
+		cd ulimit &&
+		for i in $(test_seq 64)
+		do
+			test_commit $i &&
+			run_with_limited_open_files test_might_fail git commit-graph write \
+				--split=no-merge --reachable || return 1
+		done
+	)
+'
+
+while read mode modebits
+do
+	test_expect_success POSIXPERM "split commit-graph respects core.sharedrepository $mode" '
+		rm -rf $graphdir $infodir/commit-graph &&
+		git reset --hard commits/1 &&
+		test_config core.sharedrepository "$mode" &&
+		git commit-graph write --split --reachable &&
+		ls $graphdir/graph-*.graph >graph-files &&
+		test_line_count = 1 graph-files &&
+		echo "$modebits" >expect &&
+		test_modebits $graphdir/graph-*.graph >actual &&
+		test_cmp expect actual &&
+		test_modebits $graphdir/commit-graph-chain >actual &&
+		test_cmp expect actual
+	'
+done <<\EOF
+0666 -r--r--r--
+0600 -r--------
+EOF
+
+test_expect_success '--split=replace with partial Bloom data' '
+	rm -rf $graphdir $infodir/commit-graph &&
+	git reset --hard commits/3 &&
+	git rev-list -1 HEAD~2 >a &&
+	git rev-list -1 HEAD~1 >b &&
+	git commit-graph write --split=no-merge --stdin-commits --changed-paths <a &&
+	git commit-graph write --split=no-merge --stdin-commits <b &&
+	git commit-graph write --split=replace --stdin-commits --changed-paths <c &&
+	ls $graphdir/graph-*.graph >graph-files &&
+	test_line_count = 1 graph-files &&
+	verify_chain_files_exist $graphdir
+'
+
 test_done
diff --git a/third_party/git/t/t5400-send-pack.sh b/third_party/git/t/t5400-send-pack.sh
index 571d620aed..b84618c925 100755
--- a/third_party/git/t/t5400-send-pack.sh
+++ b/third_party/git/t/t5400-send-pack.sh
@@ -288,7 +288,7 @@ test_expect_success 'receive-pack de-dupes .have lines' '
 	$shared .have
 	EOF
 
-	GIT_TRACE_PACKET=$(pwd)/trace GIT_TEST_PROTOCOL_VERSION= \
+	GIT_TRACE_PACKET=$(pwd)/trace GIT_TEST_PROTOCOL_VERSION=0 \
 	    git push \
 		--receive-pack="unset GIT_TRACE_PACKET; git-receive-pack" \
 		fork HEAD:foo &&
diff --git a/third_party/git/t/t5407-post-rewrite-hook.sh b/third_party/git/t/t5407-post-rewrite-hook.sh
index 7344253bfb..80750a817e 100755
--- a/third_party/git/t/t5407-post-rewrite-hook.sh
+++ b/third_party/git/t/t5407-post-rewrite-hook.sh
@@ -53,10 +53,10 @@ test_expect_success 'git commit --amend --no-post-rewrite' '
 	test ! -f post-rewrite.data
 '
 
-test_expect_success 'git rebase' '
+test_expect_success 'git rebase --apply' '
 	git reset --hard D &&
 	clear_hook_input &&
-	test_must_fail git rebase --onto A B &&
+	test_must_fail git rebase --apply --onto A B &&
 	echo C > foo &&
 	git add foo &&
 	git rebase --continue &&
@@ -68,10 +68,10 @@ test_expect_success 'git rebase' '
 	verify_hook_input
 '
 
-test_expect_success 'git rebase --skip' '
+test_expect_success 'git rebase --apply --skip' '
 	git reset --hard D &&
 	clear_hook_input &&
-	test_must_fail git rebase --onto A B &&
+	test_must_fail git rebase --apply --onto A B &&
 	test_must_fail git rebase --skip &&
 	echo D > foo &&
 	git add foo &&
@@ -84,10 +84,10 @@ test_expect_success 'git rebase --skip' '
 	verify_hook_input
 '
 
-test_expect_success 'git rebase --skip the last one' '
+test_expect_success 'git rebase --apply --skip the last one' '
 	git reset --hard F &&
 	clear_hook_input &&
-	test_must_fail git rebase --onto D A &&
+	test_must_fail git rebase --apply --onto D A &&
 	git rebase --skip &&
 	echo rebase >expected.args &&
 	cat >expected.data <<-EOF &&
@@ -128,7 +128,7 @@ test_expect_success 'git rebase -m --skip' '
 	verify_hook_input
 '
 
-test_expect_success 'git rebase with implicit use of interactive backend' '
+test_expect_success 'git rebase with implicit use of merge backend' '
 	git reset --hard D &&
 	clear_hook_input &&
 	test_must_fail git rebase --keep-empty --onto A B &&
@@ -143,7 +143,7 @@ test_expect_success 'git rebase with implicit use of interactive backend' '
 	verify_hook_input
 '
 
-test_expect_success 'git rebase --skip with implicit use of interactive backend' '
+test_expect_success 'git rebase --skip with implicit use of merge backend' '
 	git reset --hard D &&
 	clear_hook_input &&
 	test_must_fail git rebase --keep-empty --onto A B &&
diff --git a/third_party/git/t/t5409-colorize-remote-messages.sh b/third_party/git/t/t5409-colorize-remote-messages.sh
index 2a8c449661..5d8f401d8e 100755
--- a/third_party/git/t/t5409-colorize-remote-messages.sh
+++ b/third_party/git/t/t5409-colorize-remote-messages.sh
@@ -56,14 +56,13 @@ test_expect_success 'short line' '
 
 test_expect_success 'case-insensitive' '
 	git --git-dir child/.git -c color.remote=always push -f origin HEAD:refs/heads/case-insensitive 2>output &&
-	cat output &&
 	test_decode_color <output >decoded &&
 	grep "<BOLD;RED>error<RESET>: error" decoded &&
 	grep "<BOLD;RED>ERROR<RESET>: also highlighted" decoded
 '
 
 test_expect_success 'leading space' '
-	git --git-dir child/.git -c color.remote=always push -f origin HEAD:refs/heads/leading-space 2>output &&        cat output &&
+	git --git-dir child/.git -c color.remote=always push -f origin HEAD:refs/heads/leading-space 2>output &&
 	test_decode_color <output >decoded &&
 	grep "  <BOLD;RED>error<RESET>: leading space" decoded
 '
diff --git a/third_party/git/t/t5411-proc-receive-hook.sh b/third_party/git/t/t5411-proc-receive-hook.sh
new file mode 100755
index 0000000000..746487286f
--- /dev/null
+++ b/third_party/git/t/t5411-proc-receive-hook.sh
@@ -0,0 +1,117 @@
+#!/bin/sh
+#
+# Copyright (c) 2020 Jiang Xin
+#
+
+test_description='Test proc-receive hook'
+
+. ./test-lib.sh
+
+. "$TEST_DIRECTORY"/t5411/common-functions.sh
+
+setup_upstream_and_workbench () {
+	# Refs of upstream : master(A)
+	# Refs of workbench: master(A)  tags/v123
+	test_expect_success "setup upstream and workbench" '
+		rm -rf upstream.git &&
+		rm -rf 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 tag -m "v123" v123 $A &&
+			git remote add origin ../upstream.git &&
+			git push origin master &&
+			git update-ref refs/heads/master $A $B &&
+			git -C ../upstream.git update-ref \
+				refs/heads/master $A $B
+		) &&
+		TAG=$(git -C workbench rev-parse v123) &&
+
+		# setup pre-receive hook
+		write_script upstream.git/hooks/pre-receive <<-\EOF &&
+		exec >&2
+		echo "# pre-receive hook"
+		while read old new ref
+		do
+			echo "pre-receive< $old $new $ref"
+		done
+		EOF
+
+		# setup post-receive hook
+		write_script upstream.git/hooks/post-receive <<-\EOF &&
+		exec >&2
+		echo "# post-receive hook"
+		while read old new ref
+		do
+			echo "post-receive< $old $new $ref"
+		done
+		EOF
+
+		upstream=upstream.git
+	'
+}
+
+run_proc_receive_hook_test() {
+	case $1 in
+	http)
+		PROTOCOL="HTTP protocol"
+		URL_PREFIX="http://.*"
+		;;
+	local)
+		PROTOCOL="builtin protocol"
+		URL_PREFIX="\.\."
+		;;
+	esac
+
+	# Include test cases for both file and HTTP protocol
+	for t in  "$TEST_DIRECTORY"/t5411/test-*.sh
+	do
+		. "$t"
+	done
+}
+
+# Initialize the upstream repository and local workbench.
+setup_upstream_and_workbench
+
+# Load test cases that only need to be executed once.
+for t in  "$TEST_DIRECTORY"/t5411/once-*.sh
+do
+	. "$t"
+done
+
+# Initialize the upstream repository and local workbench.
+setup_upstream_and_workbench
+
+# Run test cases for 'proc-receive' hook on local file protocol.
+run_proc_receive_hook_test local
+
+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
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+test_expect_success "setup for HTTP protocol" '
+	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/auth-push/smart/upstream.git" &&
+	set_askpass user@host pass@host
+'
+
+setup_askpass_helper
+
+# Run test cases for 'proc-receive' hook on HTTP protocol.
+run_proc_receive_hook_test http
+
+test_done
diff --git a/third_party/git/t/t5411/common-functions.sh b/third_party/git/t/t5411/common-functions.sh
new file mode 100644
index 0000000000..6580bebd8e
--- /dev/null
+++ b/third_party/git/t/t5411/common-functions.sh
@@ -0,0 +1,56 @@
+# 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.  Single quotes are replaced with double quotes, because
+# it is boring to prepare unquoted single quotes in expect text.  We also
+# remove some locale error messages, which break test if we turn on
+# `GIT_TEST_GETTEXT_POISON=true` in order to test unintentional translations
+# on plumbing commands.
+make_user_friendly_and_stable_output () {
+	sed \
+		-e "s/  *\$//" \
+		-e "s/   */ /g" \
+		-e "s/'/\"/g" \
+		-e "s/	/    /g" \
+		-e "s/$A/<COMMIT-A>/g" \
+		-e "s/$B/<COMMIT-B>/g" \
+		-e "s/$TAG/<TAG-v123>/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>#" \
+		-e "/^error: / d"
+}
diff --git a/third_party/git/t/t5411/once-0010-report-status-v1.sh b/third_party/git/t/t5411/once-0010-report-status-v1.sh
new file mode 100644
index 0000000000..dc2cf4a522
--- /dev/null
+++ b/third_party/git/t/t5411/once-0010-report-status-v1.sh
@@ -0,0 +1,94 @@
+test_expect_success "setup receive.procReceiveRefs" '
+	git -C "$upstream" config --add receive.procReceiveRefs refs/for
+'
+
+test_expect_success "setup proc-receive hook" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v \
+		-r "ok refs/for/master/topic1" \
+		-r "option fall-through" \
+		-r "ok refs/for/master/topic2" \
+		-r "option refname refs/for/changes/23/123/1" \
+		-r "option new-oid $A" \
+		-r "ok refs/for/master/topic2" \
+		-r "option refname refs/for/changes/24/124/2" \
+		-r "option old-oid $B" \
+		-r "option new-oid $A" \
+		-r "option forced-update" \
+		-r "ng refs/for/next/topic target branch not exist"
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         : (B)                   refs/for/master/topic1(A)  foo(A)  refs/for/next/topic(A)  refs/for/master/topic2(A)
+test_expect_success "proc-receive: report status v1" '
+	{
+		if test -z "$GIT_DEFAULT_HASH" || test "$GIT_DEFAULT_HASH" = "sha1"
+		then
+			printf "%s %s refs/heads/master\0report-status\n" \
+				$A $B | packetize
+		else
+			printf "%s %s refs/heads/master\0report-status object-format=$GIT_DEFAULT_HASH\n" \
+				$A $B | packetize
+		fi &&
+		printf "%s %s refs/for/master/topic1\n" \
+			$ZERO_OID $A | packetize &&
+		printf "%s %s refs/heads/foo\n" \
+			$ZERO_OID $A | packetize &&
+		printf "%s %s refs/for/next/topic\n" \
+			$ZERO_OID $A | packetize &&
+		printf "%s %s refs/for/master/topic2\n" \
+			$ZERO_OID $A | packetize &&
+		printf 0000 &&
+		printf "" | git -C "$upstream" pack-objects --stdout
+	} | git receive-pack "$upstream" --stateless-rpc \
+	>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	# pre-receive hook
+	pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+	pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic1
+	pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo
+	pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic
+	pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic2
+	# proc-receive hook
+	proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic1
+	proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic
+	proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic2
+	proc-receive> ok refs/for/master/topic1
+	proc-receive> option fall-through
+	proc-receive> ok refs/for/master/topic2
+	proc-receive> option refname refs/for/changes/23/123/1
+	proc-receive> option new-oid <COMMIT-A>
+	proc-receive> ok refs/for/master/topic2
+	proc-receive> option refname refs/for/changes/24/124/2
+	proc-receive> option old-oid <COMMIT-B>
+	proc-receive> option new-oid <COMMIT-A>
+	proc-receive> option forced-update
+	proc-receive> ng refs/for/next/topic target branch not exist
+	000eunpack ok
+	0019ok refs/heads/master
+	001eok refs/for/master/topic1
+	0016ok refs/heads/foo
+	0033ng refs/for/next/topic target branch not exist
+	001eok refs/for/master/topic2
+	0000# post-receive hook
+	post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+	post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic1
+	post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo
+	post-receive< <ZERO-OID> <COMMIT-A> refs/for/changes/23/123/1
+	post-receive< <COMMIT-B> <COMMIT-A> refs/for/changes/24/124/2
+	EOF
+	test_cmp expect actual &&
+
+	git -C "$upstream" show-ref >out &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	<COMMIT-A> refs/for/master/topic1
+	<COMMIT-A> refs/heads/foo
+	<COMMIT-B> refs/heads/master
+	EOF
+	test_cmp expect actual
+'
diff --git a/third_party/git/t/t5411/test-0000-standard-git-push.sh b/third_party/git/t/t5411/test-0000-standard-git-push.sh
new file mode 100644
index 0000000000..e206587348
--- /dev/null
+++ b/third_party/git/t/t5411/test-0000-standard-git-push.sh
@@ -0,0 +1,143 @@
+# Refs of upstream : master(A)  
+# Refs of workbench: master(A)  tags/v123
+# git-push         : master(B)             next(A)
+test_expect_success "git-push ($PROTOCOL)" '
+	git -C workbench push origin \
+		$B:refs/heads/master \
+		HEAD:refs/heads/next \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+	remote: # post-receive hook
+	remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+	To <URL/of/upstream.git>
+	 <OID-A>..<OID-B> <COMMIT-B> -> master
+	 * [new branch] HEAD -> next
+	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/master
+	<COMMIT-A> refs/heads/next
+	EOF
+	test_cmp expect actual
+'
+
+# Refs of upstream : master(B)  next(A)
+# Refs of workbench: master(A)           tags/v123
+# git-push --atomic: master(A)  next(B)
+test_expect_success "git-push --atomic ($PROTOCOL)" '
+	test_must_fail git -C workbench push --atomic origin \
+		master \
+		$B:refs/heads/next \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out |
+		sed -n \
+			-e "/^To / { s/   */ /g; p; }" \
+			-e "/^ ! / { s/   */ /g; p; }" \
+			>actual &&
+	cat >expect <<-EOF &&
+	To <URL/of/upstream.git>
+	 ! [rejected] master -> master (non-fast-forward)
+	 ! [rejected] <COMMIT-B> -> next (atomic push failed)
+	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/master
+	<COMMIT-A> refs/heads/next
+	EOF
+	test_cmp expect actual
+'
+
+# Refs of upstream : master(B)  next(A)
+# Refs of workbench: master(A)           tags/v123
+# git-push         : master(A)  next(B)
+test_expect_success "non-fast-forward git-push ($PROTOCOL)" '
+	test_must_fail git \
+		-C workbench \
+		-c advice.pushUpdateRejected=false \
+		push origin \
+		master \
+		$B:refs/heads/next \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/next
+	remote: # post-receive hook
+	remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/next
+	To <URL/of/upstream.git>
+	 <OID-A>..<OID-B> <COMMIT-B> -> next
+	 ! [rejected] master -> master (non-fast-forward)
+	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/master
+	<COMMIT-B> refs/heads/next
+	EOF
+	test_cmp expect actual
+'
+
+# Refs of upstream : master(B)  next(B)
+# Refs of workbench: master(A)           tags/v123
+# git-push -f      : master(A)  NULL     tags/v123  refs/review/master/topic(A)  a/b/c(A)
+test_expect_success "git-push -f ($PROTOCOL)" '
+	git -C workbench push -f origin \
+		refs/tags/v123 \
+		:refs/heads/next \
+		master \
+		master:refs/review/master/topic \
+		HEAD:refs/heads/a/b/c \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master
+	remote: pre-receive< <COMMIT-B> <ZERO-OID> refs/heads/next
+	remote: pre-receive< <ZERO-OID> <TAG-v123> refs/tags/v123
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c
+	remote: # post-receive hook
+	remote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master
+	remote: post-receive< <COMMIT-B> <ZERO-OID> refs/heads/next
+	remote: post-receive< <ZERO-OID> <TAG-v123> refs/tags/v123
+	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic
+	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c
+	To <URL/of/upstream.git>
+	 + <OID-B>...<OID-A> master -> master (forced update)
+	 - [deleted] next
+	 * [new tag] v123 -> v123
+	 * [new reference] master -> refs/review/master/topic
+	 * [new branch] HEAD -> a/b/c
+	EOF
+	test_cmp expect actual &&
+	git -C "$upstream" show-ref >out &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	<COMMIT-A> refs/heads/a/b/c
+	<COMMIT-A> refs/heads/master
+	<COMMIT-A> refs/review/master/topic
+	<TAG-v123> refs/tags/v123
+	EOF
+	test_cmp expect actual
+'
+
+# Refs of upstream : master(A)  tags/v123  refs/review/master/topic(A)  a/b/c(A)
+# Refs of workbench: master(A)  tags/v123
+test_expect_success "cleanup ($PROTOCOL)" '
+	(
+		cd "$upstream" &&
+		git update-ref -d refs/review/master/topic &&
+		git update-ref -d refs/tags/v123 &&
+		git update-ref -d refs/heads/a/b/c
+	)
+'
diff --git a/third_party/git/t/t5411/test-0001-standard-git-push--porcelain.sh b/third_party/git/t/t5411/test-0001-standard-git-push--porcelain.sh
new file mode 100644
index 0000000000..48f6fcc846
--- /dev/null
+++ b/third_party/git/t/t5411/test-0001-standard-git-push--porcelain.sh
@@ -0,0 +1,147 @@
+# Refs of upstream : master(A)  
+# Refs of workbench: master(A)  tags/v123
+# git-push         : master(B)             next(A)
+test_expect_success "git-push ($PROTOCOL/porcelain)" '
+	git -C workbench push --porcelain origin \
+		$B:refs/heads/master \
+		HEAD:refs/heads/next \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+	remote: # post-receive hook
+	remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+	To <URL/of/upstream.git>
+	     <COMMIT-B>:refs/heads/master    <OID-A>..<OID-B>
+	*    HEAD: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/master
+	<COMMIT-A> refs/heads/next
+	EOF
+	test_cmp expect actual
+'
+
+# Refs of upstream : master(B)  next(A)
+# Refs of workbench: master(A)           tags/v123
+# git-push --atomic: master(A)  next(B)
+test_expect_success "git-push --atomic ($PROTOCOL/porcelain)" '
+	test_must_fail git -C workbench push --atomic --porcelain origin \
+		master \
+		$B:refs/heads/next \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out |
+		sed -n \
+			-e "s/^# GETTEXT POISON #//" \
+			-e "/^To / { s/   */ /g; p; }" \
+			-e "/^! / { s/   */ /g; p; }" \
+			>actual &&
+	cat >expect <<-EOF &&
+	To <URL/of/upstream.git>
+	! refs/heads/master:refs/heads/master [rejected] (non-fast-forward)
+	! <COMMIT-B>:refs/heads/next [rejected] (atomic push failed)
+	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/master
+	<COMMIT-A> refs/heads/next
+	EOF
+	test_cmp expect actual
+'
+
+# Refs of upstream : master(B)  next(A)
+# Refs of workbench: master(A)           tags/v123
+# git-push         : master(A)  next(B)
+test_expect_success "non-fast-forward git-push ($PROTOCOL/porcelain)" '
+	test_must_fail git \
+		-C workbench \
+		-c advice.pushUpdateRejected=false \
+		push --porcelain origin \
+		master \
+		$B:refs/heads/next \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/next
+	remote: # post-receive hook
+	remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/next
+	To <URL/of/upstream.git>
+	     <COMMIT-B>:refs/heads/next    <OID-A>..<OID-B>
+	!    refs/heads/master:refs/heads/master    [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/master
+	<COMMIT-B> refs/heads/next
+	EOF
+	test_cmp expect actual
+'
+
+# Refs of upstream : master(B)  next(B)
+# Refs of workbench: master(A)           tags/v123
+# git-push -f      : master(A)  NULL     tags/v123  refs/review/master/topic(A)  a/b/c(A)
+test_expect_success "git-push -f ($PROTOCOL/porcelain)" '
+	git -C workbench push --porcelain -f origin \
+		refs/tags/v123 \
+		:refs/heads/next \
+		master \
+		master:refs/review/master/topic \
+		HEAD:refs/heads/a/b/c \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master
+	remote: pre-receive< <COMMIT-B> <ZERO-OID> refs/heads/next
+	remote: pre-receive< <ZERO-OID> <TAG-v123> refs/tags/v123
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c
+	remote: # post-receive hook
+	remote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master
+	remote: post-receive< <COMMIT-B> <ZERO-OID> refs/heads/next
+	remote: post-receive< <ZERO-OID> <TAG-v123> refs/tags/v123
+	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic
+	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c
+	To <URL/of/upstream.git>
+	+    refs/heads/master:refs/heads/master    <OID-B>...<OID-A> (forced update)
+	-    :refs/heads/next    [deleted]
+	*    refs/tags/v123:refs/tags/v123    [new tag]
+	*    refs/heads/master:refs/review/master/topic    [new reference]
+	*    HEAD:refs/heads/a/b/c    [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-A> refs/heads/a/b/c
+	<COMMIT-A> refs/heads/master
+	<COMMIT-A> refs/review/master/topic
+	<TAG-v123> refs/tags/v123
+	EOF
+	test_cmp expect actual
+'
+
+# Refs of upstream : master(A)  tags/v123  refs/review/master/topic(A)  a/b/c(A)
+# Refs of workbench: master(A)  tags/v123
+test_expect_success "cleanup ($PROTOCOL/porcelain)" '
+	(
+		cd "$upstream" &&
+		git update-ref -d refs/review/master/topic &&
+		git update-ref -d refs/tags/v123 &&
+		git update-ref -d refs/heads/a/b/c
+	)
+'
diff --git a/third_party/git/t/t5411/test-0002-pre-receive-declined.sh b/third_party/git/t/t5411/test-0002-pre-receive-declined.sh
new file mode 100644
index 0000000000..c246f7e68e
--- /dev/null
+++ b/third_party/git/t/t5411/test-0002-pre-receive-declined.sh
@@ -0,0 +1,33 @@
+test_expect_success "setup pre-receive hook ($PROTOCOL)" '
+	mv "$upstream/hooks/pre-receive" "$upstream/hooks/pre-receive.ok" &&
+	write_script "$upstream/hooks/pre-receive" <<-EOF
+	exit 1
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git-push         : master(B)             next(A)
+test_expect_success "git-push is declined ($PROTOCOL)" '
+	test_must_fail git -C workbench push origin \
+		$B:refs/heads/master \
+		HEAD:refs/heads/next \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	To <URL/of/upstream.git>
+	 ! [remote rejected] <COMMIT-B> -> master (pre-receive hook declined)
+	 ! [remote rejected] HEAD -> next (pre-receive hook declined)
+	EOF
+	test_cmp expect actual &&
+	git -C "$upstream" show-ref >out &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	<COMMIT-A> refs/heads/master
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success "cleanup ($PROTOCOL)" '
+	mv "$upstream/hooks/pre-receive.ok" "$upstream/hooks/pre-receive"
+'
diff --git a/third_party/git/t/t5411/test-0003-pre-receive-declined--porcelain.sh b/third_party/git/t/t5411/test-0003-pre-receive-declined--porcelain.sh
new file mode 100644
index 0000000000..b14894de81
--- /dev/null
+++ b/third_party/git/t/t5411/test-0003-pre-receive-declined--porcelain.sh
@@ -0,0 +1,34 @@
+test_expect_success "setup pre-receive hook ($PROTOCOL/porcelain)" '
+	mv "$upstream/hooks/pre-receive" "$upstream/hooks/pre-receive.ok" &&
+	write_script "$upstream/hooks/pre-receive" <<-EOF
+	exit 1
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git-push         : master(B)             next(A)
+test_expect_success "git-push is declined ($PROTOCOL/porcelain)" '
+	test_must_fail git -C workbench push --porcelain origin \
+		$B:refs/heads/master \
+		HEAD:refs/heads/next \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	To <URL/of/upstream.git>
+	!    <COMMIT-B>:refs/heads/master    [remote rejected] (pre-receive hook declined)
+	!    HEAD:refs/heads/next    [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-A> refs/heads/master
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success "cleanup ($PROTOCOL/porcelain)" '
+	mv "$upstream/hooks/pre-receive.ok" "$upstream/hooks/pre-receive"
+'
diff --git a/third_party/git/t/t5411/test-0010-proc-receive-settings.sh b/third_party/git/t/t5411/test-0010-proc-receive-settings.sh
new file mode 100644
index 0000000000..a36809927b
--- /dev/null
+++ b/third_party/git/t/t5411/test-0010-proc-receive-settings.sh
@@ -0,0 +1,7 @@
+test_expect_success "add two receive.procReceiveRefs settings" '
+	(
+		cd "$upstream" &&
+		git config --add receive.procReceiveRefs refs/for &&
+		git config --add receive.procReceiveRefs refs/review/
+	)
+'
diff --git a/third_party/git/t/t5411/test-0011-no-hook-error.sh b/third_party/git/t/t5411/test-0011-no-hook-error.sh
new file mode 100644
index 0000000000..bb6ec92a92
--- /dev/null
+++ b/third_party/git/t/t5411/test-0011-no-hook-error.sh
@@ -0,0 +1,64 @@
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         :                       next(A)  refs/for/master/topic(A)
+test_expect_success "proc-receive: no hook, fail to push special ref ($PROTOCOL)" '
+	test_must_fail git -C workbench push origin \
+		HEAD:next \
+		HEAD:refs/for/master/topic \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: error: cannot find hook "proc-receive"
+	remote: # post-receive hook
+	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+	To <URL/of/upstream.git>
+	 * [new branch] HEAD -> next
+	 ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)
+	EOF
+	test_cmp expect actual &&
+	git -C "$upstream" show-ref >out &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	<COMMIT-A> refs/heads/master
+	<COMMIT-A> refs/heads/next
+	EOF
+	test_cmp expect actual
+'
+
+# Refs of upstream : master(A)             next(A)
+# Refs of workbench: master(A)  tags/v123
+test_expect_success "cleanup ($PROTOCOL)" '
+	git -C "$upstream" update-ref -d refs/heads/next
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push --atomic: (B)                   next(A)  refs/for/master/topic(A)
+test_expect_success "proc-receive: no hook, all failed for atomic push ($PROTOCOL)" '
+	test_must_fail git -C workbench push --atomic origin \
+		$B:master \
+		HEAD:next \
+		HEAD:refs/for/master/topic >out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: error: cannot find hook "proc-receive"
+	To <URL/of/upstream.git>
+	 ! [remote rejected] <COMMIT-B> -> master (fail to run proc-receive hook)
+	 ! [remote rejected] HEAD -> next (fail to run proc-receive hook)
+	 ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)
+	EOF
+	test_cmp expect actual &&
+	git -C "$upstream" show-ref >out &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	<COMMIT-A> refs/heads/master
+	EOF
+	test_cmp expect actual
+'
diff --git a/third_party/git/t/t5411/test-0012-no-hook-error--porcelain.sh b/third_party/git/t/t5411/test-0012-no-hook-error--porcelain.sh
new file mode 100644
index 0000000000..4814f74dc2
--- /dev/null
+++ b/third_party/git/t/t5411/test-0012-no-hook-error--porcelain.sh
@@ -0,0 +1,66 @@
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         :                       next(A)  refs/for/master/topic(A)
+test_expect_success "proc-receive: no hook, fail to push special ref ($PROTOCOL/porcelain)" '
+	test_must_fail git -C workbench push --porcelain origin \
+		HEAD:next \
+		HEAD:refs/for/master/topic \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: error: cannot find hook "proc-receive"
+	remote: # post-receive hook
+	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+	To <URL/of/upstream.git>
+	*    HEAD:refs/heads/next    [new branch]
+	!    HEAD:refs/for/master/topic    [remote rejected] (fail to run proc-receive hook)
+	Done
+	EOF
+	test_cmp expect actual &&
+	git -C "$upstream" show-ref >out &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	<COMMIT-A> refs/heads/master
+	<COMMIT-A> refs/heads/next
+	EOF
+	test_cmp expect actual
+'
+
+# Refs of upstream : master(A)             next(A)
+# Refs of workbench: master(A)  tags/v123
+test_expect_success "cleanup ($PROTOCOL/porcelain)" '
+	git -C "$upstream" update-ref -d refs/heads/next
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push --atomic: (B)                   next(A)  refs/for/master/topic(A)
+test_expect_success "proc-receive: no hook, all failed for atomic push ($PROTOCOL/porcelain)" '
+	test_must_fail git -C workbench push --porcelain --atomic origin \
+		$B:master \
+		HEAD:next \
+		HEAD:refs/for/master/topic >out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: error: cannot find hook "proc-receive"
+	To <URL/of/upstream.git>
+	!    <COMMIT-B>:refs/heads/master    [remote rejected] (fail to run proc-receive hook)
+	!    HEAD:refs/heads/next    [remote rejected] (fail to run proc-receive hook)
+	!    HEAD:refs/for/master/topic    [remote rejected] (fail to run proc-receive hook)
+	Done
+	EOF
+	test_cmp expect actual &&
+	git -C "$upstream" show-ref >out &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	<COMMIT-A> refs/heads/master
+	EOF
+	test_cmp expect actual
+'
diff --git a/third_party/git/t/t5411/test-0013-bad-protocol.sh b/third_party/git/t/t5411/test-0013-bad-protocol.sh
new file mode 100644
index 0000000000..c5fe4cb37b
--- /dev/null
+++ b/third_party/git/t/t5411/test-0013-bad-protocol.sh
@@ -0,0 +1,217 @@
+test_expect_success "setup proc-receive hook (unknown version, $PROTOCOL)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v --version 2
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         :                       refs/for/master/topic(A)
+test_expect_success "proc-receive: bad protocol (unknown version, $PROTOCOL)" '
+	test_must_fail git -C workbench push origin \
+		HEAD:refs/for/master/topic \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+
+	# Check status report for git-push
+	sed -n \
+		-e "/^To / { p; n; p; }" \
+		<actual >actual-report &&
+	cat >expect <<-EOF &&
+	To <URL/of/upstream.git>
+	 ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)
+	EOF
+	test_cmp expect actual-report &&
+
+	# Check error message from "receive-pack", but ignore unstable fatal error
+	# message ("remote: fatal: the remote end hung up unexpectedly") which
+	# is different from the remote HTTP server with different locale settings.
+	grep "^remote: error:" <actual >actual-error &&
+	cat >expect <<-EOF &&
+	remote: error: proc-receive version "2" is not supported
+	EOF
+	test_cmp expect actual-error &&
+
+	git -C "$upstream" show-ref >out &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	<COMMIT-A> refs/heads/master
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (hook --die-version, $PROTOCOL)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v --die-version
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         :                       refs/for/master/topic(A)
+test_expect_success "proc-receive: bad protocol (hook --die-version, $PROTOCOL)" '
+	test_must_fail git -C workbench push origin \
+		HEAD:refs/for/master/topic \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: # proc-receive hook
+	remote: fatal: bad protocol version: 1
+	remote: error: proc-receive version "0" is not supported
+	To <URL/of/upstream.git>
+	 ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)
+	EOF
+	test_cmp expect actual &&
+
+	git -C "$upstream" show-ref >out &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	<COMMIT-A> refs/heads/master
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (hook --die-readline, $PROTOCOL)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v --die-readline
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         :                       refs/for/master/topic(A)
+test_expect_success "proc-receive: bad protocol (hook --die-readline, $PROTOCOL)" '
+	test_must_fail git -C workbench push origin \
+		HEAD:refs/for/master/topic \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+
+	grep "remote: fatal: protocol error: expected \"old new ref\", got \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"" actual &&
+
+	git -C "$upstream" show-ref >out &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	<COMMIT-A> refs/heads/master
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (no report, $PROTOCOL)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         :                       next(A)  refs/for/master/topic(A)
+test_expect_success "proc-receive: bad protocol (no report, $PROTOCOL)" '
+	test_must_fail git -C workbench push origin \
+		HEAD:refs/heads/next \
+		HEAD:refs/for/master/topic >out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: # proc-receive hook
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: # post-receive hook
+	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+	To <URL/of/upstream.git>
+	 * [new branch] HEAD -> next
+	 ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)
+	EOF
+	test_cmp expect actual &&
+	git -C "$upstream" show-ref >out &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	<COMMIT-A> refs/heads/master
+	<COMMIT-A> refs/heads/next
+	EOF
+	test_cmp expect actual
+'
+
+# Refs of upstream : master(A)             next(A)
+# Refs of workbench: master(A)  tags/v123
+test_expect_success "cleanup ($PROTOCOL)" '
+	git -C "$upstream" update-ref -d refs/heads/next
+
+'
+
+test_expect_success "setup proc-receive hook (no ref, $PROTOCOL)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v \
+		-r "ok"
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         :                       refs/for/master/topic
+test_expect_success "proc-receive: bad protocol (no ref, $PROTOCOL)" '
+	test_must_fail git -C workbench push origin \
+		HEAD:refs/for/master/topic\
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: # proc-receive hook
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: proc-receive> ok
+	remote: error: proc-receive reported incomplete status line: "ok"
+	To <URL/of/upstream.git>
+	 ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)
+	EOF
+	test_cmp expect actual &&
+	git -C "$upstream" show-ref >out &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	<COMMIT-A> refs/heads/master
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (unknown status, $PROTOCOL)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v \
+		-r "xx refs/for/master/topic"
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         :                       refs/for/master/topic
+test_expect_success "proc-receive: bad protocol (unknown status, $PROTOCOL)" '
+	test_must_fail git -C workbench push origin \
+			HEAD:refs/for/master/topic \
+			>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: # proc-receive hook
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: proc-receive> xx refs/for/master/topic
+	remote: error: proc-receive reported bad status "xx" on ref "refs/for/master/topic"
+	To <URL/of/upstream.git>
+	 ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)
+	EOF
+	test_cmp expect actual &&
+	git -C "$upstream" show-ref >out &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	<COMMIT-A> refs/heads/master
+	EOF
+	test_cmp expect actual
+'
diff --git a/third_party/git/t/t5411/test-0014-bad-protocol--porcelain.sh b/third_party/git/t/t5411/test-0014-bad-protocol--porcelain.sh
new file mode 100644
index 0000000000..53b47b0185
--- /dev/null
+++ b/third_party/git/t/t5411/test-0014-bad-protocol--porcelain.sh
@@ -0,0 +1,160 @@
+test_expect_success "setup proc-receive hook (unknown version, $PROTOCOL/porcelain)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v --version 2
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         :                       refs/for/master/topic(A)
+test_expect_success "proc-receive: bad protocol (unknown version, $PROTOCOL/porcelain)" '
+	test_must_fail git -C workbench push --porcelain origin \
+		HEAD:refs/for/master/topic \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+
+	# Check status report for git-push
+	sed -n \
+		-e "/^To / { p; n; p; n; p; }" \
+		<actual >actual-report &&
+	cat >expect <<-EOF &&
+	To <URL/of/upstream.git>
+	!    HEAD:refs/for/master/topic    [remote rejected] (fail to run proc-receive hook)
+	Done
+	EOF
+	test_cmp expect actual-report &&
+
+	# Check error message from "receive-pack", but ignore unstable fatal error
+	# message ("remote: fatal: the remote end hung up unexpectedly") which
+	# is different from the remote HTTP server with different locale settings.
+	grep "^remote: error:" <actual >actual-error &&
+	cat >expect <<-EOF &&
+	remote: error: proc-receive version "2" is not supported
+	EOF
+	test_cmp expect actual-error &&
+
+	git -C "$upstream" show-ref >out &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	<COMMIT-A> refs/heads/master
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (no report, $PROTOCOL/porcelain)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         :                       next(A)  refs/for/master/topic(A)
+test_expect_success "proc-receive: bad protocol (no report, $PROTOCOL/porcelain)" '
+	test_must_fail git -C workbench push --porcelain origin \
+		HEAD:refs/heads/next \
+		HEAD:refs/for/master/topic >out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: # proc-receive hook
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: # post-receive hook
+	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+	To <URL/of/upstream.git>
+	*    HEAD:refs/heads/next    [new branch]
+	!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)
+	Done
+	EOF
+	test_cmp expect actual &&
+	git -C "$upstream" show-ref >out &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	<COMMIT-A> refs/heads/master
+	<COMMIT-A> refs/heads/next
+	EOF
+	test_cmp expect actual
+'
+
+# Refs of upstream : master(A)             next(A)
+# Refs of workbench: master(A)  tags/v123
+test_expect_success "cleanup ($PROTOCOL/porcelain)" '
+	git -C "$upstream" update-ref -d refs/heads/next
+
+'
+
+test_expect_success "setup proc-receive hook (no ref, $PROTOCOL/porcelain)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v \
+		-r "ok"
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         :                       refs/for/master/topic
+test_expect_success "proc-receive: bad protocol (no ref, $PROTOCOL/porcelain)" '
+	test_must_fail git -C workbench push --porcelain origin \
+		HEAD:refs/for/master/topic\
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: # proc-receive hook
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: proc-receive> ok
+	remote: error: proc-receive reported incomplete status line: "ok"
+	To <URL/of/upstream.git>
+	!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)
+	Done
+	EOF
+	test_cmp expect actual &&
+	git -C "$upstream" show-ref >out &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	<COMMIT-A> refs/heads/master
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (unknown status, $PROTOCOL/porcelain)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v \
+		-r "xx refs/for/master/topic"
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         :                       refs/for/master/topic
+test_expect_success "proc-receive: bad protocol (unknown status, $PROTOCOL/porcelain)" '
+	test_must_fail git -C workbench push --porcelain origin \
+			HEAD:refs/for/master/topic \
+			>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: # proc-receive hook
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: proc-receive> xx refs/for/master/topic
+	remote: error: proc-receive reported bad status "xx" on ref "refs/for/master/topic"
+	To <URL/of/upstream.git>
+	!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)
+	Done
+	EOF
+	test_cmp expect actual &&
+	git -C "$upstream" show-ref >out &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	<COMMIT-A> refs/heads/master
+	EOF
+	test_cmp expect actual
+'
diff --git a/third_party/git/t/t5411/test-0020-report-ng.sh b/third_party/git/t/t5411/test-0020-report-ng.sh
new file mode 100644
index 0000000000..f726b7ca9c
--- /dev/null
+++ b/third_party/git/t/t5411/test-0020-report-ng.sh
@@ -0,0 +1,67 @@
+test_expect_success "setup proc-receive hook (ng, no message, $PROTOCOL)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v \
+		-r "ng refs/for/master/topic"
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         :                       refs/for/master/topic
+test_expect_success "proc-receive: fail to update (ng, no message, $PROTOCOL)" '
+	test_must_fail git -C workbench push origin \
+		HEAD:refs/for/master/topic \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: # proc-receive hook
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: proc-receive> ng refs/for/master/topic
+	To <URL/of/upstream.git>
+	 ! [remote rejected] HEAD -> refs/for/master/topic (failed)
+	EOF
+	test_cmp expect actual &&
+	git -C "$upstream" show-ref >out &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	<COMMIT-A> refs/heads/master
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (ng message, $PROTOCOL)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v \
+		-r "ng refs/for/master/topic error msg"
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         :                       refs/for/master/topic
+test_expect_success "proc-receive: fail to update (ng, with message, $PROTOCOL)" '
+	test_must_fail git -C workbench push origin \
+		HEAD:refs/for/master/topic \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: # proc-receive hook
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: proc-receive> ng refs/for/master/topic error msg
+	To <URL/of/upstream.git>
+	 ! [remote rejected] HEAD -> refs/for/master/topic (error msg)
+	EOF
+	test_cmp expect actual &&
+	git -C "$upstream" show-ref >out &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	<COMMIT-A> refs/heads/master
+	EOF
+	test_cmp expect actual
+'
diff --git a/third_party/git/t/t5411/test-0021-report-ng--porcelain.sh b/third_party/git/t/t5411/test-0021-report-ng--porcelain.sh
new file mode 100644
index 0000000000..fbf5569103
--- /dev/null
+++ b/third_party/git/t/t5411/test-0021-report-ng--porcelain.sh
@@ -0,0 +1,69 @@
+test_expect_success "setup proc-receive hook (ng, no message, $PROTOCOL/porcelain)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v \
+		-r "ng refs/for/master/topic"
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         :                       refs/for/master/topic
+test_expect_success "proc-receive: fail to update (ng, no message, $PROTOCOL/porcelain)" '
+	test_must_fail git -C workbench push --porcelain origin \
+		HEAD:refs/for/master/topic \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: # proc-receive hook
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: proc-receive> ng refs/for/master/topic
+	To <URL/of/upstream.git>
+	!    HEAD:refs/for/master/topic    [remote rejected] (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-A> refs/heads/master
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (ng message, $PROTOCOL/porcelain)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v \
+		-r "ng refs/for/master/topic error msg"
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         :                       refs/for/master/topic
+test_expect_success "proc-receive: fail to update (ng, with message, $PROTOCOL/porcelain)" '
+	test_must_fail git -C workbench push --porcelain origin \
+		HEAD:refs/for/master/topic \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: # proc-receive hook
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: proc-receive> ng refs/for/master/topic error msg
+	To <URL/of/upstream.git>
+	!    HEAD:refs/for/master/topic    [remote rejected] (error msg)
+	Done
+	EOF
+	test_cmp expect actual &&
+	git -C "$upstream" show-ref >out &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	<COMMIT-A> refs/heads/master
+	EOF
+	test_cmp expect actual
+'
diff --git a/third_party/git/t/t5411/test-0022-report-unexpect-ref.sh b/third_party/git/t/t5411/test-0022-report-unexpect-ref.sh
new file mode 100644
index 0000000000..92a415b929
--- /dev/null
+++ b/third_party/git/t/t5411/test-0022-report-unexpect-ref.sh
@@ -0,0 +1,45 @@
+test_expect_success "setup proc-receive hook (unexpected ref, $PROTOCOL)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v \
+		-r "ok refs/heads/master"
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         : (B)                   refs/for/master/topic
+test_expect_success "proc-receive: report unexpected ref ($PROTOCOL)" '
+	test_must_fail git -C workbench push origin \
+		$B:refs/heads/master \
+		HEAD:refs/for/master/topic \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: # proc-receive hook
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: proc-receive> ok refs/heads/master
+	remote: error: proc-receive reported status on unexpected ref: refs/heads/master
+	remote: # post-receive hook
+	remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+	To <URL/of/upstream.git>
+	 <OID-A>..<OID-B> <COMMIT-B> -> master
+	 ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)
+	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/master
+	EOF
+	test_cmp expect actual
+'
+
+# Refs of upstream : master(B)
+# Refs of workbench: master(A)  tags/v123
+test_expect_success "cleanup ($PROTOCOL)" '
+	git -C "$upstream" update-ref refs/heads/master $A
+'
diff --git a/third_party/git/t/t5411/test-0023-report-unexpect-ref--porcelain.sh b/third_party/git/t/t5411/test-0023-report-unexpect-ref--porcelain.sh
new file mode 100644
index 0000000000..acbf93e40a
--- /dev/null
+++ b/third_party/git/t/t5411/test-0023-report-unexpect-ref--porcelain.sh
@@ -0,0 +1,46 @@
+test_expect_success "setup proc-receive hook (unexpected ref, $PROTOCOL/porcelain)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v \
+		-r "ok refs/heads/master"
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         : (B)                   refs/for/master/topic
+test_expect_success "proc-receive: report unexpected ref ($PROTOCOL/porcelain)" '
+	test_must_fail git -C workbench push --porcelain origin \
+		$B:refs/heads/master \
+		HEAD:refs/for/master/topic \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: # proc-receive hook
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: proc-receive> ok refs/heads/master
+	remote: error: proc-receive reported status on unexpected ref: refs/heads/master
+	remote: # post-receive hook
+	remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+	To <URL/of/upstream.git>
+	     <COMMIT-B>:refs/heads/master    <OID-A>..<OID-B>
+	!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)
+	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/master
+	EOF
+	test_cmp expect actual
+'
+
+# Refs of upstream : master(B)
+# Refs of workbench: master(A)  tags/v123
+test_expect_success "cleanup ($PROTOCOL/porcelain)" '
+	git -C "$upstream" update-ref refs/heads/master $A
+'
diff --git a/third_party/git/t/t5411/test-0024-report-unknown-ref.sh b/third_party/git/t/t5411/test-0024-report-unknown-ref.sh
new file mode 100644
index 0000000000..c3946f329a
--- /dev/null
+++ b/third_party/git/t/t5411/test-0024-report-unknown-ref.sh
@@ -0,0 +1,34 @@
+test_expect_success "setup proc-receive hook (unexpected ref, $PROTOCOL)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v \
+		-r "ok refs/for/master/topic"
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         :                       refs/for/a/b/c/my/topic
+test_expect_success "proc-receive: report unknown reference ($PROTOCOL)" '
+	test_must_fail git -C workbench push origin \
+		HEAD:refs/for/a/b/c/my/topic \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic
+	remote: # proc-receive hook
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic
+	remote: proc-receive> ok refs/for/master/topic
+	remote: error: proc-receive reported status on unknown ref: refs/for/master/topic
+	To <URL/of/upstream.git>
+	 ! [remote rejected] HEAD -> refs/for/a/b/c/my/topic (proc-receive failed to report status)
+	EOF
+	test_cmp expect actual &&
+	git -C "$upstream" show-ref >out &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	<COMMIT-A> refs/heads/master
+	EOF
+	test_cmp expect actual
+'
diff --git a/third_party/git/t/t5411/test-0025-report-unknown-ref--porcelain.sh b/third_party/git/t/t5411/test-0025-report-unknown-ref--porcelain.sh
new file mode 100644
index 0000000000..d093b1a579
--- /dev/null
+++ b/third_party/git/t/t5411/test-0025-report-unknown-ref--porcelain.sh
@@ -0,0 +1,35 @@
+test_expect_success "setup proc-receive hook (unexpected ref, $PROTOCOL/porcelain)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v \
+		-r "ok refs/for/master/topic"
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         :                       refs/for/a/b/c/my/topic
+test_expect_success "proc-receive: report unknown reference ($PROTOCOL/porcelain)" '
+	test_must_fail git -C workbench push --porcelain origin \
+		HEAD:refs/for/a/b/c/my/topic \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic
+	remote: # proc-receive hook
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic
+	remote: proc-receive> ok refs/for/master/topic
+	remote: error: proc-receive reported status on unknown ref: refs/for/master/topic
+	To <URL/of/upstream.git>
+	!    HEAD:refs/for/a/b/c/my/topic    [remote rejected] (proc-receive failed to report status)
+	Done
+	EOF
+	test_cmp expect actual &&
+	git -C "$upstream" show-ref >out &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	<COMMIT-A> refs/heads/master
+	EOF
+	test_cmp expect actual
+'
diff --git a/third_party/git/t/t5411/test-0026-push-options.sh b/third_party/git/t/t5411/test-0026-push-options.sh
new file mode 100644
index 0000000000..d0c4da8b23
--- /dev/null
+++ b/third_party/git/t/t5411/test-0026-push-options.sh
@@ -0,0 +1,79 @@
+test_expect_success "setup proc-receive hook and disable push-options ($PROTOCOL)" '
+	git -C "$upstream" config receive.advertisePushOptions false &&
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v \
+		-r "ok refs/for/master/topic"
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push -o ...  :                       refs/for/master/topic
+test_expect_success "proc-receive: not support push options ($PROTOCOL)" '
+	test_must_fail git -C workbench push \
+		-o issue=123 \
+		-o reviewer=user1 \
+		origin \
+		HEAD:refs/for/master/topic \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	test_i18ngrep "fatal: the receiving end does not support push options" \
+		actual &&
+	git -C "$upstream" show-ref >out &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	<COMMIT-A> refs/heads/master
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success "enable push options ($PROTOCOL)" '
+	git -C "$upstream" config receive.advertisePushOptions true
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push -o ...  :                       next(A)  refs/for/master/topic
+test_expect_success "proc-receive: push with options ($PROTOCOL)" '
+	git -C workbench push \
+		--atomic \
+		-o issue=123 \
+		-o reviewer=user1 \
+		origin \
+		HEAD:refs/heads/next \
+		HEAD:refs/for/master/topic \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: # proc-receive hook
+	remote: proc-receive: atomic push_options
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: proc-receive< issue=123
+	remote: proc-receive< reviewer=user1
+	remote: proc-receive> ok refs/for/master/topic
+	remote: # post-receive hook
+	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	To <URL/of/upstream.git>
+	 * [new branch] HEAD -> next
+	 * [new reference] HEAD -> refs/for/master/topic
+	EOF
+	test_cmp expect actual &&
+	git -C "$upstream" show-ref >out &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	<COMMIT-A> refs/heads/master
+	<COMMIT-A> refs/heads/next
+	EOF
+	test_cmp expect actual
+'
+
+# Refs of upstream : master(A)             next(A)
+# Refs of workbench: master(A)  tags/v123
+test_expect_success "cleanup ($PROTOCOL)" '
+	git -C "$upstream" update-ref -d refs/heads/next
+'
diff --git a/third_party/git/t/t5411/test-0027-push-options--porcelain.sh b/third_party/git/t/t5411/test-0027-push-options--porcelain.sh
new file mode 100644
index 0000000000..c89a1e7c57
--- /dev/null
+++ b/third_party/git/t/t5411/test-0027-push-options--porcelain.sh
@@ -0,0 +1,82 @@
+test_expect_success "setup proc-receive hook and disable push-options ($PROTOCOL/porcelain)" '
+	git -C "$upstream" config receive.advertisePushOptions false &&
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v \
+		-r "ok refs/for/master/topic"
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push -o ...  :                       refs/for/master/topic
+test_expect_success "proc-receive: not support push options ($PROTOCOL/porcelain)" '
+	test_must_fail git -C workbench push \
+		--porcelain \
+		-o issue=123 \
+		-o reviewer=user1 \
+		origin \
+		HEAD:refs/for/master/topic \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	test_i18ngrep "fatal: the receiving end does not support push options" \
+		actual &&
+	git -C "$upstream" show-ref >out &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	<COMMIT-A> refs/heads/master
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success "enable push options ($PROTOCOL/porcelain)" '
+	git -C "$upstream" config receive.advertisePushOptions true
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push -o ...  :                       next(A)  refs/for/master/topic
+test_expect_success "proc-receive: push with options ($PROTOCOL/porcelain)" '
+	git -C workbench push \
+		--porcelain \
+		--atomic \
+		-o issue=123 \
+		-o reviewer=user1 \
+		origin \
+		HEAD:refs/heads/next \
+		HEAD:refs/for/master/topic \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: # proc-receive hook
+	remote: proc-receive: atomic push_options
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: proc-receive< issue=123
+	remote: proc-receive< reviewer=user1
+	remote: proc-receive> ok refs/for/master/topic
+	remote: # post-receive hook
+	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	To <URL/of/upstream.git>
+	*    HEAD:refs/heads/next    [new branch]
+	*    HEAD:refs/for/master/topic    [new reference]
+	Done
+	EOF
+	test_cmp expect actual &&
+	git -C "$upstream" show-ref >out &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	<COMMIT-A> refs/heads/master
+	<COMMIT-A> refs/heads/next
+	EOF
+	test_cmp expect actual
+'
+
+# Refs of upstream : master(A)             next(A)
+# Refs of workbench: master(A)  tags/v123
+test_expect_success "cleanup ($PROTOCOL/porcelain)" '
+	git -C "$upstream" update-ref -d refs/heads/next
+'
diff --git a/third_party/git/t/t5411/test-0030-report-ok.sh b/third_party/git/t/t5411/test-0030-report-ok.sh
new file mode 100644
index 0000000000..44c99d3831
--- /dev/null
+++ b/third_party/git/t/t5411/test-0030-report-ok.sh
@@ -0,0 +1,35 @@
+test_expect_success "setup proc-receive hook (ok, $PROTOCOL)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v \
+		-r "ok refs/for/master/topic"
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         :                       refs/for/master/topic
+test_expect_success "proc-receive: ok ($PROTOCOL)" '
+	git -C workbench push origin \
+		HEAD:refs/for/master/topic \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: # proc-receive hook
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: proc-receive> ok refs/for/master/topic
+	remote: # post-receive hook
+	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	To <URL/of/upstream.git>
+	 * [new reference] HEAD -> refs/for/master/topic
+	EOF
+	test_cmp expect actual &&
+	git -C "$upstream" show-ref >out &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	<COMMIT-A> refs/heads/master
+	EOF
+	test_cmp expect actual
+'
diff --git a/third_party/git/t/t5411/test-0031-report-ok--porcelain.sh b/third_party/git/t/t5411/test-0031-report-ok--porcelain.sh
new file mode 100644
index 0000000000..3223b26184
--- /dev/null
+++ b/third_party/git/t/t5411/test-0031-report-ok--porcelain.sh
@@ -0,0 +1,36 @@
+test_expect_success "setup proc-receive hook (ok, $PROTOCOL/porcelain)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v \
+		-r "ok refs/for/master/topic"
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         :                       refs/for/master/topic
+test_expect_success "proc-receive: ok ($PROTOCOL/porcelain)" '
+	git -C workbench push --porcelain origin \
+		HEAD:refs/for/master/topic \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: # proc-receive hook
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: proc-receive> ok refs/for/master/topic
+	remote: # post-receive hook
+	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	To <URL/of/upstream.git>
+	*    HEAD:refs/for/master/topic    [new reference]
+	Done
+	EOF
+	test_cmp expect actual &&
+	git -C "$upstream" show-ref >out &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	<COMMIT-A> refs/heads/master
+	EOF
+	test_cmp expect actual
+'
diff --git a/third_party/git/t/t5411/test-0032-report-with-options.sh b/third_party/git/t/t5411/test-0032-report-with-options.sh
new file mode 100644
index 0000000000..b77b78c49f
--- /dev/null
+++ b/third_party/git/t/t5411/test-0032-report-with-options.sh
@@ -0,0 +1,256 @@
+test_expect_success "setup proc-receive hook (option without matching ok, $PROTOCOL)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v \
+		-r "option refname refs/pull/123/head" \
+		-r "option old-oid $B"
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)
+test_expect_success "proc-receive: report option without matching ok ($PROTOCOL)" '
+	test_must_fail git -C workbench push origin \
+		HEAD:refs/for/master/topic \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: # proc-receive hook
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: proc-receive> option refname refs/pull/123/head
+	remote: proc-receive> option old-oid <COMMIT-B>
+	remote: error: proc-receive reported "option" without a matching "ok/ng" directive
+	To <URL/of/upstream.git>
+	 ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (option refname, $PROTOCOL)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v \
+		-r "ok refs/for/master/topic" \
+		-r "option refname refs/pull/123/head"
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)
+test_expect_success "proc-receive: report option refname ($PROTOCOL)" '
+	git -C workbench push origin \
+		HEAD:refs/for/master/topic \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: # proc-receive hook
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: proc-receive> ok refs/for/master/topic
+	remote: proc-receive> option refname refs/pull/123/head
+	remote: # post-receive hook
+	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head
+	To <URL/of/upstream.git>
+	 * [new reference] HEAD -> refs/pull/123/head
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (option refname and forced-update, $PROTOCOL)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v \
+		-r "ok refs/for/master/topic" \
+		-r "option refname refs/pull/123/head" \
+		-r "option forced-update"
+	EOF
+'
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)
+test_expect_success "proc-receive: report option refname and forced-update ($PROTOCOL)" '
+	git -C workbench push origin \
+		HEAD:refs/for/master/topic \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: # proc-receive hook
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: proc-receive> ok refs/for/master/topic
+	remote: proc-receive> option refname refs/pull/123/head
+	remote: proc-receive> option forced-update
+	remote: # post-receive hook
+	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head
+	To <URL/of/upstream.git>
+	 * [new reference] HEAD -> refs/pull/123/head
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (option refname and old-oid, $PROTOCOL)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v \
+		-r "ok refs/for/master/topic" \
+		-r "option refname refs/pull/123/head" \
+		-r "option old-oid $B"
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)
+test_expect_success "proc-receive: report option refname and old-oid ($PROTOCOL)" '
+	git -C workbench push origin \
+		HEAD:refs/for/master/topic \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: # proc-receive hook
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: proc-receive> ok refs/for/master/topic
+	remote: proc-receive> option refname refs/pull/123/head
+	remote: proc-receive> option old-oid <COMMIT-B>
+	remote: # post-receive hook
+	remote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/123/head
+	To <URL/of/upstream.git>
+	 <OID-B>..<OID-A> HEAD -> refs/pull/123/head
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (option old-oid, $PROTOCOL)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v \
+		-r "ok refs/for/master/topic" \
+		-r "option old-oid $B"
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)
+test_expect_success "proc-receive: report option old-oid ($PROTOCOL)" '
+	git -C workbench push origin \
+		HEAD:refs/for/master/topic \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: # proc-receive hook
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: proc-receive> ok refs/for/master/topic
+	remote: proc-receive> option old-oid <COMMIT-B>
+	remote: # post-receive hook
+	remote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/master/topic
+	To <URL/of/upstream.git>
+	 <OID-B>..<OID-A> HEAD -> refs/for/master/topic
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (option old-oid and new-oid, $PROTOCOL)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v \
+		-r "ok refs/for/master/topic" \
+		-r "option old-oid $A" \
+		-r "option new-oid $B"
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)
+test_expect_success "proc-receive: report option old-oid and new-oid ($PROTOCOL)" '
+	git -C workbench push origin \
+		HEAD:refs/for/master/topic \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: # proc-receive hook
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: proc-receive> ok refs/for/master/topic
+	remote: proc-receive> option old-oid <COMMIT-A>
+	remote: proc-receive> option new-oid <COMMIT-B>
+	remote: # post-receive hook
+	remote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic
+	To <URL/of/upstream.git>
+	 <OID-A>..<OID-B> HEAD -> refs/for/master/topic
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (report with multiple rewrites, $PROTOCOL)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v \
+		-r "ok refs/for/a/b/c/topic" \
+		-r "ok refs/for/next/topic" \
+		-r "option refname refs/pull/123/head" \
+		-r "ok refs/for/master/topic" \
+		-r "option refname refs/pull/124/head" \
+		-r "option old-oid $B" \
+		-r "option forced-update" \
+		-r "option new-oid $A"
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)
+test_expect_success "proc-receive: report with multiple rewrites ($PROTOCOL)" '
+	git -C workbench push origin \
+		HEAD:refs/for/next/topic \
+		HEAD:refs/for/a/b/c/topic \
+		HEAD:refs/for/master/topic \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: # proc-receive hook
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: proc-receive> ok refs/for/a/b/c/topic
+	remote: proc-receive> ok refs/for/next/topic
+	remote: proc-receive> option refname refs/pull/123/head
+	remote: proc-receive> ok refs/for/master/topic
+	remote: proc-receive> option refname refs/pull/124/head
+	remote: proc-receive> option old-oid <COMMIT-B>
+	remote: proc-receive> option forced-update
+	remote: proc-receive> option new-oid <COMMIT-A>
+	remote: # post-receive hook
+	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head
+	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic
+	remote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head
+	To <URL/of/upstream.git>
+	 * [new reference] HEAD -> refs/pull/123/head
+	 * [new reference] HEAD -> refs/for/a/b/c/topic
+	 + <OID-B>...<OID-A> HEAD -> refs/pull/124/head (forced update)
+	EOF
+	test_cmp expect actual &&
+
+	git -C "$upstream" show-ref >out &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	<COMMIT-A> refs/heads/master
+	EOF
+	test_cmp expect actual
+'
diff --git a/third_party/git/t/t5411/test-0033-report-with-options--porcelain.sh b/third_party/git/t/t5411/test-0033-report-with-options--porcelain.sh
new file mode 100644
index 0000000000..1fe352b686
--- /dev/null
+++ b/third_party/git/t/t5411/test-0033-report-with-options--porcelain.sh
@@ -0,0 +1,265 @@
+test_expect_success "setup proc-receive hook (option without matching ok, $PROTOCOL/porcelain)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v \
+		-r "option refname refs/pull/123/head" \
+		-r "option old-oid $B"
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)
+test_expect_success "proc-receive: report option without matching ok ($PROTOCOL/porcelain)" '
+	test_must_fail git -C workbench push --porcelain origin \
+		HEAD:refs/for/master/topic \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: # proc-receive hook
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: proc-receive> option refname refs/pull/123/head
+	remote: proc-receive> option old-oid <COMMIT-B>
+	remote: error: proc-receive reported "option" without a matching "ok/ng" directive
+	To <URL/of/upstream.git>
+	!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)
+	Done
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (option refname, $PROTOCOL/porcelain)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v \
+		-r "ok refs/for/master/topic" \
+		-r "option refname refs/pull/123/head"
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)
+test_expect_success "proc-receive: report option refname ($PROTOCOL/porcelain)" '
+	git -C workbench push --porcelain origin \
+		HEAD:refs/for/master/topic \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: # proc-receive hook
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: proc-receive> ok refs/for/master/topic
+	remote: proc-receive> option refname refs/pull/123/head
+	remote: # post-receive hook
+	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head
+	To <URL/of/upstream.git>
+	*    HEAD:refs/pull/123/head    [new reference]
+	Done
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (option refname and forced-update, $PROTOCOL/porcelain)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v \
+		-r "ok refs/for/master/topic" \
+		-r "option refname refs/pull/123/head" \
+		-r "option forced-update"
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)
+test_expect_success "proc-receive: report option refname and forced-update ($PROTOCOL/porcelain)" '
+	git -C workbench push --porcelain origin \
+		HEAD:refs/for/master/topic \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: # proc-receive hook
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: proc-receive> ok refs/for/master/topic
+	remote: proc-receive> option refname refs/pull/123/head
+	remote: proc-receive> option forced-update
+	remote: # post-receive hook
+	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head
+	To <URL/of/upstream.git>
+	*    HEAD:refs/pull/123/head    [new reference]
+	Done
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (option refname and old-oid, $PROTOCOL/porcelain)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v \
+		-r "ok refs/for/master/topic" \
+		-r "option refname refs/pull/123/head" \
+		-r "option old-oid $B"
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)
+test_expect_success "proc-receive: report option refname and old-oid ($PROTOCOL/porcelain)" '
+	git -C workbench push --porcelain origin \
+		HEAD:refs/for/master/topic \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: # proc-receive hook
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: proc-receive> ok refs/for/master/topic
+	remote: proc-receive> option refname refs/pull/123/head
+	remote: proc-receive> option old-oid <COMMIT-B>
+	remote: # post-receive hook
+	remote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/123/head
+	To <URL/of/upstream.git>
+	     HEAD:refs/pull/123/head    <OID-B>..<OID-A>
+	Done
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (option old-oid, $PROTOCOL/porcelain)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v \
+		-r "ok refs/for/master/topic" \
+		-r "option old-oid $B"
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)
+test_expect_success "proc-receive: report option old-oid ($PROTOCOL/porcelain)" '
+	git -C workbench push --porcelain origin \
+		HEAD:refs/for/master/topic \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: # proc-receive hook
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: proc-receive> ok refs/for/master/topic
+	remote: proc-receive> option old-oid <COMMIT-B>
+	remote: # post-receive hook
+	remote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/master/topic
+	To <URL/of/upstream.git>
+	     HEAD:refs/for/master/topic    <OID-B>..<OID-A>
+	Done
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (option old-oid and new-oid, $PROTOCOL/porcelain)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v \
+		-r "ok refs/for/master/topic" \
+		-r "option old-oid $A" \
+		-r "option new-oid $B"
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)
+test_expect_success "proc-receive: report option old-oid and new-oid ($PROTOCOL/porcelain)" '
+	git -C workbench push --porcelain origin \
+		HEAD:refs/for/master/topic \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: # proc-receive hook
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: proc-receive> ok refs/for/master/topic
+	remote: proc-receive> option old-oid <COMMIT-A>
+	remote: proc-receive> option new-oid <COMMIT-B>
+	remote: # post-receive hook
+	remote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic
+	To <URL/of/upstream.git>
+	     HEAD:refs/for/master/topic    <OID-A>..<OID-B>
+	Done
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (report with multiple rewrites, $PROTOCOL/porcelain)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v \
+		-r "ok refs/for/a/b/c/topic" \
+		-r "ok refs/for/next/topic" \
+		-r "option refname refs/pull/123/head" \
+		-r "ok refs/for/master/topic" \
+		-r "option refname refs/pull/124/head" \
+		-r "option old-oid $B" \
+		-r "option forced-update" \
+		-r "option new-oid $A"
+
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)
+test_expect_success "proc-receive: report with multiple rewrites ($PROTOCOL/porcelain)" '
+	git -C workbench push --porcelain origin \
+		HEAD:refs/for/next/topic \
+		HEAD:refs/for/a/b/c/topic \
+		HEAD:refs/for/master/topic \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: # proc-receive hook
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: proc-receive> ok refs/for/a/b/c/topic
+	remote: proc-receive> ok refs/for/next/topic
+	remote: proc-receive> option refname refs/pull/123/head
+	remote: proc-receive> ok refs/for/master/topic
+	remote: proc-receive> option refname refs/pull/124/head
+	remote: proc-receive> option old-oid <COMMIT-B>
+	remote: proc-receive> option forced-update
+	remote: proc-receive> option new-oid <COMMIT-A>
+	remote: # post-receive hook
+	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head
+	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic
+	remote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head
+	To <URL/of/upstream.git>
+	*    HEAD:refs/pull/123/head    [new reference]
+	*    HEAD:refs/for/a/b/c/topic    [new reference]
+	+    HEAD:refs/pull/124/head    <OID-B>...<OID-A> (forced update)
+	Done
+	EOF
+	test_cmp expect actual &&
+
+	git -C "$upstream" show-ref >out &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	<COMMIT-A> refs/heads/master
+	EOF
+	test_cmp expect actual
+'
diff --git a/third_party/git/t/t5411/test-0034-report-ft.sh b/third_party/git/t/t5411/test-0034-report-ft.sh
new file mode 100644
index 0000000000..aca2b0676c
--- /dev/null
+++ b/third_party/git/t/t5411/test-0034-report-ft.sh
@@ -0,0 +1,44 @@
+test_expect_success "setup proc-receive hook (ft, $PROTOCOL)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v \
+		-r "ok refs/for/master/topic" \
+		-r "option fall-through"
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         :                       refs/for/master/topic(B)
+test_expect_success "proc-receive: fall throught, let receive-pack to execute ($PROTOCOL)" '
+	git -C workbench push origin \
+		$B:refs/for/master/topic \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic
+	remote: # proc-receive hook
+	remote: proc-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic
+	remote: proc-receive> ok refs/for/master/topic
+	remote: proc-receive> option fall-through
+	remote: # post-receive hook
+	remote: post-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic
+	To <URL/of/upstream.git>
+	 * [new reference] <COMMIT-B> -> refs/for/master/topic
+	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/for/master/topic
+	<COMMIT-A> refs/heads/master
+	EOF
+	test_cmp expect actual
+'
+
+# Refs of upstream : master(A)             refs/for/master/topic(A)
+# Refs of workbench: master(A)  tags/v123
+test_expect_success "cleanup ($PROTOCOL)" '
+	git -C "$upstream" update-ref -d refs/for/master/topic
+'
diff --git a/third_party/git/t/t5411/test-0035-report-ft--porcelain.sh b/third_party/git/t/t5411/test-0035-report-ft--porcelain.sh
new file mode 100644
index 0000000000..30ffffb352
--- /dev/null
+++ b/third_party/git/t/t5411/test-0035-report-ft--porcelain.sh
@@ -0,0 +1,45 @@
+test_expect_success "setup proc-receive hook (fall-through, $PROTOCOL/porcelain)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v \
+		-r "ok refs/for/master/topic" \
+		-r "option fall-through"
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         :                       refs/for/master/topic(B)
+test_expect_success "proc-receive: fall throught, let receive-pack to execute ($PROTOCOL/porcelain)" '
+	git -C workbench push --porcelain origin \
+		$B:refs/for/master/topic \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic
+	remote: # proc-receive hook
+	remote: proc-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic
+	remote: proc-receive> ok refs/for/master/topic
+	remote: proc-receive> option fall-through
+	remote: # post-receive hook
+	remote: post-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic
+	To <URL/of/upstream.git>
+	*    <COMMIT-B>:refs/for/master/topic    [new reference]
+	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/for/master/topic
+	<COMMIT-A> refs/heads/master
+	EOF
+	test_cmp expect actual
+'
+
+# Refs of upstream : master(A)             refs/for/master/topic(A)
+# Refs of workbench: master(A)  tags/v123
+test_expect_success "cleanup ($PROTOCOL/porcelain)" '
+	git -C "$upstream" update-ref -d refs/for/master/topic
+'
diff --git a/third_party/git/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh b/third_party/git/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh
new file mode 100644
index 0000000000..73283d81e8
--- /dev/null
+++ b/third_party/git/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh
@@ -0,0 +1,227 @@
+test_expect_success "setup git config for remote-tracking of special refs" '
+	(
+		cd workbench &&
+		if ! git config --get-all remote.origin.fetch | grep refs/for/
+		then
+			git config --add remote.origin.fetch \
+				"+refs/for/*:refs/t/for/*" &&
+			git config --add remote.origin.fetch \
+				"+refs/pull/*:refs/t/pull/*" &&
+			git config --add remote.origin.fetch \
+				"+refs/changes/*:refs/t/changes/*"
+		fi
+	)
+'
+
+test_expect_success "setup proc-receive hook (multiple rewrites for one ref, no refname for the 1st rewrite, $PROTOCOL)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v \
+		-r "ok refs/for/master/topic" \
+		-r "option old-oid $A" \
+		-r "option new-oid $B" \
+		-r "ok refs/for/master/topic" \
+		-r "option refname refs/changes/24/124/1" \
+		-r "option old-oid $ZERO_OID" \
+		-r "option new-oid $A" \
+		-r "ok refs/for/master/topic" \
+		-r "option refname refs/changes/25/125/1" \
+		-r "option old-oid $A" \
+		-r "option new-oid $B"
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         :                       refs/for/master/topic(A)
+test_expect_success "proc-receive: multiple rewrite for one ref, no refname for the 1st rewrite ($PROTOCOL)" '
+	git -C workbench push origin \
+		HEAD:refs/for/master/topic \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: # proc-receive hook
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: proc-receive> ok refs/for/master/topic
+	remote: proc-receive> option old-oid <COMMIT-A>
+	remote: proc-receive> option new-oid <COMMIT-B>
+	remote: proc-receive> ok refs/for/master/topic
+	remote: proc-receive> option refname refs/changes/24/124/1
+	remote: proc-receive> option old-oid <ZERO-OID>
+	remote: proc-receive> option new-oid <COMMIT-A>
+	remote: proc-receive> ok refs/for/master/topic
+	remote: proc-receive> option refname refs/changes/25/125/1
+	remote: proc-receive> option old-oid <COMMIT-A>
+	remote: proc-receive> option new-oid <COMMIT-B>
+	remote: # post-receive hook
+	remote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic
+	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1
+	remote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/25/125/1
+	To <URL/of/upstream.git>
+	 <OID-A>..<OID-B> HEAD -> refs/for/master/topic
+	 * [new reference] HEAD -> refs/changes/24/124/1
+	 <OID-A>..<OID-B> HEAD -> refs/changes/25/125/1
+	EOF
+	test_cmp expect actual &&
+	git -C "$upstream" show-ref >out &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	<COMMIT-A> refs/heads/master
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success "proc-receive: check remote-tracking #1 ($PROTOCOL)" '
+	git -C workbench show-ref |
+		grep -v -e refs/remotes -e refs/heads -e refs/tags >out &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	<COMMIT-A> refs/t/changes/24/124/1
+	<COMMIT-B> refs/t/changes/25/125/1
+	<COMMIT-B> refs/t/for/master/topic
+	EOF
+	test_cmp expect actual &&
+	git -C workbench update-ref -d refs/t/for/master/topic &&
+	git -C workbench update-ref -d refs/t/changes/24/124/1 &&
+	git -C workbench update-ref -d refs/t/changes/25/125/1
+'
+
+test_expect_success "setup proc-receive hook (multiple rewrites for one ref, no refname for the 2nd rewrite, $PROTOCOL)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v \
+		-r "ok refs/for/master/topic" \
+		-r "option refname refs/changes/24/124/1" \
+		-r "option old-oid $ZERO_OID" \
+		-r "option new-oid $A" \
+		-r "ok refs/for/master/topic" \
+		-r "option old-oid $A" \
+		-r "option new-oid $B" \
+		-r "ok refs/for/master/topic" \
+		-r "option refname refs/changes/25/125/1" \
+		-r "option old-oid $B" \
+		-r "option new-oid $A" \
+		-r "option forced-update"
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         :                       refs/for/master/topic(A)
+test_expect_success "proc-receive: multiple rewrites for one ref, no refname for the 2nd rewrite ($PROTOCOL)" '
+	git -C workbench push origin \
+		HEAD:refs/for/master/topic \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: # proc-receive hook
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: proc-receive> ok refs/for/master/topic
+	remote: proc-receive> option refname refs/changes/24/124/1
+	remote: proc-receive> option old-oid <ZERO-OID>
+	remote: proc-receive> option new-oid <COMMIT-A>
+	remote: proc-receive> ok refs/for/master/topic
+	remote: proc-receive> option old-oid <COMMIT-A>
+	remote: proc-receive> option new-oid <COMMIT-B>
+	remote: proc-receive> ok refs/for/master/topic
+	remote: proc-receive> option refname refs/changes/25/125/1
+	remote: proc-receive> option old-oid <COMMIT-B>
+	remote: proc-receive> option new-oid <COMMIT-A>
+	remote: proc-receive> option forced-update
+	remote: # post-receive hook
+	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1
+	remote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic
+	remote: post-receive< <COMMIT-B> <COMMIT-A> refs/changes/25/125/1
+	To <URL/of/upstream.git>
+	 * [new reference] HEAD -> refs/changes/24/124/1
+	 <OID-A>..<OID-B> HEAD -> refs/for/master/topic
+	 + <OID-B>...<OID-A> HEAD -> refs/changes/25/125/1 (forced update)
+	EOF
+	test_cmp expect actual &&
+	git -C "$upstream" show-ref >out &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	<COMMIT-A> refs/heads/master
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success "proc-receive: check remote-tracking #2 ($PROTOCOL)" '
+	git -C workbench show-ref |
+		grep -v -e refs/remotes -e refs/heads -e refs/tags >out &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	<COMMIT-A> refs/t/changes/24/124/1
+	<COMMIT-A> refs/t/changes/25/125/1
+	<COMMIT-B> refs/t/for/master/topic
+	EOF
+	test_cmp expect actual &&
+	git -C workbench update-ref -d refs/t/for/master/topic &&
+	git -C workbench update-ref -d refs/t/changes/24/124/1 &&
+	git -C workbench update-ref -d refs/t/changes/25/125/1
+'
+
+test_expect_success "setup proc-receive hook (multiple rewrites for one ref, $PROTOCOL)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v \
+		-r "ok refs/for/master/topic" \
+		-r "option refname refs/changes/23/123/1" \
+		-r "ok refs/for/master/topic" \
+		-r "option refname refs/changes/24/124/2" \
+		-r "option old-oid $A" \
+		-r "option new-oid $B"
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         :                       refs/for/master/topic(A)
+test_expect_success "proc-receive: multiple rewrites for one ref ($PROTOCOL)" '
+	git -C workbench push origin \
+		HEAD:refs/for/master/topic \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: # proc-receive hook
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: proc-receive> ok refs/for/master/topic
+	remote: proc-receive> option refname refs/changes/23/123/1
+	remote: proc-receive> ok refs/for/master/topic
+	remote: proc-receive> option refname refs/changes/24/124/2
+	remote: proc-receive> option old-oid <COMMIT-A>
+	remote: proc-receive> option new-oid <COMMIT-B>
+	remote: # post-receive hook
+	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/23/123/1
+	remote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/24/124/2
+	To <URL/of/upstream.git>
+	 * [new reference] HEAD -> refs/changes/23/123/1
+	 <OID-A>..<OID-B> HEAD -> refs/changes/24/124/2
+	EOF
+	test_cmp expect actual &&
+	git -C "$upstream" show-ref >out &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	<COMMIT-A> refs/heads/master
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success "proc-receive: check remote-tracking #3 ($PROTOCOL)" '
+	git -C workbench show-ref |
+		grep -v -e refs/remotes -e refs/heads -e refs/tags >out &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	<COMMIT-A> refs/t/changes/23/123/1
+	<COMMIT-B> refs/t/changes/24/124/2
+	EOF
+	test_cmp expect actual &&
+	git -C workbench update-ref -d refs/t/changes/24/124/1 &&
+	git -C workbench update-ref -d refs/t/changes/25/125/2
+'
diff --git a/third_party/git/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh b/third_party/git/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh
new file mode 100644
index 0000000000..77b5b22ed4
--- /dev/null
+++ b/third_party/git/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh
@@ -0,0 +1,172 @@
+test_expect_success "setup proc-receive hook (multiple rewrites for one ref, no refname for the 1st rewrite, $PROTOCOL/porcelain)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v \
+		-r "ok refs/for/master/topic" \
+		-r "option old-oid $A" \
+		-r "option new-oid $B" \
+		-r "ok refs/for/master/topic" \
+		-r "option refname refs/changes/24/124/1" \
+		-r "option old-oid $ZERO_OID" \
+		-r "option new-oid $A" \
+		-r "ok refs/for/master/topic" \
+		-r "option refname refs/changes/25/125/1" \
+		-r "option old-oid $A" \
+		-r "option new-oid $B"
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         :                       refs/for/master/topic(A)
+test_expect_success "proc-receive: multiple rewrite for one ref, no refname for the 1st rewrite ($PROTOCOL/porcelain)" '
+	git -C workbench push --porcelain origin \
+		HEAD:refs/for/master/topic \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: # proc-receive hook
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: proc-receive> ok refs/for/master/topic
+	remote: proc-receive> option old-oid <COMMIT-A>
+	remote: proc-receive> option new-oid <COMMIT-B>
+	remote: proc-receive> ok refs/for/master/topic
+	remote: proc-receive> option refname refs/changes/24/124/1
+	remote: proc-receive> option old-oid <ZERO-OID>
+	remote: proc-receive> option new-oid <COMMIT-A>
+	remote: proc-receive> ok refs/for/master/topic
+	remote: proc-receive> option refname refs/changes/25/125/1
+	remote: proc-receive> option old-oid <COMMIT-A>
+	remote: proc-receive> option new-oid <COMMIT-B>
+	remote: # post-receive hook
+	remote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic
+	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1
+	remote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/25/125/1
+	To <URL/of/upstream.git>
+	     HEAD:refs/for/master/topic    <OID-A>..<OID-B>
+	*    HEAD:refs/changes/24/124/1    [new reference]
+	     HEAD:refs/changes/25/125/1    <OID-A>..<OID-B>
+	Done
+	EOF
+	test_cmp expect actual &&
+	git -C "$upstream" show-ref >out &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	<COMMIT-A> refs/heads/master
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (multiple rewrites for one ref, no refname for the 2nd rewrite, $PROTOCOL/porcelain)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v \
+		-r "ok refs/for/master/topic" \
+		-r "option refname refs/changes/24/124/1" \
+		-r "option old-oid $ZERO_OID" \
+		-r "option new-oid $A" \
+		-r "ok refs/for/master/topic" \
+		-r "option old-oid $A" \
+		-r "option new-oid $B" \
+		-r "ok refs/for/master/topic" \
+		-r "option refname refs/changes/25/125/1" \
+		-r "option old-oid $B" \
+		-r "option new-oid $A" \
+		-r "option forced-update"
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         :                       refs/for/master/topic(A)
+test_expect_success "proc-receive: multiple rewrites for one ref, no refname for the 2nd rewrite ($PROTOCOL/porcelain)" '
+	git -C workbench push --porcelain origin \
+		HEAD:refs/for/master/topic \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: # proc-receive hook
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: proc-receive> ok refs/for/master/topic
+	remote: proc-receive> option refname refs/changes/24/124/1
+	remote: proc-receive> option old-oid <ZERO-OID>
+	remote: proc-receive> option new-oid <COMMIT-A>
+	remote: proc-receive> ok refs/for/master/topic
+	remote: proc-receive> option old-oid <COMMIT-A>
+	remote: proc-receive> option new-oid <COMMIT-B>
+	remote: proc-receive> ok refs/for/master/topic
+	remote: proc-receive> option refname refs/changes/25/125/1
+	remote: proc-receive> option old-oid <COMMIT-B>
+	remote: proc-receive> option new-oid <COMMIT-A>
+	remote: proc-receive> option forced-update
+	remote: # post-receive hook
+	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1
+	remote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic
+	remote: post-receive< <COMMIT-B> <COMMIT-A> refs/changes/25/125/1
+	To <URL/of/upstream.git>
+	*    HEAD:refs/changes/24/124/1    [new reference]
+	     HEAD:refs/for/master/topic    <OID-A>..<OID-B>
+	+    HEAD:refs/changes/25/125/1    <OID-B>...<OID-A> (forced update)
+	Done
+	EOF
+	test_cmp expect actual &&
+	git -C "$upstream" show-ref >out &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	<COMMIT-A> refs/heads/master
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (multiple rewrites for one ref, $PROTOCOL/porcelain)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v \
+		-r "ok refs/for/master/topic" \
+		-r "option refname refs/changes/23/123/1" \
+		-r "ok refs/for/master/topic" \
+		-r "option refname refs/changes/24/124/2" \
+		-r "option old-oid $A" \
+		-r "option new-oid $B"
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         :                       refs/for/master/topic(A)
+test_expect_success "proc-receive: multiple rewrites for one ref ($PROTOCOL/porcelain)" '
+	git -C workbench push --porcelain origin \
+		HEAD:refs/for/master/topic \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: # proc-receive hook
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: proc-receive> ok refs/for/master/topic
+	remote: proc-receive> option refname refs/changes/23/123/1
+	remote: proc-receive> ok refs/for/master/topic
+	remote: proc-receive> option refname refs/changes/24/124/2
+	remote: proc-receive> option old-oid <COMMIT-A>
+	remote: proc-receive> option new-oid <COMMIT-B>
+	remote: # post-receive hook
+	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/23/123/1
+	remote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/24/124/2
+	To <URL/of/upstream.git>
+	*    HEAD:refs/changes/23/123/1    [new reference]
+	     HEAD:refs/changes/24/124/2    <OID-A>..<OID-B>
+	Done
+	EOF
+	test_cmp expect actual &&
+	git -C "$upstream" show-ref >out &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	<COMMIT-A> refs/heads/master
+	EOF
+	test_cmp expect actual
+'
diff --git a/third_party/git/t/t5411/test-0038-report-mixed-refs.sh b/third_party/git/t/t5411/test-0038-report-mixed-refs.sh
new file mode 100644
index 0000000000..a74a2cb449
--- /dev/null
+++ b/third_party/git/t/t5411/test-0038-report-mixed-refs.sh
@@ -0,0 +1,89 @@
+test_expect_success "setup proc-receive hook ($PROTOCOL)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v \
+		-r "ok refs/for/next/topic2" \
+		-r "ng refs/for/next/topic1 fail to call Web API" \
+		-r "ok refs/for/master/topic" \
+		-r "option refname refs/for/master/topic" \
+		-r "option old-oid $A" \
+		-r "option new-oid $B"
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         : (B)                   bar(A)  baz(A)  refs/for/next/topic(A)  foo(A)  refs/for/master/topic(A)
+test_expect_success "proc-receive: report update of mixed refs ($PROTOCOL)" '
+	test_must_fail git -C workbench push origin \
+		$B:refs/heads/master \
+		HEAD:refs/heads/bar \
+		HEAD:refs/heads/baz \
+		HEAD:refs/for/next/topic2 \
+		HEAD:refs/for/next/topic1 \
+		HEAD:refs/heads/foo \
+		HEAD:refs/for/master/topic \
+		HEAD:refs/for/next/topic3 \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic1
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic3
+	remote: # proc-receive hook
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic1
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic3
+	remote: proc-receive> ok refs/for/next/topic2
+	remote: proc-receive> ng refs/for/next/topic1 fail to call Web API
+	remote: proc-receive> ok refs/for/master/topic
+	remote: proc-receive> option refname refs/for/master/topic
+	remote: proc-receive> option old-oid <COMMIT-A>
+	remote: proc-receive> option new-oid <COMMIT-B>
+	remote: # post-receive hook
+	remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar
+	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz
+	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2
+	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo
+	remote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic
+	To <URL/of/upstream.git>
+	 <OID-A>..<OID-B> <COMMIT-B> -> master
+	 * [new branch] HEAD -> bar
+	 * [new branch] HEAD -> baz
+	 * [new reference] HEAD -> refs/for/next/topic2
+	 * [new branch] HEAD -> foo
+	 <OID-A>..<OID-B> HEAD -> refs/for/master/topic
+	 ! [remote rejected] HEAD -> refs/for/next/topic1 (fail to call Web API)
+	 ! [remote rejected] HEAD -> refs/for/next/topic3 (proc-receive failed to report status)
+	EOF
+	test_cmp expect actual &&
+	git -C "$upstream" show-ref >out &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	<COMMIT-A> refs/heads/bar
+	<COMMIT-A> refs/heads/baz
+	<COMMIT-A> refs/heads/foo
+	<COMMIT-B> refs/heads/master
+	EOF
+	test_cmp expect actual
+'
+
+# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)
+# Refs of workbench: master(A)  tags/v123
+test_expect_success "cleanup ($PROTOCOL)" '
+	(
+		cd "$upstream" &&
+		git update-ref refs/heads/master $A &&
+		git update-ref -d refs/heads/foo &&
+		git update-ref -d refs/heads/bar &&
+		git update-ref -d refs/heads/baz
+	)
+'
diff --git a/third_party/git/t/t5411/test-0039-report-mixed-refs--porcelain.sh b/third_party/git/t/t5411/test-0039-report-mixed-refs--porcelain.sh
new file mode 100644
index 0000000000..e4baa13ea3
--- /dev/null
+++ b/third_party/git/t/t5411/test-0039-report-mixed-refs--porcelain.sh
@@ -0,0 +1,91 @@
+test_expect_success "setup proc-receive hook ($PROTOCOL/porcelain)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v \
+		-r "ok refs/for/next/topic2" \
+		-r "ng refs/for/next/topic1 fail to call Web API" \
+		-r "ok refs/for/master/topic" \
+		-r "option refname refs/for/master/topic" \
+		-r "option old-oid $A" \
+		-r "option new-oid $B"
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         : (B)                   bar(A)  baz(A)  refs/for/next/topic(A)  foo(A)  refs/for/master/topic(A)
+test_expect_success "proc-receive: report update of mixed refs ($PROTOCOL/porcelain)" '
+	test_must_fail git -C workbench push --porcelain origin \
+		$B:refs/heads/master \
+		HEAD:refs/heads/bar \
+		HEAD:refs/heads/baz \
+		HEAD:refs/for/next/topic2 \
+		HEAD:refs/for/next/topic1 \
+		HEAD:refs/heads/foo \
+		HEAD:refs/for/master/topic \
+		HEAD:refs/for/next/topic3 \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic1
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic3
+	remote: # proc-receive hook
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic1
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic3
+	remote: proc-receive> ok refs/for/next/topic2
+	remote: proc-receive> ng refs/for/next/topic1 fail to call Web API
+	remote: proc-receive> ok refs/for/master/topic
+	remote: proc-receive> option refname refs/for/master/topic
+	remote: proc-receive> option old-oid <COMMIT-A>
+	remote: proc-receive> option new-oid <COMMIT-B>
+	remote: # post-receive hook
+	remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar
+	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz
+	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2
+	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo
+	remote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic
+	To <URL/of/upstream.git>
+	     <COMMIT-B>:refs/heads/master    <OID-A>..<OID-B>
+	*    HEAD:refs/heads/bar    [new branch]
+	*    HEAD:refs/heads/baz    [new branch]
+	*    HEAD:refs/for/next/topic2    [new reference]
+	*    HEAD:refs/heads/foo    [new branch]
+	     HEAD:refs/for/master/topic    <OID-A>..<OID-B>
+	!    HEAD:refs/for/next/topic1    [remote rejected] (fail to call Web API)
+	!    HEAD:refs/for/next/topic3    [remote rejected] (proc-receive failed to report status)
+	Done
+	EOF
+	test_cmp expect actual &&
+	git -C "$upstream" show-ref >out &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	<COMMIT-A> refs/heads/bar
+	<COMMIT-A> refs/heads/baz
+	<COMMIT-A> refs/heads/foo
+	<COMMIT-B> refs/heads/master
+	EOF
+	test_cmp expect actual
+'
+
+# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)
+# Refs of workbench: master(A)  tags/v123
+test_expect_success "cleanup ($PROTOCOL/porcelain)" '
+	(
+		cd "$upstream" &&
+		git update-ref refs/heads/master $A &&
+		git update-ref -d refs/heads/foo &&
+		git update-ref -d refs/heads/bar &&
+		git update-ref -d refs/heads/baz
+	)
+
+'
diff --git a/third_party/git/t/t5411/test-0040-process-all-refs.sh b/third_party/git/t/t5411/test-0040-process-all-refs.sh
new file mode 100644
index 0000000000..b07c999f53
--- /dev/null
+++ b/third_party/git/t/t5411/test-0040-process-all-refs.sh
@@ -0,0 +1,113 @@
+test_expect_success "config receive.procReceiveRefs = refs ($PROTOCOL)" '
+	git -C "$upstream" config --unset-all receive.procReceiveRefs &&
+	git -C "$upstream" config --add receive.procReceiveRefs refs
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+test_expect_success "setup upstream branches ($PROTOCOL)" '
+	(
+		cd "$upstream" &&
+		git update-ref refs/heads/master $B &&
+		git update-ref refs/heads/foo $A &&
+		git update-ref refs/heads/bar $A &&
+		git update-ref refs/heads/baz $A
+	)
+
+'
+
+test_expect_success "setup proc-receive hook ($PROTOCOL)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v \
+		-r "ok refs/heads/master" \
+		-r "option fall-through" \
+		-r "ok refs/heads/foo" \
+		-r "option fall-through" \
+		-r "ok refs/heads/bar" \
+		-r "option fall-through" \
+		-r "ok refs/for/master/topic" \
+		-r "option refname refs/pull/123/head" \
+		-r "option old-oid $A" \
+		-r "option new-oid $B" \
+		-r "ok refs/for/next/topic" \
+		-r "option refname refs/pull/124/head" \
+		-r "option old-oid $B" \
+		-r "option new-oid $A" \
+		-r "option forced-update"
+	EOF
+'
+
+# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)
+# Refs of workbench: master(A)  tags/v123
+# git push -f      : master(A)             (NULL)  (B)              refs/for/master/topic(A)  refs/for/next/topic(A)
+test_expect_success "proc-receive: process all refs ($PROTOCOL)" '
+	git -C workbench push -f origin \
+		HEAD:refs/heads/master \
+		:refs/heads/foo \
+		$B:refs/heads/bar \
+		HEAD:refs/for/master/topic \
+		HEAD:refs/for/next/topic \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar
+	remote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo
+	remote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic
+	remote: # proc-receive hook
+	remote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar
+	remote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo
+	remote: proc-receive< <COMMIT-B> <COMMIT-A> refs/heads/master
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic
+	remote: proc-receive> ok refs/heads/master
+	remote: proc-receive> option fall-through
+	remote: proc-receive> ok refs/heads/foo
+	remote: proc-receive> option fall-through
+	remote: proc-receive> ok refs/heads/bar
+	remote: proc-receive> option fall-through
+	remote: proc-receive> ok refs/for/master/topic
+	remote: proc-receive> option refname refs/pull/123/head
+	remote: proc-receive> option old-oid <COMMIT-A>
+	remote: proc-receive> option new-oid <COMMIT-B>
+	remote: proc-receive> ok refs/for/next/topic
+	remote: proc-receive> option refname refs/pull/124/head
+	remote: proc-receive> option old-oid <COMMIT-B>
+	remote: proc-receive> option new-oid <COMMIT-A>
+	remote: proc-receive> option forced-update
+	remote: # post-receive hook
+	remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar
+	remote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo
+	remote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master
+	remote: post-receive< <COMMIT-A> <COMMIT-B> refs/pull/123/head
+	remote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head
+	To <URL/of/upstream.git>
+	 <OID-A>..<OID-B> <COMMIT-B> -> bar
+	 - [deleted] foo
+	 + <OID-B>...<OID-A> HEAD -> master (forced update)
+	 <OID-A>..<OID-B> HEAD -> refs/pull/123/head
+	 + <OID-B>...<OID-A> HEAD -> refs/pull/124/head (forced update)
+	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
+	EOF
+	test_cmp expect actual
+'
+
+# Refs of upstream : master(A)             bar(A)  baz(B)
+# Refs of workbench: master(A)  tags/v123
+test_expect_success "cleanup ($PROTOCOL)" '
+	(
+		cd "$upstream" &&
+		git update-ref -d refs/heads/bar &&
+		git update-ref -d refs/heads/baz
+	)
+'
diff --git a/third_party/git/t/t5411/test-0041-process-all-refs--porcelain.sh b/third_party/git/t/t5411/test-0041-process-all-refs--porcelain.sh
new file mode 100644
index 0000000000..0dd9824616
--- /dev/null
+++ b/third_party/git/t/t5411/test-0041-process-all-refs--porcelain.sh
@@ -0,0 +1,114 @@
+test_expect_success "config receive.procReceiveRefs = refs ($PROTOCOL/porcelain)" '
+	git -C "$upstream" config --unset-all receive.procReceiveRefs &&
+	git -C "$upstream" config --add receive.procReceiveRefs refs
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+test_expect_success "setup upstream branches ($PROTOCOL/porcelain)" '
+	(
+		cd "$upstream" &&
+		git update-ref refs/heads/master $B &&
+		git update-ref refs/heads/foo $A &&
+		git update-ref refs/heads/bar $A &&
+		git update-ref refs/heads/baz $A
+	)
+
+'
+
+test_expect_success "setup proc-receive hook ($PROTOCOL/porcelain)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v \
+		-r "ok refs/heads/master" \
+		-r "option fall-through" \
+		-r "ok refs/heads/foo" \
+		-r "option fall-through" \
+		-r "ok refs/heads/bar" \
+		-r "option fall-through" \
+		-r "ok refs/for/master/topic" \
+		-r "option refname refs/pull/123/head" \
+		-r "option old-oid $A" \
+		-r "option new-oid $B" \
+		-r "ok refs/for/next/topic" \
+		-r "option refname refs/pull/124/head" \
+		-r "option old-oid $B" \
+		-r "option new-oid $A" \
+		-r "option forced-update"
+	EOF
+'
+
+# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)
+# Refs of workbench: master(A)  tags/v123
+# git push -f      : master(A)             (NULL)  (B)              refs/for/master/topic(A)  refs/for/next/topic(A)
+test_expect_success "proc-receive: process all refs ($PROTOCOL/porcelain)" '
+	git -C workbench push --porcelain -f origin \
+		HEAD:refs/heads/master \
+		:refs/heads/foo \
+		$B:refs/heads/bar \
+		HEAD:refs/for/master/topic \
+		HEAD:refs/for/next/topic \
+		>out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar
+	remote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo
+	remote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic
+	remote: # proc-receive hook
+	remote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar
+	remote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo
+	remote: proc-receive< <COMMIT-B> <COMMIT-A> refs/heads/master
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic
+	remote: proc-receive> ok refs/heads/master
+	remote: proc-receive> option fall-through
+	remote: proc-receive> ok refs/heads/foo
+	remote: proc-receive> option fall-through
+	remote: proc-receive> ok refs/heads/bar
+	remote: proc-receive> option fall-through
+	remote: proc-receive> ok refs/for/master/topic
+	remote: proc-receive> option refname refs/pull/123/head
+	remote: proc-receive> option old-oid <COMMIT-A>
+	remote: proc-receive> option new-oid <COMMIT-B>
+	remote: proc-receive> ok refs/for/next/topic
+	remote: proc-receive> option refname refs/pull/124/head
+	remote: proc-receive> option old-oid <COMMIT-B>
+	remote: proc-receive> option new-oid <COMMIT-A>
+	remote: proc-receive> option forced-update
+	remote: # post-receive hook
+	remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar
+	remote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo
+	remote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master
+	remote: post-receive< <COMMIT-A> <COMMIT-B> refs/pull/123/head
+	remote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head
+	To <URL/of/upstream.git>
+	     <COMMIT-B>:refs/heads/bar    <OID-A>..<OID-B>
+	-    :refs/heads/foo    [deleted]
+	+    HEAD:refs/heads/master    <OID-B>...<OID-A> (forced update)
+	     HEAD:refs/pull/123/head    <OID-A>..<OID-B>
+	+    HEAD:refs/pull/124/head    <OID-B>...<OID-A> (forced update)
+	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
+	EOF
+	test_cmp expect actual
+'
+
+# Refs of upstream : master(A)             bar(A)  baz(B)
+# Refs of workbench: master(A)  tags/v123
+test_expect_success "cleanup ($PROTOCOL/porcelain)" '
+	(
+		cd "$upstream" &&
+		git update-ref -d refs/heads/bar &&
+		git update-ref -d refs/heads/baz
+	)
+'
diff --git a/third_party/git/t/t5411/test-0050-proc-receive-refs-with-modifiers.sh b/third_party/git/t/t5411/test-0050-proc-receive-refs-with-modifiers.sh
new file mode 100644
index 0000000000..c22849cbe2
--- /dev/null
+++ b/third_party/git/t/t5411/test-0050-proc-receive-refs-with-modifiers.sh
@@ -0,0 +1,135 @@
+test_expect_success "config receive.procReceiveRefs with modifiers ($PROTOCOL)" '
+	(
+		cd "$upstream" &&
+		git config --unset-all receive.procReceiveRefs &&
+		git config --add receive.procReceiveRefs m:refs/heads/master &&
+		git config --add receive.procReceiveRefs ad:refs/heads &&
+		git config --add receive.procReceiveRefs "a!:refs/heads"
+	)
+'
+
+test_expect_success "setup proc-receive hook ($PROTOCOL)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v \
+		-r "ok refs/heads/master" \
+		-r "option refname refs/pull/123/head" \
+		-r "option old-oid $A" \
+		-r "option new-oid $B" \
+		-r "ok refs/tags/v123 " \
+		-r "option refname refs/pull/124/head"
+	EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+# git push         : master(B)  tags/v123
+test_expect_success "proc-receive: update branch and new tag ($PROTOCOL)" '
+	git -C workbench push origin \
+		$B:refs/heads/master \
+		v123 >out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+	remote: pre-receive< <ZERO-OID> <TAG-v123> refs/tags/v123
+	remote: # proc-receive hook
+	remote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+	remote: proc-receive< <ZERO-OID> <TAG-v123> refs/tags/v123
+	remote: proc-receive> ok refs/heads/master
+	remote: proc-receive> option refname refs/pull/123/head
+	remote: proc-receive> option old-oid <COMMIT-A>
+	remote: proc-receive> option new-oid <COMMIT-B>
+	remote: proc-receive> ok refs/tags/v123
+	remote: proc-receive> option refname refs/pull/124/head
+	remote: # post-receive hook
+	remote: post-receive< <COMMIT-A> <COMMIT-B> refs/pull/123/head
+	remote: post-receive< <ZERO-OID> <TAG-v123> refs/pull/124/head
+	To <URL/of/upstream.git>
+	 <OID-A>..<OID-B> <COMMIT-B> -> refs/pull/123/head
+	 * [new reference] v123 -> refs/pull/124/head
+	EOF
+	test_cmp expect actual &&
+	git -C "$upstream" show-ref >out &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	<COMMIT-A> refs/heads/master
+	EOF
+	test_cmp expect actual
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A)  tags/v123
+test_expect_success "setup upstream: create tags/v123 ($PROTOCOL)" '
+	git -C "$upstream" update-ref refs/heads/topic $A &&
+	git -C "$upstream" update-ref refs/tags/v123 $TAG &&
+	git -C "$upstream" show-ref >out &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	<COMMIT-A> refs/heads/master
+	<COMMIT-A> refs/heads/topic
+	<TAG-v123> refs/tags/v123
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook ($PROTOCOL)" '
+	write_script "$upstream/hooks/proc-receive" <<-EOF
+	printf >&2 "# proc-receive hook\n"
+	test-tool proc-receive -v \
+		-r "ok refs/heads/master" \
+		-r "option refname refs/pull/123/head" \
+		-r "option old-oid $A" \
+		-r "option new-oid $ZERO_OID" \
+		-r "ok refs/heads/next" \
+		-r "option refname refs/pull/124/head" \
+		-r "option new-oid $A"
+	EOF
+'
+
+# Refs of upstream : master(A)  topic(A)  tags/v123
+# Refs of workbench: master(A)            tags/v123
+# git push         : NULL       topic(B)  NULL       next(A)
+test_expect_success "proc-receive: create/delete branch, and delete tag ($PROTOCOL)" '
+	git -C workbench push origin \
+		:refs/heads/master \
+		$B:refs/heads/topic \
+		$A:refs/heads/next \
+		:refs/tags/v123 >out 2>&1 &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	remote: # pre-receive hook
+	remote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/master
+	remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/topic
+	remote: pre-receive< <TAG-v123> <ZERO-OID> refs/tags/v123
+	remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+	remote: # proc-receive hook
+	remote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/master
+	remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+	remote: proc-receive> ok refs/heads/master
+	remote: proc-receive> option refname refs/pull/123/head
+	remote: proc-receive> option old-oid <COMMIT-A>
+	remote: proc-receive> option new-oid <ZERO-OID>
+	remote: proc-receive> ok refs/heads/next
+	remote: proc-receive> option refname refs/pull/124/head
+	remote: proc-receive> option new-oid <COMMIT-A>
+	remote: # post-receive hook
+	remote: post-receive< <COMMIT-A> <ZERO-OID> refs/pull/123/head
+	remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/topic
+	remote: post-receive< <TAG-v123> <ZERO-OID> refs/tags/v123
+	remote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/124/head
+	To <URL/of/upstream.git>
+	 - [deleted] refs/pull/123/head
+	 <OID-A>..<OID-B> <COMMIT-B> -> topic
+	 - [deleted] v123
+	 * [new reference] <COMMIT-A> -> refs/pull/124/head
+	EOF
+	test_cmp expect actual &&
+	git -C "$upstream" show-ref >out &&
+	make_user_friendly_and_stable_output <out >actual &&
+	cat >expect <<-EOF &&
+	<COMMIT-A> refs/heads/master
+	<COMMIT-B> refs/heads/topic
+	EOF
+	test_cmp expect actual
+'
diff --git a/third_party/git/t/t5500-fetch-pack.sh b/third_party/git/t/t5500-fetch-pack.sh
index 1c71c0ec77..3557374312 100755
--- a/third_party/git/t/t5500-fetch-pack.sh
+++ b/third_party/git/t/t5500-fetch-pack.sh
@@ -385,6 +385,54 @@ test_expect_success 'clone shallow with packed refs' '
 	test_cmp count8.expected count8.actual
 '
 
+test_expect_success 'in_vain not triggered before first ACK' '
+	rm -rf myserver myclient &&
+	git init myserver &&
+	test_commit -C myserver foo &&
+	git clone "file://$(pwd)/myserver" myclient &&
+
+	# MAX_IN_VAIN is 256. Because of batching, the client will send 496
+	# (16+32+64+128+256) commits, not 256, before giving up. So create 496
+	# irrelevant commits.
+	test_commit_bulk -C myclient 496 &&
+
+	# The new commit that the client wants to fetch.
+	test_commit -C myserver bar &&
+
+	git -C myclient fetch --progress origin 2>log &&
+	test_i18ngrep "remote: Total 3 " log
+'
+
+test_expect_success 'in_vain resetted upon ACK' '
+	rm -rf myserver myclient &&
+	git init myserver &&
+
+	# Linked list of commits on master. The first is common; the rest are
+	# not.
+	test_commit -C myserver first_master_commit &&
+	git clone "file://$(pwd)/myserver" myclient &&
+	test_commit_bulk -C myclient 255 &&
+
+	# Another linked list of commits on anotherbranch with no connection to
+	# master. The first is common; the rest are not.
+	git -C myserver checkout --orphan anotherbranch &&
+	test_commit -C myserver first_anotherbranch_commit &&
+	git -C myclient fetch origin anotherbranch:refs/heads/anotherbranch &&
+	git -C myclient checkout anotherbranch &&
+	test_commit_bulk -C myclient 255 &&
+
+	# The new commit that the client wants to fetch.
+	git -C myserver checkout master &&
+	test_commit -C myserver to_fetch &&
+
+	# The client will send (as "have"s) all 256 commits in anotherbranch
+	# first. The 256th commit is common between the client and the server,
+	# and should reset in_vain. This allows negotiation to continue until
+	# the client reports that first_anotherbranch_commit is common.
+	git -C myclient fetch --progress origin master 2>log &&
+	test_i18ngrep "Total 3 " log
+'
+
 test_expect_success 'fetch in shallow repo unreachable shallow objects' '
 	(
 		git clone --bare --branch B --single-branch "file://$(pwd)/." no-reflog &&
@@ -440,11 +488,12 @@ test_expect_success 'setup tests for the --stdin parameter' '
 '
 
 test_expect_success 'setup fetch refs from cmdline v[12]' '
+	cp -r client client0 &&
 	cp -r client client1 &&
 	cp -r client client2
 '
 
-for version in '' 1 2
+for version in '' 0 1 2
 do
 	test_expect_success "protocol.version=$version fetch refs from cmdline" "
 		(
@@ -638,7 +687,7 @@ test_expect_success 'fetch-pack cannot fetch a raw sha1 that is not advertised a
 	git init client &&
 	# Some protocol versions (e.g. 2) support fetching
 	# unadvertised objects, so restrict this test to v0.
-	test_must_fail env GIT_TEST_PROTOCOL_VERSION= git -C client fetch-pack ../server \
+	test_must_fail env GIT_TEST_PROTOCOL_VERSION=0 git -C client fetch-pack ../server \
 		$(git -C server rev-parse refs/heads/master^) 2>err &&
 	test_i18ngrep "Server does not allow request for unadvertised object" err
 '
@@ -708,13 +757,22 @@ do
 	# file with scheme
 	for p in file
 	do
-		test_expect_success "fetch-pack --diag-url $p://$h/$r" '
+		test_expect_success !MINGW "fetch-pack --diag-url $p://$h/$r" '
 			check_prot_path $p://$h/$r $p "/$r"
 		'
+		test_expect_success MINGW "fetch-pack --diag-url $p://$h/$r" '
+			check_prot_path $p://$h/$r $p "//$h/$r"
+		'
+		test_expect_success MINGW "fetch-pack --diag-url $p:///$r" '
+			check_prot_path $p:///$r $p "/$r"
+		'
 		# No "/~" -> "~" conversion for file
-		test_expect_success "fetch-pack --diag-url $p://$h/~$r" '
+		test_expect_success !MINGW "fetch-pack --diag-url $p://$h/~$r" '
 			check_prot_path $p://$h/~$r $p "/~$r"
 		'
+		test_expect_success MINGW "fetch-pack --diag-url $p://$h/~$r" '
+			check_prot_path $p://$h/~$r $p "//$h/~$r"
+		'
 	done
 	# file without scheme
 	for h in nohost nohost:12 [::1] [::1]:23 [ [:aa
@@ -783,6 +841,45 @@ test_expect_success 'clone shallow since selects no commits' '
 	)
 '
 
+# A few subtle things about the request in this test:
+#
+#  - the server must have commit-graphs present and enabled
+#
+#  - the history is such that our want/have share a common ancestor ("base"
+#    here)
+#
+#  - we send only a single have, which is fewer than a normal client would
+#    send. This ensures that we don't parse "base" up front with
+#    parse_object(), but rather traverse to it as a parent while deciding if we
+#    can stop the "have" negotiation, and call parse_commit(). The former
+#    sees the actual object data and so always loads the three oid, whereas the
+#    latter will try to load it lazily.
+#
+#  - we must use protocol v2, because it handles the "have" negotiation before
+#    processing the shallow directives
+#
+test_expect_success 'shallow since with commit graph and already-seen commit' '
+	test_create_repo shallow-since-graph &&
+	(
+	cd shallow-since-graph &&
+	test_commit base &&
+	test_commit master &&
+	git checkout -b other HEAD^ &&
+	test_commit other &&
+	git commit-graph write --reachable &&
+	git config core.commitGraph true &&
+
+	GIT_PROTOCOL=version=2 git upload-pack . <<-EOF >/dev/null
+	0012command=fetch
+	$(echo "object-format=$(test_oid algo)" | packetize)
+	00010013deepen-since 1
+	$(echo "want $(git rev-parse other)" | packetize)
+	$(echo "have $(git rev-parse master)" | packetize)
+	0000
+	EOF
+	)
+'
+
 test_expect_success 'shallow clone exclude tag two' '
 	test_create_repo shallow-exclude &&
 	(
@@ -870,7 +967,10 @@ test_expect_success 'filtering by size' '
 	git -C client fetch-pack --filter=blob:limit=0 ../server HEAD &&
 
 	# Ensure that object is not inadvertently fetched
-	test_must_fail git -C client cat-file -e $(git hash-object server/one.t)
+	commit=$(git -C server rev-parse HEAD) &&
+	blob=$(git hash-object server/one.t) &&
+	git -C client rev-list --objects --missing=allow-any "$commit" >oids &&
+	! grep "$blob" oids
 '
 
 test_expect_success 'filtering by size has no effect if support for it is not advertised' '
@@ -882,7 +982,10 @@ test_expect_success 'filtering by size has no effect if support for it is not ad
 	git -C client fetch-pack --filter=blob:limit=0 ../server HEAD 2> err &&
 
 	# Ensure that object is fetched
-	git -C client cat-file -e $(git hash-object server/one.t) &&
+	commit=$(git -C server rev-parse HEAD) &&
+	blob=$(git hash-object server/one.t) &&
+	git -C client rev-list --objects --missing=allow-any "$commit" >oids &&
+	grep "$blob" oids &&
 
 	test_i18ngrep "filtering not recognized by server" err
 '
@@ -897,16 +1000,17 @@ fetch_filter_blob_limit_zero () {
 	test_config -C "$SERVER" uploadpack.allowfilter 1 &&
 
 	git clone "$URL" client &&
-	test_config -C client extensions.partialclone origin &&
 
 	test_commit -C "$SERVER" two &&
 
 	git -C client fetch --filter=blob:limit=0 origin HEAD:somewhere &&
 
 	# Ensure that commit is fetched, but blob is not
-	test_config -C client extensions.partialclone "arbitrary string" &&
-	git -C client cat-file -e $(git -C "$SERVER" rev-parse two) &&
-	test_must_fail git -C client cat-file -e $(git hash-object "$SERVER/two.t")
+	commit=$(git -C "$SERVER" rev-parse two) &&
+	blob=$(git hash-object server/two.t) &&
+	git -C client rev-list --objects --missing=allow-any "$commit" >oids &&
+	grep "$commit" oids &&
+	! grep "$blob" oids
 }
 
 test_expect_success 'fetch with --filter=blob:limit=0' '
@@ -920,4 +1024,7 @@ test_expect_success 'fetch with --filter=blob:limit=0 and HTTP' '
 	fetch_filter_blob_limit_zero "$HTTPD_DOCUMENT_ROOT_PATH/server" "$HTTPD_URL/smart/server"
 '
 
+# DO NOT add non-httpd-specific tests here, because the last part of this
+# test script is only executed when httpd is available and enabled.
+
 test_done
diff --git a/third_party/git/t/t5504-fetch-receive-strict.sh b/third_party/git/t/t5504-fetch-receive-strict.sh
index fdfe179b11..1a16ac4c0d 100755
--- a/third_party/git/t/t5504-fetch-receive-strict.sh
+++ b/third_party/git/t/t5504-fetch-receive-strict.sh
@@ -64,6 +64,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' '
@@ -144,11 +145,11 @@ test_expect_success 'fsck with no skipList input' '
 
 test_expect_success 'setup sorted and unsorted skipLists' '
 	cat >SKIP.unsorted <<-EOF &&
-	0000000000000000000000000000000000000004
-	0000000000000000000000000000000000000002
+	$(test_oid 004)
+	$(test_oid 002)
 	$commit
-	0000000000000000000000000000000000000001
-	0000000000000000000000000000000000000003
+	$(test_oid 001)
+	$(test_oid 003)
 	EOF
 	sort SKIP.unsorted >SKIP.sorted
 '
@@ -172,14 +173,14 @@ test_expect_success 'fsck with invalid or bogus skipList input' '
 test_expect_success 'fsck with other accepted skipList input (comments & empty lines)' '
 	cat >SKIP.with-comment <<-EOF &&
 	# Some bad commit
-	0000000000000000000000000000000000000001
+	$(test_oid 001)
 	EOF
 	test_must_fail git -c fsck.skipList=SKIP.with-comment fsck 2>err-with-comment &&
 	test_i18ngrep "missingEmail" err-with-comment &&
 	cat >SKIP.with-empty-line <<-EOF &&
-	0000000000000000000000000000000000000001
+	$(test_oid 001)
 
-	0000000000000000000000000000000000000002
+	$(test_oid 002)
 	EOF
 	test_must_fail git -c fsck.skipList=SKIP.with-empty-line fsck 2>err-with-empty-line &&
 	test_i18ngrep "missingEmail" err-with-empty-line
@@ -204,7 +205,7 @@ test_expect_success 'fsck with exhaustive accepted skipList input (various types
 	echo " # Comment after whitespace" >>SKIP.exhaustive &&
 	echo "$commit # Our bad commit (with leading whitespace and trailing comment)" >>SKIP.exhaustive &&
 	echo "# Some bad commit (leading whitespace)" >>SKIP.exhaustive &&
-	echo "  0000000000000000000000000000000000000001" >>SKIP.exhaustive &&
+	echo "  $(test_oid 001)" >>SKIP.exhaustive &&
 	git -c fsck.skipList=SKIP.exhaustive fsck 2>err &&
 	test_must_be_empty err
 '
diff --git a/third_party/git/t/t5505-remote.sh b/third_party/git/t/t5505-remote.sh
index 883b32efa0..8d62edd98b 100755
--- a/third_party/git/t/t5505-remote.sh
+++ b/third_party/git/t/t5505-remote.sh
@@ -734,15 +734,53 @@ test_expect_success 'reject adding remote with an invalid name' '
 # the last two ones check if the config is updated.
 
 test_expect_success 'rename a remote' '
+	test_config_global remote.pushDefault origin &&
 	git clone one four &&
 	(
 		cd four &&
+		git config branch.master.pushRemote origin &&
 		git remote rename origin upstream &&
 		test -z "$(git for-each-ref refs/remotes/origin)" &&
 		test "$(git symbolic-ref refs/remotes/upstream/HEAD)" = "refs/remotes/upstream/master" &&
 		test "$(git rev-parse upstream/master)" = "$(git rev-parse master)" &&
 		test "$(git config remote.upstream.fetch)" = "+refs/heads/*:refs/remotes/upstream/*" &&
-		test "$(git config branch.master.remote)" = "upstream"
+		test "$(git config branch.master.remote)" = "upstream" &&
+		test "$(git config branch.master.pushRemote)" = "upstream" &&
+		test "$(git config --global remote.pushDefault)" = "origin"
+	)
+'
+
+test_expect_success 'rename a remote renames repo remote.pushDefault' '
+	git clone one four.1 &&
+	(
+		cd four.1 &&
+		git config remote.pushDefault origin &&
+		git remote rename origin upstream &&
+		test "$(git config --local remote.pushDefault)" = "upstream"
+	)
+'
+
+test_expect_success 'rename a remote renames repo remote.pushDefault but ignores global' '
+	test_config_global remote.pushDefault other &&
+	git clone one four.2 &&
+	(
+		cd four.2 &&
+		git config remote.pushDefault origin &&
+		git remote rename origin upstream &&
+		test "$(git config --global remote.pushDefault)" = "other" &&
+		test "$(git config --local remote.pushDefault)" = "upstream"
+	)
+'
+
+test_expect_success 'rename a remote renames repo remote.pushDefault but keeps global' '
+	test_config_global remote.pushDefault origin &&
+	git clone one four.3 &&
+	(
+		cd four.3 &&
+		git config remote.pushDefault origin &&
+		git remote rename origin upstream &&
+		test "$(git config --global remote.pushDefault)" = "origin" &&
+		test "$(git config --local remote.pushDefault)" = "upstream"
 	)
 '
 
@@ -784,6 +822,54 @@ test_expect_success 'rename succeeds with existing remote.<target>.prune' '
 	git -C four.four remote rename origin upstream
 '
 
+test_expect_success 'remove a remote' '
+	test_config_global remote.pushDefault origin &&
+	git clone one four.five &&
+	(
+		cd four.five &&
+		git config branch.master.pushRemote origin &&
+		git remote remove origin &&
+		test -z "$(git for-each-ref refs/remotes/origin)" &&
+		test_must_fail git config branch.master.remote &&
+		test_must_fail git config branch.master.pushRemote &&
+		test "$(git config --global remote.pushDefault)" = "origin"
+	)
+'
+
+test_expect_success 'remove a remote removes repo remote.pushDefault' '
+	git clone one four.five.1 &&
+	(
+		cd four.five.1 &&
+		git config remote.pushDefault origin &&
+		git remote remove origin &&
+		test_must_fail git config --local remote.pushDefault
+	)
+'
+
+test_expect_success 'remove a remote removes repo remote.pushDefault but ignores global' '
+	test_config_global remote.pushDefault other &&
+	git clone one four.five.2 &&
+	(
+		cd four.five.2 &&
+		git config remote.pushDefault origin &&
+		git remote remove origin &&
+		test "$(git config --global remote.pushDefault)" = "other" &&
+		test_must_fail git config --local remote.pushDefault
+	)
+'
+
+test_expect_success 'remove a remote removes repo remote.pushDefault but keeps global' '
+	test_config_global remote.pushDefault origin &&
+	git clone one four.five.3 &&
+	(
+		cd four.five.3 &&
+		git config remote.pushDefault origin &&
+		git remote remove origin &&
+		test "$(git config --global remote.pushDefault)" = "origin" &&
+		test_must_fail git config --local remote.pushDefault
+	)
+'
+
 cat >remotes_origin <<EOF
 URL: $(pwd)/one
 Push: refs/heads/master:refs/heads/upstream
@@ -902,7 +988,7 @@ test_expect_success 'remote set-branches' '
 	+refs/heads/maint:refs/remotes/scratch/maint
 	+refs/heads/master:refs/remotes/scratch/master
 	+refs/heads/next:refs/remotes/scratch/next
-	+refs/heads/pu:refs/remotes/scratch/pu
+	+refs/heads/seen:refs/remotes/scratch/seen
 	+refs/heads/t/topic:refs/remotes/scratch/t/topic
 	EOF
 	sort <<-\EOF >expect.setup-ffonly &&
@@ -912,7 +998,7 @@ test_expect_success 'remote set-branches' '
 	sort <<-\EOF >expect.respect-ffonly &&
 	refs/heads/master:refs/remotes/scratch/master
 	+refs/heads/next:refs/remotes/scratch/next
-	+refs/heads/pu:refs/remotes/scratch/pu
+	+refs/heads/seen:refs/remotes/scratch/seen
 	EOF
 
 	git clone .git/ setbranches &&
@@ -930,7 +1016,7 @@ test_expect_success 'remote set-branches' '
 		git config --get-all remote.scratch.fetch >config-result &&
 		sort <config-result >../actual.replace &&
 
-		git remote set-branches --add scratch pu t/topic &&
+		git remote set-branches --add scratch seen t/topic &&
 		git config --get-all remote.scratch.fetch >config-result &&
 		sort <config-result >../actual.add-two &&
 
@@ -942,7 +1028,7 @@ test_expect_success 'remote set-branches' '
 		git config --get-all remote.scratch.fetch >config-result &&
 		sort <config-result >../actual.setup-ffonly &&
 
-		git remote set-branches --add scratch pu &&
+		git remote set-branches --add scratch seen &&
 		git config --get-all remote.scratch.fetch >config-result &&
 		sort <config-result >../actual.respect-ffonly
 	) &&
diff --git a/third_party/git/t/t5509-fetch-push-namespaces.sh b/third_party/git/t/t5509-fetch-push-namespaces.sh
index 75cbfcc392..a67f792adf 100755
--- a/third_party/git/t/t5509-fetch-push-namespaces.sh
+++ b/third_party/git/t/t5509-fetch-push-namespaces.sh
@@ -20,7 +20,7 @@ test_expect_success setup '
 	) &&
 	commit0=$(cd original && git rev-parse HEAD^) &&
 	commit1=$(cd original && git rev-parse HEAD) &&
-	git init pushee &&
+	git init --bare pushee &&
 	git init puller
 '
 
@@ -152,4 +152,15 @@ test_expect_success 'clone chooses correct HEAD (v2)' '
 	test_cmp expect actual
 '
 
+test_expect_success 'denyCurrentBranch and unborn branch with ref namespace' '
+	(
+		cd original &&
+		git init unborn &&
+		git remote add unborn-namespaced "ext::git --namespace=namespace %s unborn" &&
+		test_must_fail git push unborn-namespaced HEAD:master &&
+		git -C unborn config receive.denyCurrentBranch updateInstead &&
+		git push unborn-namespaced HEAD:master
+	)
+'
+
 test_done
diff --git a/third_party/git/t/t5510-fetch.sh b/third_party/git/t/t5510-fetch.sh
index 139f7106f7..dbc724e4c0 100755
--- a/third_party/git/t/t5510-fetch.sh
+++ b/third_party/git/t/t5510-fetch.sh
@@ -11,7 +11,7 @@ D=$(pwd)
 
 test_bundle_object_count () {
 	git verify-pack -v "$1" >verify.out &&
-	test "$2" = $(grep '^[0-9a-f]\{40\} ' verify.out | wc -l)
+	test "$2" = $(grep "^$OID_REGEX " verify.out | wc -l)
 }
 
 convert_bundle_to_pack () {
@@ -174,6 +174,30 @@ test_expect_success 'fetch --prune --tags with refspec prunes based on refspec'
 	git rev-parse sometag
 '
 
+test_expect_success '--refmap="" ignores configured refspec' '
+	cd "$TRASH_DIRECTORY" &&
+	git clone "$D" remote-refs &&
+	git -C remote-refs rev-parse remotes/origin/master >old &&
+	git -C remote-refs update-ref refs/remotes/origin/master master~1 &&
+	git -C remote-refs rev-parse remotes/origin/master >new &&
+	git -C remote-refs fetch --refmap= origin "+refs/heads/*:refs/hidden/origin/*" &&
+	git -C remote-refs rev-parse remotes/origin/master >actual &&
+	test_cmp new actual &&
+	git -C remote-refs fetch origin &&
+	git -C remote-refs rev-parse remotes/origin/master >actual &&
+	test_cmp old actual
+'
+
+test_expect_success '--refmap="" and --prune' '
+	git -C remote-refs update-ref refs/remotes/origin/foo/otherbranch master &&
+	git -C remote-refs update-ref refs/hidden/foo/otherbranch master &&
+	git -C remote-refs fetch --prune --refmap="" origin +refs/heads/*:refs/hidden/* &&
+	git -C remote-refs rev-parse remotes/origin/foo/otherbranch &&
+	test_must_fail git -C remote-refs rev-parse refs/hidden/foo/otherbranch &&
+	git -C remote-refs fetch --prune origin &&
+	test_must_fail git -C remote-refs rev-parse remotes/origin/foo/otherbranch
+'
+
 test_expect_success 'fetch tags when there is no tags' '
 
     cd "$D" &&
@@ -189,7 +213,7 @@ test_expect_success 'fetch tags when there is no tags' '
 test_expect_success 'fetch following tags' '
 
 	cd "$D" &&
-	git tag -a -m 'annotated' anno HEAD &&
+	git tag -a -m "annotated" anno HEAD &&
 	git tag light HEAD &&
 
 	mkdir four &&
@@ -257,14 +281,19 @@ test_expect_success 'create bundle 1' '
 	cd "$D" &&
 	echo >file updated again by origin &&
 	git commit -a -m "tip" &&
-	git bundle create bundle1 master^..master
+	git bundle create --version=3 bundle1 master^..master
 '
 
 test_expect_success 'header of bundle looks right' '
-	head -n 1 "$D"/bundle1 | grep "^#" &&
-	head -n 2 "$D"/bundle1 | grep "^-[0-9a-f]\{40\} " &&
-	head -n 3 "$D"/bundle1 | grep "^[0-9a-f]\{40\} " &&
-	head -n 4 "$D"/bundle1 | grep "^$"
+	cat >expect <<-EOF &&
+	# v3 git bundle
+	@object-format=$(test_oid algo)
+	-OID updated by origin
+	OID refs/heads/master
+
+	EOF
+	sed -e "s/$OID_REGEX/OID/g" -e "5q" "$D"/bundle1 >actual &&
+	test_cmp expect actual
 '
 
 test_expect_success 'create bundle 2' '
@@ -289,7 +318,7 @@ test_expect_success 'bundle 1 has only 3 files ' '
 test_expect_success 'unbundle 2' '
 	cd "$D/bundle" &&
 	git fetch ../bundle2 master:master &&
-	test "tip" = "$(git log -1 --pretty=oneline master | cut -b42-)"
+	test "tip" = "$(git log -1 --pretty=oneline master | cut -d" " -f2)"
 '
 
 test_expect_success 'bundle does not prerequisite objects' '
@@ -306,7 +335,7 @@ test_expect_success 'bundle does not prerequisite objects' '
 test_expect_success 'bundle should be able to create a full history' '
 
 	cd "$D" &&
-	git tag -a -m '1.0' v1.0 master &&
+	git tag -a -m "1.0" v1.0 master &&
 	git bundle create bundle4 v1.0
 
 '
@@ -354,7 +383,6 @@ test_expect_success 'fetch from GIT URL with a non-applying branch.<name>.merge
 # the strange name is: a\!'b
 test_expect_success 'quoting of a strangely named repo' '
 	test_must_fail git fetch "a\\!'\''b" > result 2>&1 &&
-	cat result &&
 	grep "fatal: '\''a\\\\!'\''b'\''" result
 '
 
@@ -515,10 +543,23 @@ test_expect_success 'fetch into the current branch with --update-head-ok' '
 
 '
 
-test_expect_success 'fetch --dry-run' '
+test_expect_success 'fetch --dry-run does not touch FETCH_HEAD, but still prints what would be written' '
+	rm -f .git/FETCH_HEAD err &&
+	git fetch --dry-run . 2>err &&
+	! test -f .git/FETCH_HEAD &&
+	grep FETCH_HEAD err
+'
 
+test_expect_success '--no-write-fetch-head does not touch FETCH_HEAD, and does not print what would be written' '
+	rm -f .git/FETCH_HEAD err &&
+	git fetch --no-write-fetch-head . 2>err &&
+	! test -f .git/FETCH_HEAD &&
+	! grep FETCH_HEAD err
+'
+
+test_expect_success '--write-fetch-head gets defeated by --dry-run' '
 	rm -f .git/FETCH_HEAD &&
-	git fetch --dry-run . &&
+	git fetch --dry-run --write-fetch-head . &&
 	! test -f .git/FETCH_HEAD
 '
 
@@ -570,6 +611,35 @@ test_expect_success 'LHS of refspec follows ref disambiguation rules' '
 	)
 '
 
+test_expect_success 'fetch.writeCommitGraph' '
+	git clone three write &&
+	(
+		cd three &&
+		test_commit new
+	) &&
+	(
+		cd write &&
+		git -c fetch.writeCommitGraph fetch origin &&
+		test_path_is_file .git/objects/info/commit-graphs/commit-graph-chain
+	)
+'
+
+test_expect_success 'fetch.writeCommitGraph with submodules' '
+	git clone dups super &&
+	(
+		cd super &&
+		git submodule add "file://$TRASH_DIRECTORY/three" &&
+		git commit -m "add submodule"
+	) &&
+	git clone "super" super-clone &&
+	(
+		cd super-clone &&
+		rm -rf .git/objects/info &&
+		git -c fetch.writeCommitGraph=true fetch origin &&
+		test_path_is_file .git/objects/info/commit-graphs/commit-graph-chain
+	)
+'
+
 # configured prune tests
 
 set_config_tristate () {
@@ -744,7 +814,7 @@ test_configured_prune true  true  unset unset pruned pruned \
 	"--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*"
 
 # --prune-tags on its own does nothing, needs --prune as well, same
-# for for fetch.pruneTags without fetch.prune
+# for fetch.pruneTags without fetch.prune
 test_configured_prune unset unset unset unset kept kept     "--prune-tags"
 test_configured_prune unset unset true unset  kept kept     ""
 test_configured_prune unset unset unset true  kept kept     ""
@@ -866,7 +936,7 @@ test_expect_success 'fetching with auto-gc does not lock up' '
 		git config fetch.unpackLimit 1 &&
 		git config gc.autoPackLimit 1 &&
 		git config gc.autoDetach false &&
-		GIT_ASK_YESNO="$D/askyesno" git fetch >fetch.out 2>&1 &&
+		GIT_ASK_YESNO="$D/askyesno" git fetch --verbose >fetch.out 2>&1 &&
 		test_i18ngrep "Auto packing the repository" fetch.out &&
 		! grep "Should I try again" fetch.out
 	)
@@ -902,6 +972,29 @@ test_expect_success C_LOCALE_OUTPUT 'fetch compact output' '
 	test_cmp expect actual
 '
 
+test_expect_success '--no-show-forced-updates' '
+	mkdir forced-updates &&
+	(
+		cd forced-updates &&
+		git init &&
+		test_commit 1 &&
+		test_commit 2
+	) &&
+	git clone forced-updates forced-update-clone &&
+	git clone forced-updates no-forced-update-clone &&
+	git -C forced-updates reset --hard HEAD~1 &&
+	(
+		cd forced-update-clone &&
+		git fetch --show-forced-updates origin 2>output &&
+		test_i18ngrep "(forced update)" output
+	) &&
+	(
+		cd no-forced-update-clone &&
+		git fetch --no-show-forced-updates origin 2>output &&
+		test_i18ngrep ! "(forced update)" output
+	)
+'
+
 setup_negotiation_tip () {
 	SERVER="$1"
 	URL="$2"
@@ -978,27 +1071,7 @@ test_expect_success '--negotiation-tip limits "have" lines sent with HTTP protoc
 	check_negotiation_tip
 '
 
-test_expect_success '--no-show-forced-updates' '
-	mkdir forced-updates &&
-	(
-		cd forced-updates &&
-		git init &&
-		test_commit 1 &&
-		test_commit 2
-	) &&
-	git clone forced-updates forced-update-clone &&
-	git clone forced-updates no-forced-update-clone &&
-	git -C forced-updates reset --hard HEAD~1 &&
-	(
-		cd forced-update-clone &&
-		git fetch --show-forced-updates origin 2>output &&
-		test_i18ngrep "(forced update)" output
-	) &&
-	(
-		cd no-forced-update-clone &&
-		git fetch --no-show-forced-updates origin 2>output &&
-		! test_i18ngrep "(forced update)" output
-	)
-'
+# DO NOT add non-httpd-specific tests here, because the last part of this
+# test script is only executed when httpd is available and enabled.
 
 test_done
diff --git a/third_party/git/t/t5512-ls-remote.sh b/third_party/git/t/t5512-ls-remote.sh
index 43e1d8d4d2..e98c3a0174 100755
--- a/third_party/git/t/t5512-ls-remote.sh
+++ b/third_party/git/t/t5512-ls-remote.sh
@@ -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,62 +217,64 @@ 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= git ls-remote --symref >actual &&
+	GIT_TEST_PROTOCOL_VERSION=0 git ls-remote --symref >actual &&
 	test_cmp expect actual
 '
 
 test_expect_success 'ls-remote with filtered symref (refname)' '
-	cat >expect <<-\EOF &&
+	rev=$(git rev-parse HEAD) &&
+	cat >expect <<-EOF &&
 	ref: refs/heads/master	HEAD
-	1bd44cb9d13204b0fe1958db0082f5028a16eb3a	HEAD
+	$rev	HEAD
 	EOF
 	# Protocol v2 supports sending symrefs for refs other than HEAD, so use
 	# protocol v0 here.
-	GIT_TEST_PROTOCOL_VERSION= git ls-remote --symref . HEAD >actual &&
+	GIT_TEST_PROTOCOL_VERSION=0 git ls-remote --symref . HEAD >actual &&
 	test_cmp expect actual
 '
 
 test_expect_failure 'ls-remote with filtered symref (--heads)' '
 	git symbolic-ref refs/heads/foo refs/tags/mark &&
-	cat >expect <<-\EOF &&
+	cat >expect <<-EOF &&
 	ref: refs/tags/mark	refs/heads/foo
-	1bd44cb9d13204b0fe1958db0082f5028a16eb3a	refs/heads/foo
-	1bd44cb9d13204b0fe1958db0082f5028a16eb3a	refs/heads/master
+	$rev	refs/heads/foo
+	$rev	refs/heads/master
 	EOF
 	# Protocol v2 supports sending symrefs for refs other than HEAD, so use
 	# protocol v0 here.
-	GIT_TEST_PROTOCOL_VERSION= git ls-remote --symref --heads . >actual &&
+	GIT_TEST_PROTOCOL_VERSION=0 git ls-remote --symref --heads . >actual &&
 	test_cmp expect actual
 '
 
 test_expect_success 'ls-remote --symref omits filtered-out matches' '
-	cat >expect <<-\EOF &&
-	1bd44cb9d13204b0fe1958db0082f5028a16eb3a	refs/heads/foo
-	1bd44cb9d13204b0fe1958db0082f5028a16eb3a	refs/heads/master
+	cat >expect <<-EOF &&
+	$rev	refs/heads/foo
+	$rev	refs/heads/master
 	EOF
 	# Protocol v2 supports sending symrefs for refs other than HEAD, so use
 	# protocol v0 here.
-	GIT_TEST_PROTOCOL_VERSION= git ls-remote --symref --heads . >actual &&
+	GIT_TEST_PROTOCOL_VERSION=0 git ls-remote --symref --heads . >actual &&
 	test_cmp expect actual &&
-	GIT_TEST_PROTOCOL_VERSION= git ls-remote --symref . "refs/heads/*" >actual &&
+	GIT_TEST_PROTOCOL_VERSION=0 git ls-remote --symref . "refs/heads/*" >actual &&
 	test_cmp expect actual
 '
 
 test_lazy_prereq GIT_DAEMON '
-	git env--helper --type=bool --default=true --exit-code GIT_TEST_GIT_DAEMON
+	test_bool_env GIT_TEST_GIT_DAEMON true
 '
 
 # This test spawns a daemon, so run it only if the user would be OK with
diff --git a/third_party/git/t/t5514-fetch-multiple.sh b/third_party/git/t/t5514-fetch-multiple.sh
index 5426d4b5ab..bd202ec6f3 100755
--- a/third_party/git/t/t5514-fetch-multiple.sh
+++ b/third_party/git/t/t5514-fetch-multiple.sh
@@ -108,7 +108,7 @@ test_expect_success 'git fetch --multiple (two remotes)' '
 	 GIT_TRACE=1 git fetch --multiple one two 2>trace &&
 	 git branch -r > output &&
 	 test_cmp ../expect output &&
-	 grep "built-in: git gc" trace >gc &&
+	 grep "built-in: git maintenance" trace >gc &&
 	 test_line_count = 1 gc
 	)
 '
@@ -183,4 +183,15 @@ test_expect_success 'git fetch --all --tags' '
 	test_cmp expect test8/output
 '
 
+test_expect_success 'parallel' '
+	git remote add one ./bogus1 &&
+	git remote add two ./bogus2 &&
+
+	test_must_fail env GIT_TRACE="$PWD/trace" \
+		git fetch --jobs=2 --multiple one two 2>err &&
+	grep "preparing to run up to 2 tasks" trace &&
+	test_i18ngrep "could not fetch .one.*128" err &&
+	test_i18ngrep "could not fetch .two.*128" err
+'
+
 test_done
diff --git a/third_party/git/t/t5515-fetch-merge-logic.sh b/third_party/git/t/t5515-fetch-merge-logic.sh
index e55d8474ef..70a9d2d8ab 100755
--- a/third_party/git/t/t5515-fetch-merge-logic.sh
+++ b/third_party/git/t/t5515-fetch-merge-logic.sh
@@ -8,18 +8,60 @@ test_description='Merge logic in fetch'
 
 # NEEDSWORK: If the overspecification of the expected result is reduced, we
 # might be able to run this test in all protocol versions.
-GIT_TEST_PROTOCOL_VERSION=
+GIT_TEST_PROTOCOL_VERSION=0
+export GIT_TEST_PROTOCOL_VERSION
 
 . ./test-lib.sh
 
-LF='
-'
+build_script () {
+	script="$1" &&
+	for i in one three_file master topic_2 one_tree three two two2 three2
+	do
+		echo "s/$(test_oid --hash=sha1 "$i")/$(test_oid "$i")/g" >>"$script"
+	done
+}
+
+convert_expected () {
+	file="$1" &&
+	script="$2" &&
+	sed -f "$script" "$file" >"$file.tmp" &&
+	mv "$file.tmp" "$file"
+}
 
 test_expect_success setup '
 	GIT_AUTHOR_DATE="2006-06-26 00:00:00 +0000" &&
 	GIT_COMMITTER_DATE="2006-06-26 00:00:00 +0000" &&
 	export GIT_AUTHOR_DATE GIT_COMMITTER_DATE &&
 
+	test_oid_cache <<-EOF &&
+	one sha1:8e32a6d901327a23ef831511badce7bf3bf46689
+	one sha256:8739546433ab1ac72ee93088dce611210effee072b2b586ceac6dde43ebec9ce
+
+	three_file sha1:0e3b14047d3ee365f4f2a1b673db059c3972589c
+	three_file sha256:bc4447d50c07497a8bfe6eef817f2364ecca9d471452e43b52756cc1a908bd32
+
+	master sha1:6c9dec2b923228c9ff994c6cfe4ae16c12408dc5
+	master sha256:8521c3072461fcfe8f32d67f95cc6e6b832a2db2fa29769ffc788bce85ebcd75
+
+	one_tree sha1:22feea448b023a2d864ef94b013735af34d238ba
+	one_tree sha256:6e4743f4ef2356b881dda5e91f5c7cdffe870faf350bf7b312f80a20935f5d83
+
+	three sha1:c61a82b60967180544e3c19f819ddbd0c9f89899
+	three sha256:0cc6d1eda617ded715170786e31ba4e2d0185404ec5a3508dd0d73b324860c6a
+
+	two sha1:525b7fb068d59950d185a8779dc957c77eed73ba
+	two sha256:3b21de3440cd38c2a9e9b464adb923f7054949ed4c918e1a0ac4c95cd52774db
+
+	topic_2 sha1:754b754407bf032e9a2f9d5a9ad05ca79a6b228f
+	topic_2 sha256:6c7abaea8a6d8ef4d89877e68462758dc6774690fbbbb0e6d7dd57415c9abde0
+
+	two2 sha1:6134ee8f857693b96ff1cc98d3e2fd62b199e5a8
+	two2 sha256:87a2d3ee29c83a3dc7afd41c0606b11f67603120b910a7be7840accdc18344d4
+
+	three2 sha1:0567da4d5edd2ff4bb292a465ba9e64dcad9536b
+	three2 sha256:cceb3e8eca364fa9a0a39a1efbebecacc664af86cbbd8070571f5faeb5f0e8c3
+	EOF
+
 	echo >file original &&
 	git add file &&
 	git commit -a -m One &&
@@ -89,7 +131,8 @@ test_expect_success setup '
 		git config branch.br-$remote-octopus.remote $remote &&
 		git config branch.br-$remote-octopus.merge refs/heads/one &&
 		git config --add branch.br-$remote-octopus.merge two
-	done
+	done &&
+	build_script sed_script
 '
 
 # Merge logic depends on branch properties and Pull: or .fetch lines
@@ -140,6 +183,10 @@ do
 	actual_r="$pfx-refs.$test"
 
 	test_expect_success "$cmd" '
+		cp "$expect_f" expect_f &&
+		convert_expected expect_f sed_script &&
+		cp "$expect_r" expect_r &&
+		convert_expected expect_r sed_script &&
 		{
 			echo "# $cmd"
 			set x $cmd; shift
@@ -155,18 +202,18 @@ do
 			cat .git/FETCH_HEAD
 		} >"$actual_f" &&
 		git show-ref >"$actual_r" &&
-		if test -f "$expect_f"
+		if test -f "expect_f"
 		then
-			test_cmp "$expect_f" "$actual_f" &&
+			test_cmp "expect_f" "$actual_f" &&
 			rm -f "$actual_f"
 		else
 			# this is to help developing new tests.
 			cp "$actual_f" "$expect_f"
 			false
 		fi &&
-		if test -f "$expect_r"
+		if test -f "expect_r"
 		then
-			test_cmp "$expect_r" "$actual_r" &&
+			test_cmp "expect_r" "$actual_r" &&
 			rm -f "$actual_r"
 		else
 			# this is to help developing new tests.
diff --git a/third_party/git/t/t5516-fetch-push.sh b/third_party/git/t/t5516-fetch-push.sh
index c81ca360ac..d11382f769 100755
--- a/third_party/git/t/t5516-fetch-push.sh
+++ b/third_party/git/t/t5516-fetch-push.sh
@@ -747,42 +747,42 @@ test_expect_success 'deletion of a non-existent ref alone does trigger post-rece
 '
 
 test_expect_success 'mixed ref updates, deletes, invalid deletes trigger hooks with correct input' '
-	mk_test_with_hooks testrepo heads/master heads/next heads/pu &&
+	mk_test_with_hooks testrepo heads/master heads/next heads/seen &&
 	orgmaster=$(cd testrepo && git show-ref -s --verify refs/heads/master) &&
 	newmaster=$(git show-ref -s --verify refs/heads/master) &&
 	orgnext=$(cd testrepo && git show-ref -s --verify refs/heads/next) &&
 	newnext=$ZERO_OID &&
-	orgpu=$(cd testrepo && git show-ref -s --verify refs/heads/pu) &&
-	newpu=$(git show-ref -s --verify refs/heads/master) &&
+	orgseen=$(cd testrepo && git show-ref -s --verify refs/heads/seen) &&
+	newseen=$(git show-ref -s --verify refs/heads/master) &&
 	git push testrepo refs/heads/master:refs/heads/master \
-	    refs/heads/master:refs/heads/pu :refs/heads/next \
+	    refs/heads/master:refs/heads/seen :refs/heads/next \
 	    :refs/heads/nonexistent &&
 	(
 		cd testrepo/.git &&
 		cat >pre-receive.expect <<-EOF &&
 		$orgmaster $newmaster refs/heads/master
 		$orgnext $newnext refs/heads/next
-		$orgpu $newpu refs/heads/pu
+		$orgseen $newseen refs/heads/seen
 		$ZERO_OID $ZERO_OID refs/heads/nonexistent
 		EOF
 
 		cat >update.expect <<-EOF &&
 		refs/heads/master $orgmaster $newmaster
 		refs/heads/next $orgnext $newnext
-		refs/heads/pu $orgpu $newpu
+		refs/heads/seen $orgseen $newseen
 		refs/heads/nonexistent $ZERO_OID $ZERO_OID
 		EOF
 
 		cat >post-receive.expect <<-EOF &&
 		$orgmaster $newmaster refs/heads/master
 		$orgnext $newnext refs/heads/next
-		$orgpu $newpu refs/heads/pu
+		$orgseen $newseen refs/heads/seen
 		EOF
 
 		cat >post-update.expect <<-EOF &&
 		refs/heads/master
 		refs/heads/next
-		refs/heads/pu
+		refs/heads/seen
 		EOF
 
 		test_cmp pre-receive.expect pre-receive.actual &&
@@ -1039,7 +1039,7 @@ test_force_fetch_tag "annotated tag" "-f -a -m'tag message'"
 test_expect_success 'push --porcelain' '
 	mk_empty testrepo &&
 	echo >.git/foo  "To testrepo" &&
-	echo >>.git/foo "*	refs/heads/master:refs/remotes/origin/master	[new branch]"  &&
+	echo >>.git/foo "*	refs/heads/master:refs/remotes/origin/master	[new reference]"  &&
 	echo >>.git/foo "Done" &&
 	git push >.git/bar --porcelain  testrepo refs/heads/master:refs/remotes/origin/master &&
 	(
@@ -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
@@ -1151,7 +1152,7 @@ test_expect_success 'fetch exact SHA1' '
 		# unadvertised objects, so restrict this test to v0.
 
 		# fetching the hidden object should fail by default
-		test_must_fail env GIT_TEST_PROTOCOL_VERSION= \
+		test_must_fail env GIT_TEST_PROTOCOL_VERSION=0 \
 			git fetch -v ../testrepo $the_commit:refs/heads/copy 2>err &&
 		test_i18ngrep "Server does not allow request for unadvertised object" err &&
 		test_must_fail git rev-parse --verify refs/heads/copy &&
@@ -1210,7 +1211,7 @@ do
 			cd shallow &&
 			# Some protocol versions (e.g. 2) support fetching
 			# unadvertised objects, so restrict this test to v0.
-			test_must_fail env GIT_TEST_PROTOCOL_VERSION= \
+			test_must_fail env GIT_TEST_PROTOCOL_VERSION=0 \
 				git fetch --depth=1 ../testrepo/.git $SHA1 &&
 			git --git-dir=../testrepo/.git config uploadpack.allowreachablesha1inwant true &&
 			git fetch --depth=1 ../testrepo/.git $SHA1 &&
@@ -1241,9 +1242,9 @@ do
 			cd shallow &&
 			# Some protocol versions (e.g. 2) support fetching
 			# unadvertised objects, so restrict this test to v0.
-			test_must_fail env GIT_TEST_PROTOCOL_VERSION= \
+			test_must_fail env GIT_TEST_PROTOCOL_VERSION=0 \
 				git fetch ../testrepo/.git $SHA1_3 &&
-			test_must_fail env GIT_TEST_PROTOCOL_VERSION= \
+			test_must_fail env GIT_TEST_PROTOCOL_VERSION=0 \
 				git fetch ../testrepo/.git $SHA1_1 &&
 			git --git-dir=../testrepo/.git config uploadpack.allowreachablesha1inwant true &&
 			git fetch ../testrepo/.git $SHA1_1 &&
@@ -1251,7 +1252,7 @@ do
 			test_must_fail git cat-file commit $SHA1_2 &&
 			git fetch ../testrepo/.git $SHA1_2 &&
 			git cat-file commit $SHA1_2 &&
-			test_must_fail env GIT_TEST_PROTOCOL_VERSION= \
+			test_must_fail env GIT_TEST_PROTOCOL_VERSION=0 \
 				git fetch ../testrepo/.git $SHA1_3 2>err &&
 			test_i18ngrep "remote error:.*not our ref.*$SHA1_3\$" err
 		)
@@ -1291,7 +1292,7 @@ test_expect_success 'peeled advertisements are not considered ref tips' '
 	git -C testrepo commit --allow-empty -m two &&
 	git -C testrepo tag -m foo mytag HEAD^ &&
 	oid=$(git -C testrepo rev-parse mytag^{commit}) &&
-	test_must_fail env GIT_TEST_PROTOCOL_VERSION= \
+	test_must_fail env GIT_TEST_PROTOCOL_VERSION=0 \
 		git fetch testrepo $oid 2>err &&
 	test_i18ngrep "Server does not allow request for unadvertised object" err
 '
@@ -1712,4 +1713,15 @@ test_expect_success 'updateInstead with push-to-checkout hook' '
 	)
 '
 
+test_expect_success 'denyCurrentBranch and worktrees' '
+	git worktree add new-wt &&
+	git clone . cloned &&
+	test_commit -C cloned first &&
+	test_config receive.denyCurrentBranch refuse &&
+	test_must_fail git -C cloned push origin HEAD:new-wt &&
+	test_config receive.denyCurrentBranch updateInstead &&
+	git -C cloned push origin HEAD:new-wt &&
+	test_must_fail git -C cloned push --delete origin new-wt
+'
+
 test_done
diff --git a/third_party/git/t/t5517-push-mirror.sh b/third_party/git/t/t5517-push-mirror.sh
index c05a661400..e4edd56404 100755
--- a/third_party/git/t/t5517-push-mirror.sh
+++ b/third_party/git/t/t5517-push-mirror.sh
@@ -265,4 +265,14 @@ test_expect_success 'remote.foo.mirror=no has no effect' '
 
 '
 
+test_expect_success 'push to mirrored repository with refspec fails' '
+	mk_repo_pair &&
+	(
+		cd master &&
+		echo one >foo && git add foo && git commit -m one &&
+		git config --add remote.up.mirror true &&
+		test_must_fail git push up master
+	)
+'
+
 test_done
diff --git a/third_party/git/t/t5520-pull.sh b/third_party/git/t/t5520-pull.sh
index cf4cc32fd0..9fae07cdfa 100755
--- a/third_party/git/t/t5520-pull.sh
+++ b/third_party/git/t/t5520-pull.sh
@@ -5,18 +5,22 @@ test_description='pulling into void'
 . ./test-lib.sh
 
 modify () {
-	sed -e "$1" <"$2" >"$2.x" &&
+	sed -e "$1" "$2" >"$2.x" &&
 	mv "$2.x" "$2"
 }
 
 test_pull_autostash () {
+	expect_parent_num="$1" &&
+	shift &&
 	git reset --hard before-rebase &&
 	echo dirty >new_file &&
 	git add new_file &&
 	git pull "$@" . copy &&
-	test_cmp_rev HEAD^ copy &&
-	test "$(cat new_file)" = dirty &&
-	test "$(cat file)" = "modified again"
+	test_cmp_rev HEAD^"$expect_parent_num" copy &&
+	echo dirty >expect &&
+	test_cmp expect new_file &&
+	echo "modified again" >expect &&
+	test_cmp expect file
 }
 
 test_pull_autostash_fail () {
@@ -24,7 +28,7 @@ test_pull_autostash_fail () {
 	echo dirty >new_file &&
 	git add new_file &&
 	test_must_fail git pull "$@" . copy 2>err &&
-	test_i18ngrep "uncommitted changes." err
+	test_i18ngrep -E "uncommitted changes.|overwritten by merge:" err
 }
 
 test_expect_success setup '
@@ -39,8 +43,8 @@ test_expect_success 'pulling into void' '
 		cd cloned &&
 		git pull ..
 	) &&
-	test -f file &&
-	test -f cloned/file &&
+	test_path_is_file file &&
+	test_path_is_file cloned/file &&
 	test_cmp file cloned/file
 '
 
@@ -50,8 +54,8 @@ test_expect_success 'pulling into void using master:master' '
 		cd cloned-uho &&
 		git pull .. master:master
 	) &&
-	test -f file &&
-	test -f cloned-uho/file &&
+	test_path_is_file file &&
+	test_path_is_file cloned-uho/file &&
 	test_cmp file cloned-uho/file
 '
 
@@ -99,7 +103,7 @@ test_expect_success 'pulling into void must not create an octopus' '
 	(
 		cd cloned-octopus &&
 		test_must_fail git pull .. master master &&
-		! test -f file
+		test_path_is_missing file
 	)
 '
 
@@ -110,9 +114,11 @@ test_expect_success 'test . as a remote' '
 	echo updated >file &&
 	git commit -a -m updated &&
 	git checkout copy &&
-	test "$(cat file)" = file &&
+	echo file >expect &&
+	test_cmp expect file &&
 	git pull &&
-	test "$(cat file)" = updated &&
+	echo updated >expect &&
+	test_cmp expect file &&
 	git reflog -1 >reflog.actual &&
 	sed "s/^[0-9a-f][0-9a-f]*/OBJID/" reflog.actual >reflog.fuzzy &&
 	echo "OBJID HEAD@{0}: pull: Fast-forward" >reflog.expected &&
@@ -125,9 +131,11 @@ test_expect_success 'the default remote . should not break explicit pull' '
 	git commit -a -m modified &&
 	git checkout copy &&
 	git reset --hard HEAD^ &&
-	test "$(cat file)" = file &&
+	echo file >expect &&
+	test_cmp expect file &&
 	git pull . second &&
-	test "$(cat file)" = modified &&
+	echo modified >expect &&
+	test_cmp expect file &&
 	git reflog -1 >reflog.actual &&
 	sed "s/^[0-9a-f][0-9a-f]*/OBJID/" reflog.actual >reflog.fuzzy &&
 	echo "OBJID HEAD@{0}: pull . second: Fast-forward" >reflog.expected &&
@@ -137,10 +145,11 @@ test_expect_success 'the default remote . should not break explicit pull' '
 test_expect_success 'fail if wildcard spec does not match any refs' '
 	git checkout -b test copy^ &&
 	test_when_finished "git checkout -f copy && git branch -D test" &&
-	test "$(cat file)" = file &&
+	echo file >expect &&
+	test_cmp expect file &&
 	test_must_fail git pull . "refs/nonexisting1/*:refs/nonexisting2/*" 2>err &&
 	test_i18ngrep "no candidates for merging" err &&
-	test "$(cat file)" = file
+	test_cmp expect file
 '
 
 test_expect_success 'fail if no branches specified with non-default remote' '
@@ -148,11 +157,12 @@ test_expect_success 'fail if no branches specified with non-default remote' '
 	test_when_finished "git remote remove test_remote" &&
 	git checkout -b test copy^ &&
 	test_when_finished "git checkout -f copy && git branch -D test" &&
-	test "$(cat file)" = file &&
+	echo file >expect &&
+	test_cmp expect file &&
 	test_config branch.test.remote origin &&
 	test_must_fail git pull test_remote 2>err &&
 	test_i18ngrep "specify a branch on the command line" err &&
-	test "$(cat file)" = file
+	test_cmp expect file
 '
 
 test_expect_success 'fail if not on a branch' '
@@ -160,10 +170,11 @@ test_expect_success 'fail if not on a branch' '
 	test_when_finished "git remote remove origin" &&
 	git checkout HEAD^ &&
 	test_when_finished "git checkout -f copy" &&
-	test "$(cat file)" = file &&
+	echo file >expect &&
+	test_cmp expect file &&
 	test_must_fail git pull 2>err &&
 	test_i18ngrep "not currently on a branch" err &&
-	test "$(cat file)" = file
+	test_cmp expect file
 '
 
 test_expect_success 'fail if no configuration for current branch' '
@@ -172,10 +183,11 @@ test_expect_success 'fail if no configuration for current branch' '
 	git checkout -b test copy^ &&
 	test_when_finished "git checkout -f copy && git branch -D test" &&
 	test_config branch.test.remote test_remote &&
-	test "$(cat file)" = file &&
+	echo file >expect &&
+	test_cmp expect file &&
 	test_must_fail git pull 2>err &&
 	test_i18ngrep "no tracking information" err &&
-	test "$(cat file)" = file
+	test_cmp expect file
 '
 
 test_expect_success 'pull --all: fail if no configuration for current branch' '
@@ -184,10 +196,11 @@ test_expect_success 'pull --all: fail if no configuration for current branch' '
 	git checkout -b test copy^ &&
 	test_when_finished "git checkout -f copy && git branch -D test" &&
 	test_config branch.test.remote test_remote &&
-	test "$(cat file)" = file &&
+	echo file >expect &&
+	test_cmp expect file &&
 	test_must_fail git pull --all 2>err &&
 	test_i18ngrep "There is no tracking information" err &&
-	test "$(cat file)" = file
+	test_cmp expect file
 '
 
 test_expect_success 'fail if upstream branch does not exist' '
@@ -195,26 +208,31 @@ test_expect_success 'fail if upstream branch does not exist' '
 	test_when_finished "git checkout -f copy && git branch -D test" &&
 	test_config branch.test.remote . &&
 	test_config branch.test.merge refs/heads/nonexisting &&
-	test "$(cat file)" = file &&
+	echo file >expect &&
+	test_cmp expect file &&
 	test_must_fail git pull 2>err &&
 	test_i18ngrep "no such ref was fetched" err &&
-	test "$(cat file)" = file
+	test_cmp expect file
 '
 
 test_expect_success 'fail if the index has unresolved entries' '
 	git checkout -b third second^ &&
 	test_when_finished "git checkout -f copy && git branch -D third" &&
-	test "$(cat file)" = file &&
+	echo file >expect &&
+	test_cmp expect file &&
 	test_commit modified2 file &&
-	test -z "$(git ls-files -u)" &&
+	git ls-files -u >unmerged &&
+	test_must_be_empty unmerged &&
 	test_must_fail git pull . second &&
-	test -n "$(git ls-files -u)" &&
+	git ls-files -u >unmerged &&
+	test_file_not_empty unmerged &&
 	cp file expected &&
 	test_must_fail git pull . second 2>err &&
 	test_i18ngrep "Pulling is not possible because you have unmerged files." err &&
 	test_cmp expected file &&
 	git add file &&
-	test -z "$(git ls-files -u)" &&
+	git ls-files -u >unmerged &&
+	test_must_be_empty unmerged &&
 	test_must_fail git pull . second 2>err &&
 	test_i18ngrep "You have not concluded your merge" err &&
 	test_cmp expected file
@@ -223,47 +241,66 @@ test_expect_success 'fail if the index has unresolved entries' '
 test_expect_success 'fast-forwards working tree if branch head is updated' '
 	git checkout -b third second^ &&
 	test_when_finished "git checkout -f copy && git branch -D third" &&
-	test "$(cat file)" = file &&
+	echo file >expect &&
+	test_cmp expect file &&
 	git pull . second:third 2>err &&
 	test_i18ngrep "fetch updated the current branch head" err &&
-	test "$(cat file)" = modified &&
-	test "$(git rev-parse third)" = "$(git rev-parse second)"
+	echo modified >expect &&
+	test_cmp expect file &&
+	test_cmp_rev third second
 '
 
 test_expect_success 'fast-forward fails with conflicting work tree' '
 	git checkout -b third second^ &&
 	test_when_finished "git checkout -f copy && git branch -D third" &&
-	test "$(cat file)" = file &&
+	echo file >expect &&
+	test_cmp expect file &&
 	echo conflict >file &&
 	test_must_fail git pull . second:third 2>err &&
 	test_i18ngrep "Cannot fast-forward your working tree" err &&
-	test "$(cat file)" = conflict &&
-	test "$(git rev-parse third)" = "$(git rev-parse second)"
+	echo conflict >expect &&
+	test_cmp expect file &&
+	test_cmp_rev third second
 '
 
 test_expect_success '--rebase' '
 	git branch to-rebase &&
-	echo modified again > file &&
+	echo modified again >file &&
 	git commit -m file file &&
 	git checkout to-rebase &&
-	echo new > file2 &&
+	echo new >file2 &&
 	git add file2 &&
 	git commit -m "new file" &&
 	git tag before-rebase &&
 	git pull --rebase . copy &&
-	test "$(git rev-parse HEAD^)" = "$(git rev-parse copy)" &&
-	test new = "$(git show HEAD:file2)"
+	test_cmp_rev HEAD^ copy &&
+	echo new >expect &&
+	git show HEAD:file2 >actual &&
+	test_cmp expect actual
 '
 
-test_expect_success '--rebase fast forward' '
+test_expect_success '--rebase (merge) fast forward' '
 	git reset --hard before-rebase &&
 	git checkout -b ff &&
 	echo another modification >file &&
 	git commit -m third file &&
 
 	git checkout to-rebase &&
-	git pull --rebase . ff &&
-	test "$(git rev-parse HEAD)" = "$(git rev-parse ff)" &&
+	git -c rebase.backend=merge pull --rebase . ff &&
+	test_cmp_rev HEAD ff &&
+
+	# The above only validates the result.  Did we actually bypass rebase?
+	git reflog -1 >reflog.actual &&
+	sed "s/^[0-9a-f][0-9a-f]*/OBJID/" reflog.actual >reflog.fuzzy &&
+	echo "OBJID HEAD@{0}: pull --rebase . ff: Fast-forward" >reflog.expected &&
+	test_cmp reflog.expected reflog.fuzzy
+'
+
+test_expect_success '--rebase (am) fast forward' '
+	git reset --hard before-rebase &&
+
+	git -c rebase.backend=apply pull --rebase . ff &&
+	test_cmp_rev HEAD ff &&
 
 	# The above only validates the result.  Did we actually bypass rebase?
 	git reflog -1 >reflog.actual &&
@@ -287,7 +324,7 @@ test_expect_success '--rebase --autostash fast forward' '
 	git checkout behind &&
 	echo dirty >file &&
 	git pull --rebase --autostash . to-rebase-ff &&
-	test "$(git rev-parse HEAD)" = "$(git rev-parse to-rebase-ff)"
+	test_cmp_rev HEAD to-rebase-ff
 '
 
 test_expect_success '--rebase with conflicts shows advice' '
@@ -305,7 +342,7 @@ test_expect_success '--rebase with conflicts shows advice' '
 	test_tick &&
 	git commit -m "Create conflict" seq.txt &&
 	test_must_fail git pull --rebase . seq 2>err >out &&
-	test_i18ngrep "Resolve all conflicts manually" out
+	test_i18ngrep "Resolve all conflicts manually" err
 '
 
 test_expect_success 'failed --rebase shows advice' '
@@ -319,35 +356,37 @@ test_expect_success 'failed --rebase shows advice' '
 	git checkout -f -b fails-to-rebase HEAD^ &&
 	test_commit v2-without-cr file "2" file2-lf &&
 	test_must_fail git pull --rebase . diverging 2>err >out &&
-	test_i18ngrep "Resolve all conflicts manually" out
+	test_i18ngrep "Resolve all conflicts manually" err
 '
 
 test_expect_success '--rebase fails with multiple branches' '
 	git reset --hard before-rebase &&
 	test_must_fail git pull --rebase . copy master 2>err &&
-	test "$(git rev-parse HEAD)" = "$(git rev-parse before-rebase)" &&
+	test_cmp_rev HEAD before-rebase &&
 	test_i18ngrep "Cannot rebase onto multiple branches" err &&
-	test modified = "$(git show HEAD:file)"
+	echo modified >expect &&
+	git show HEAD:file >actual &&
+	test_cmp expect actual
 '
 
 test_expect_success 'pull --rebase succeeds with dirty working directory and rebase.autostash set' '
 	test_config rebase.autostash true &&
-	test_pull_autostash --rebase
+	test_pull_autostash 1 --rebase
 '
 
 test_expect_success 'pull --rebase --autostash & rebase.autostash=true' '
 	test_config rebase.autostash true &&
-	test_pull_autostash --rebase --autostash
+	test_pull_autostash 1 --rebase --autostash
 '
 
 test_expect_success 'pull --rebase --autostash & rebase.autostash=false' '
 	test_config rebase.autostash false &&
-	test_pull_autostash --rebase --autostash
+	test_pull_autostash 1 --rebase --autostash
 '
 
 test_expect_success 'pull --rebase --autostash & rebase.autostash unset' '
 	test_unconfig rebase.autostash &&
-	test_pull_autostash --rebase --autostash
+	test_pull_autostash 1 --rebase --autostash
 '
 
 test_expect_success 'pull --rebase --no-autostash & rebase.autostash=true' '
@@ -365,25 +404,54 @@ test_expect_success 'pull --rebase --no-autostash & rebase.autostash unset' '
 	test_pull_autostash_fail --rebase --no-autostash
 '
 
-for i in --autostash --no-autostash
-do
-	test_expect_success "pull $i (without --rebase) is illegal" '
-		test_must_fail git pull $i . copy 2>err &&
-		test_i18ngrep "only valid with --rebase" err
-	'
-done
+test_expect_success 'pull succeeds with dirty working directory and merge.autostash set' '
+	test_config merge.autostash true &&
+	test_pull_autostash 2
+'
+
+test_expect_success 'pull --autostash & merge.autostash=true' '
+	test_config merge.autostash true &&
+	test_pull_autostash 2 --autostash
+'
+
+test_expect_success 'pull --autostash & merge.autostash=false' '
+	test_config merge.autostash false &&
+	test_pull_autostash 2 --autostash
+'
+
+test_expect_success 'pull --autostash & merge.autostash unset' '
+	test_unconfig merge.autostash &&
+	test_pull_autostash 2 --autostash
+'
+
+test_expect_success 'pull --no-autostash & merge.autostash=true' '
+	test_config merge.autostash true &&
+	test_pull_autostash_fail --no-autostash
+'
+
+test_expect_success 'pull --no-autostash & merge.autostash=false' '
+	test_config merge.autostash false &&
+	test_pull_autostash_fail --no-autostash
+'
+
+test_expect_success 'pull --no-autostash & merge.autostash unset' '
+	test_unconfig merge.autostash &&
+	test_pull_autostash_fail --no-autostash
+'
 
 test_expect_success 'pull.rebase' '
 	git reset --hard before-rebase &&
 	test_config pull.rebase true &&
 	git pull . copy &&
-	test "$(git rev-parse HEAD^)" = "$(git rev-parse copy)" &&
-	test new = "$(git show HEAD:file2)"
+	test_cmp_rev HEAD^ copy &&
+	echo new >expect &&
+	git show HEAD:file2 >actual &&
+	test_cmp expect actual
 '
 
 test_expect_success 'pull --autostash & pull.rebase=true' '
 	test_config pull.rebase true &&
-	test_pull_autostash --autostash
+	test_pull_autostash 1 --autostash
 '
 
 test_expect_success 'pull --no-autostash & pull.rebase=true' '
@@ -395,8 +463,10 @@ test_expect_success 'branch.to-rebase.rebase' '
 	git reset --hard before-rebase &&
 	test_config branch.to-rebase.rebase true &&
 	git pull . copy &&
-	test "$(git rev-parse HEAD^)" = "$(git rev-parse copy)" &&
-	test new = "$(git show HEAD:file2)"
+	test_cmp_rev HEAD^ copy &&
+	echo new >expect &&
+	git show HEAD:file2 >actual &&
+	test_cmp expect actual
 '
 
 test_expect_success 'branch.to-rebase.rebase should override pull.rebase' '
@@ -404,23 +474,29 @@ test_expect_success 'branch.to-rebase.rebase should override pull.rebase' '
 	test_config pull.rebase true &&
 	test_config branch.to-rebase.rebase false &&
 	git pull . copy &&
-	test "$(git rev-parse HEAD^)" != "$(git rev-parse copy)" &&
-	test new = "$(git show HEAD:file2)"
+	test_cmp_rev ! HEAD^ copy &&
+	echo new >expect &&
+	git show HEAD:file2 >actual &&
+	test_cmp expect actual
 '
 
-test_expect_success "pull --rebase warns on --verify-signatures" '
+test_expect_success 'pull --rebase warns on --verify-signatures' '
 	git reset --hard before-rebase &&
 	git pull --rebase --verify-signatures . copy 2>err &&
-	test "$(git rev-parse HEAD^)" = "$(git rev-parse copy)" &&
-	test new = "$(git show HEAD:file2)" &&
+	test_cmp_rev HEAD^ copy &&
+	echo new >expect &&
+	git show HEAD:file2 >actual &&
+	test_cmp expect actual &&
 	test_i18ngrep "ignoring --verify-signatures for rebase" err
 '
 
-test_expect_success "pull --rebase does not warn on --no-verify-signatures" '
+test_expect_success 'pull --rebase does not warn on --no-verify-signatures' '
 	git reset --hard before-rebase &&
 	git pull --rebase --no-verify-signatures . copy 2>err &&
-	test "$(git rev-parse HEAD^)" = "$(git rev-parse copy)" &&
-	test new = "$(git show HEAD:file2)" &&
+	test_cmp_rev HEAD^ copy &&
+	echo new >expect &&
+	git show HEAD:file2 >actual &&
+	test_cmp expect actual &&
 	test_i18ngrep ! "verify-signatures" err
 '
 
@@ -440,25 +516,31 @@ test_expect_success 'pull.rebase=false create a new merge commit' '
 	git reset --hard before-preserve-rebase &&
 	test_config pull.rebase false &&
 	git pull . copy &&
-	test "$(git rev-parse HEAD^1)" = "$(git rev-parse before-preserve-rebase)" &&
-	test "$(git rev-parse HEAD^2)" = "$(git rev-parse copy)" &&
-	test file3 = "$(git show HEAD:file3.t)"
+	test_cmp_rev HEAD^1 before-preserve-rebase &&
+	test_cmp_rev HEAD^2 copy &&
+	echo file3 >expect &&
+	git show HEAD:file3.t >actual &&
+	test_cmp expect actual
 '
 
 test_expect_success 'pull.rebase=true flattens keep-merge' '
 	git reset --hard before-preserve-rebase &&
 	test_config pull.rebase true &&
 	git pull . copy &&
-	test "$(git rev-parse HEAD^^)" = "$(git rev-parse copy)" &&
-	test file3 = "$(git show HEAD:file3.t)"
+	test_cmp_rev HEAD^^ copy &&
+	echo file3 >expect &&
+	git show HEAD:file3.t >actual &&
+	test_cmp expect actual
 '
 
 test_expect_success 'pull.rebase=1 is treated as true and flattens keep-merge' '
 	git reset --hard before-preserve-rebase &&
 	test_config pull.rebase 1 &&
 	git pull . copy &&
-	test "$(git rev-parse HEAD^^)" = "$(git rev-parse copy)" &&
-	test file3 = "$(git show HEAD:file3.t)"
+	test_cmp_rev HEAD^^ copy &&
+	echo file3 >expect &&
+	git show HEAD:file3.t >actual &&
+	test_cmp expect actual
 '
 
 test_expect_success REBASE_P \
@@ -466,8 +548,8 @@ test_expect_success REBASE_P \
 	git reset --hard before-preserve-rebase &&
 	test_config pull.rebase preserve &&
 	git pull . copy &&
-	test "$(git rev-parse HEAD^^)" = "$(git rev-parse copy)" &&
-	test "$(git rev-parse HEAD^2)" = "$(git rev-parse keep-merge)"
+	test_cmp_rev HEAD^^ copy &&
+	test_cmp_rev HEAD^2 keep-merge
 '
 
 test_expect_success 'pull.rebase=interactive' '
@@ -478,7 +560,8 @@ test_expect_success 'pull.rebase=interactive' '
 	test_set_editor "$TRASH_DIRECTORY/fake-editor" &&
 	test_when_finished "test_might_fail git rebase --abort" &&
 	test_must_fail git pull --rebase=interactive . copy &&
-	test "I was here" = "$(cat fake.out)"
+	echo "I was here" >expect &&
+	test_cmp expect fake.out
 '
 
 test_expect_success 'pull --rebase=i' '
@@ -489,30 +572,35 @@ test_expect_success 'pull --rebase=i' '
 	test_set_editor "$TRASH_DIRECTORY/fake-editor" &&
 	test_when_finished "test_might_fail git rebase --abort" &&
 	test_must_fail git pull --rebase=i . copy &&
-	test "I was here, too" = "$(cat fake.out)"
+	echo "I was here, too" >expect &&
+	test_cmp expect fake.out
 '
 
 test_expect_success 'pull.rebase=invalid fails' '
 	git reset --hard before-preserve-rebase &&
 	test_config pull.rebase invalid &&
-	! git pull . copy
+	test_must_fail git pull . copy
 '
 
 test_expect_success '--rebase=false create a new merge commit' '
 	git reset --hard before-preserve-rebase &&
 	test_config pull.rebase true &&
 	git pull --rebase=false . copy &&
-	test "$(git rev-parse HEAD^1)" = "$(git rev-parse before-preserve-rebase)" &&
-	test "$(git rev-parse HEAD^2)" = "$(git rev-parse copy)" &&
-	test file3 = "$(git show HEAD:file3.t)"
+	test_cmp_rev HEAD^1 before-preserve-rebase &&
+	test_cmp_rev HEAD^2 copy &&
+	echo file3 >expect &&
+	git show HEAD:file3.t >actual &&
+	test_cmp expect actual
 '
 
 test_expect_success '--rebase=true rebases and flattens keep-merge' '
 	git reset --hard before-preserve-rebase &&
 	test_config pull.rebase preserve &&
 	git pull --rebase=true . copy &&
-	test "$(git rev-parse HEAD^^)" = "$(git rev-parse copy)" &&
-	test file3 = "$(git show HEAD:file3.t)"
+	test_cmp_rev HEAD^^ copy &&
+	echo file3 >expect &&
+	git show HEAD:file3.t >actual &&
+	test_cmp expect actual
 '
 
 test_expect_success REBASE_P \
@@ -520,58 +608,62 @@ test_expect_success REBASE_P \
 	git reset --hard before-preserve-rebase &&
 	test_config pull.rebase true &&
 	git pull --rebase=preserve . copy &&
-	test "$(git rev-parse HEAD^^)" = "$(git rev-parse copy)" &&
-	test "$(git rev-parse HEAD^2)" = "$(git rev-parse keep-merge)"
+	test_cmp_rev HEAD^^ copy &&
+	test_cmp_rev HEAD^2 keep-merge
 '
 
 test_expect_success '--rebase=invalid fails' '
 	git reset --hard before-preserve-rebase &&
-	! git pull --rebase=invalid . copy
+	test_must_fail git pull --rebase=invalid . copy
 '
 
 test_expect_success '--rebase overrides pull.rebase=preserve and flattens keep-merge' '
 	git reset --hard before-preserve-rebase &&
 	test_config pull.rebase preserve &&
 	git pull --rebase . copy &&
-	test "$(git rev-parse HEAD^^)" = "$(git rev-parse copy)" &&
-	test file3 = "$(git show HEAD:file3.t)"
+	test_cmp_rev HEAD^^ copy &&
+	echo file3 >expect &&
+	git show HEAD:file3.t >actual &&
+	test_cmp expect actual
 '
 
 test_expect_success '--rebase with rebased upstream' '
-
 	git remote add -f me . &&
 	git checkout copy &&
 	git tag copy-orig &&
 	git reset --hard HEAD^ &&
-	echo conflicting modification > file &&
+	echo conflicting modification >file &&
 	git commit -m conflict file &&
 	git checkout to-rebase &&
-	echo file > file2 &&
+	echo file >file2 &&
 	git commit -m to-rebase file2 &&
 	git tag to-rebase-orig &&
 	git pull --rebase me copy &&
-	test "conflicting modification" = "$(cat file)" &&
-	test file = "$(cat file2)"
-
+	echo "conflicting modification" >expect &&
+	test_cmp expect file &&
+	echo file >expect &&
+	test_cmp expect file2
 '
 
 test_expect_success '--rebase -f with rebased upstream' '
 	test_when_finished "test_might_fail git rebase --abort" &&
 	git reset --hard to-rebase-orig &&
 	git pull --rebase -f me copy &&
-	test "conflicting modification" = "$(cat file)" &&
-	test file = "$(cat file2)"
+	echo "conflicting modification" >expect &&
+	test_cmp expect file &&
+	echo file >expect &&
+	test_cmp expect file2
 '
 
 test_expect_success '--rebase with rebased default upstream' '
-
 	git update-ref refs/remotes/me/copy copy-orig &&
 	git checkout --track -b to-rebase2 me/copy &&
 	git reset --hard to-rebase-orig &&
 	git pull --rebase &&
-	test "conflicting modification" = "$(cat file)" &&
-	test file = "$(cat file2)"
-
+	echo "conflicting modification" >expect &&
+	test_cmp expect file &&
+	echo file >expect &&
+	test_cmp expect file2
 '
 
 test_expect_success 'rebased upstream + fetch + pull --rebase' '
@@ -582,13 +674,14 @@ test_expect_success 'rebased upstream + fetch + pull --rebase' '
 	git reset --hard to-rebase-orig &&
 	git fetch &&
 	git pull --rebase &&
-	test "conflicting modification" = "$(cat file)" &&
-	test file = "$(cat file2)"
+	echo "conflicting modification" >expect &&
+	test_cmp expect file &&
+	echo file >expect &&
+	test_cmp expect file2
 
 '
 
 test_expect_success 'pull --rebase dies early with dirty working directory' '
-
 	git checkout to-rebase &&
 	git update-ref refs/remotes/me/copy copy^ &&
 	COPY="$(git rev-parse --verify me/copy)" &&
@@ -596,23 +689,23 @@ test_expect_success 'pull --rebase dies early with dirty working directory' '
 	test_config branch.to-rebase.remote me &&
 	test_config branch.to-rebase.merge refs/heads/copy &&
 	test_config branch.to-rebase.rebase true &&
-	echo dirty >> file &&
+	echo dirty >>file &&
 	git add file &&
 	test_must_fail git pull &&
-	test "$COPY" = "$(git rev-parse --verify me/copy)" &&
+	test_cmp_rev "$COPY" me/copy &&
 	git checkout HEAD -- file &&
 	git pull &&
-	test "$COPY" != "$(git rev-parse --verify me/copy)"
-
+	test_cmp_rev ! "$COPY" me/copy
 '
 
 test_expect_success 'pull --rebase works on branch yet to be born' '
 	git rev-parse master >expect &&
 	mkdir empty_repo &&
-	(cd empty_repo &&
-	 git init &&
-	 git pull --rebase .. master &&
-	 git rev-parse HEAD >../actual
+	(
+		cd empty_repo &&
+		git init &&
+		git pull --rebase .. master &&
+		git rev-parse HEAD >../actual
 	) &&
 	test_cmp expect actual
 '
@@ -624,10 +717,14 @@ test_expect_success 'pull --rebase fails on unborn branch with staged changes' '
 		cd empty_repo2 &&
 		echo staged-file >staged-file &&
 		git add staged-file &&
-		test "$(git ls-files)" = staged-file &&
+		echo staged-file >expect &&
+		git ls-files >actual &&
+		test_cmp expect actual &&
 		test_must_fail git pull --rebase .. master 2>err &&
-		test "$(git ls-files)" = staged-file &&
-		test "$(git show :staged-file)" = staged-file &&
+		git ls-files >actual &&
+		test_cmp expect actual &&
+		git show :staged-file >actual &&
+		test_cmp expect actual &&
 		test_i18ngrep "unborn branch with changes added to the index" err
 	)
 '
@@ -638,7 +735,8 @@ test_expect_success 'pull --rebase fails on corrupt HEAD' '
 	(
 		cd corrupt &&
 		test_commit one &&
-		obj=$(git rev-parse --verify HEAD | sed "s#^..#&/#") &&
+		git rev-parse --verify HEAD >head &&
+		obj=$(sed "s#^..#&/#" head) &&
 		rm -f .git/objects/$obj &&
 		test_must_fail git pull --rebase
 	)
@@ -646,66 +744,79 @@ test_expect_success 'pull --rebase fails on corrupt HEAD' '
 
 test_expect_success 'setup for detecting upstreamed changes' '
 	mkdir src &&
-	(cd src &&
-	 git init &&
-	 printf "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n" > stuff &&
-	 git add stuff &&
-	 git commit -m "Initial revision"
+	(
+		cd src &&
+		git init &&
+		printf "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n" > stuff &&
+		git add stuff &&
+		git commit -m "Initial revision"
 	) &&
 	git clone src dst &&
-	(cd src &&
-	 modify s/5/43/ stuff &&
-	 git commit -a -m "5->43" &&
-	 modify s/6/42/ stuff &&
-	 git commit -a -m "Make it bigger"
+	(
+		cd src &&
+		modify s/5/43/ stuff &&
+		git commit -a -m "5->43" &&
+		modify s/6/42/ stuff &&
+		git commit -a -m "Make it bigger"
 	) &&
-	(cd dst &&
-	 modify s/5/43/ stuff &&
-	 git commit -a -m "Independent discovery of 5->43"
+	(
+		cd dst &&
+		modify s/5/43/ stuff &&
+		git commit -a -m "Independent discovery of 5->43"
 	)
 '
 
 test_expect_success 'git pull --rebase detects upstreamed changes' '
-	(cd dst &&
-	 git pull --rebase &&
-	 test -z "$(git ls-files -u)"
+	(
+		cd dst &&
+		git pull --rebase &&
+		git ls-files -u >untracked &&
+		test_must_be_empty untracked
 	)
 '
 
 test_expect_success 'setup for avoiding reapplying old patches' '
-	(cd dst &&
-	 test_might_fail git rebase --abort &&
-	 git reset --hard origin/master
+	(
+		cd dst &&
+		test_might_fail git rebase --abort &&
+		git reset --hard origin/master
 	) &&
 	git clone --bare src src-replace.git &&
 	rm -rf src &&
 	mv src-replace.git src &&
-	(cd dst &&
-	 modify s/2/22/ stuff &&
-	 git commit -a -m "Change 2" &&
-	 modify s/3/33/ stuff &&
-	 git commit -a -m "Change 3" &&
-	 modify s/4/44/ stuff &&
-	 git commit -a -m "Change 4" &&
-	 git push &&
-
-	 modify s/44/55/ stuff &&
-	 git commit --amend -a -m "Modified Change 4"
+	(
+		cd dst &&
+		modify s/2/22/ stuff &&
+		git commit -a -m "Change 2" &&
+		modify s/3/33/ stuff &&
+		git commit -a -m "Change 3" &&
+		modify s/4/44/ stuff &&
+		git commit -a -m "Change 4" &&
+		git push &&
+
+		modify s/44/55/ stuff &&
+		git commit --amend -a -m "Modified Change 4"
 	)
 '
 
 test_expect_success 'git pull --rebase does not reapply old patches' '
-	(cd dst &&
-	 test_must_fail git pull --rebase &&
-	 test 1 = $(find .git/rebase-apply -name "000*" | wc -l)
+	(
+		cd dst &&
+		test_must_fail git pull --rebase &&
+		cat .git/rebase-merge/done .git/rebase-merge/git-rebase-todo >work &&
+		grep -v -e \# -e ^$ work >patches &&
+		test_line_count = 1 patches &&
+		rm -f work
 	)
 '
 
 test_expect_success 'git pull --rebase against local branch' '
 	git checkout -b copy2 to-rebase-orig &&
 	git pull --rebase . to-rebase &&
-	test "conflicting modification" = "$(cat file)" &&
-	test file = "$(cat file2)"
+	echo "conflicting modification" >expect &&
+	test_cmp expect file &&
+	echo file >expect &&
+	test_cmp expect file2
 '
 
 test_done
diff --git a/third_party/git/t/t5521-pull-options.sh b/third_party/git/t/t5521-pull-options.sh
index ccde8ba491..db1a381cd9 100755
--- a/third_party/git/t/t5521-pull-options.sh
+++ b/third_party/git/t/t5521-pull-options.sh
@@ -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,29 +62,36 @@ 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)
 '
 
+test_expect_success 'git pull --no-write-fetch-head fails' '
+	mkdir clonedwfh &&
+	(cd clonedwfh && git init &&
+	test_expect_code 129 git pull --no-write-fetch-head "../parent" >out 2>err &&
+	test_must_be_empty out &&
+	test_i18ngrep "no-write-fetch-head" err)
+'
 
 test_expect_success 'git pull --force' '
 	mkdir clonedoldstyle &&
diff --git a/third_party/git/t/t5523-push-upstream.sh b/third_party/git/t/t5523-push-upstream.sh
index c0df81a014..e47b5db5d6 100755
--- a/third_party/git/t/t5523-push-upstream.sh
+++ b/third_party/git/t/t5523-push-upstream.sh
@@ -41,20 +41,20 @@ test_expect_success 'push -u --dry-run master:otherX' '
 	check_config master upstream refs/heads/other
 '
 
-test_expect_success 'push -u master2:master2' '
-	git branch master2 &&
-	git push -u upstream master2:master2 &&
-	check_config master2 upstream refs/heads/master2
+test_expect_success 'push -u topic_2:topic_2' '
+	git branch topic_2 &&
+	git push -u upstream topic_2:topic_2 &&
+	check_config topic_2 upstream refs/heads/topic_2
 '
 
-test_expect_success 'push -u master2:other2' '
-	git push -u upstream master2:other2 &&
-	check_config master2 upstream refs/heads/other2
+test_expect_success 'push -u topic_2:other2' '
+	git push -u upstream topic_2:other2 &&
+	check_config topic_2 upstream refs/heads/other2
 '
 
-test_expect_success 'push -u :master2' '
-	git push -u upstream :master2 &&
-	check_config master2 upstream refs/heads/other2
+test_expect_success 'push -u :topic_2' '
+	git push -u upstream :topic_2 &&
+	check_config topic_2 upstream refs/heads/other2
 '
 
 test_expect_success 'push -u --all' '
diff --git a/third_party/git/t/t5528-push-default.sh b/third_party/git/t/t5528-push-default.sh
index 44309566f1..f0a287d97d 100755
--- a/third_party/git/t/t5528-push-default.sh
+++ b/third_party/git/t/t5528-push-default.sh
@@ -98,6 +98,12 @@ test_expect_success 'push from/to new branch with upstream, matching and simple'
 	test_push_failure upstream
 '
 
+test_expect_success '"matching" fails if none match' '
+	git init --bare empty &&
+	test_must_fail git push empty : 2>actual &&
+	test_i18ngrep "Perhaps you should specify a branch" actual
+'
+
 test_expect_success 'push ambiguously named branch with upstream, matching and simple' '
 	git checkout -b ambiguous &&
 	test_config branch.ambiguous.remote parent1 &&
@@ -163,7 +169,7 @@ test_pushdefault_workflow success current master
 # update parent1's foo (which is our upstream)
 test_pushdefault_workflow success upstream foo
 
-# upsream is foo which is not the name of the current branch
+# upstream is foo which is not the name of the current branch
 test_pushdefault_workflow failure simple master
 
 # master and foo are updated
diff --git a/third_party/git/t/t5530-upload-pack-error.sh b/third_party/git/t/t5530-upload-pack-error.sh
index a1d3031d40..205a2631e7 100755
--- a/third_party/git/t/t5530-upload-pack-error.sh
+++ b/third_party/git/t/t5530-upload-pack-error.sh
@@ -14,7 +14,6 @@ corrupt_repo () {
 }
 
 test_expect_success 'setup and corrupt repository' '
-
 	echo file >file &&
 	git add file &&
 	git rev-parse :file &&
@@ -31,9 +30,10 @@ test_expect_success 'fsck fails' '
 '
 
 test_expect_success 'upload-pack fails due to error in pack-objects packing' '
-
-	printf "0032want %s\n00000009done\n0000" \
-		$(git rev-parse HEAD) >input &&
+	head=$(git rev-parse HEAD) &&
+	hexsz=$(test_oid hexsz) &&
+	printf "%04xwant %s\n00000009done\n0000" \
+		$(($hexsz + 10)) $head >input &&
 	test_must_fail git upload-pack . <input >/dev/null 2>output.err &&
 	test_i18ngrep "unable to read" output.err &&
 	test_i18ngrep "pack-objects died" output.err
@@ -51,16 +51,17 @@ test_expect_success 'fsck fails' '
 '
 test_expect_success 'upload-pack fails due to error in rev-list' '
 
-	printf "0032want %s\n0034shallow %s00000009done\n0000" \
-		$(git rev-parse HEAD) $(git rev-parse HEAD^) >input &&
+	printf "%04xwant %s\n%04xshallow %s00000009done\n0000" \
+		$(($hexsz + 10)) $(git rev-parse HEAD) \
+		$(($hexsz + 12)) $(git rev-parse HEAD^) >input &&
 	test_must_fail git upload-pack . <input >/dev/null 2>output.err &&
 	grep "bad tree object" output.err
 '
 
 test_expect_success 'upload-pack fails due to bad want (no object)' '
 
-	printf "0045want %s multi_ack_detailed\n00000009done\n0000" \
-		"deadbeefdeadbeefdeadbeefdeadbeefdeadbeef" >input &&
+	printf "%04xwant %s multi_ack_detailed\n00000009done\n0000" \
+		$(($hexsz + 29)) $(test_oid deadbeef) >input &&
 	test_must_fail git upload-pack . <input >output 2>output.err &&
 	grep "not our ref" output.err &&
 	grep "ERR" output &&
@@ -70,8 +71,8 @@ test_expect_success 'upload-pack fails due to bad want (no object)' '
 test_expect_success 'upload-pack fails due to bad want (not tip)' '
 
 	oid=$(echo an object we have | git hash-object -w --stdin) &&
-	printf "0045want %s multi_ack_detailed\n00000009done\n0000" \
-		"$oid" >input &&
+	printf "%04xwant %s multi_ack_detailed\n00000009done\n0000" \
+		$(($hexsz + 29)) "$oid" >input &&
 	test_must_fail git upload-pack . <input >output 2>output.err &&
 	grep "not our ref" output.err &&
 	grep "ERR" output &&
@@ -80,8 +81,8 @@ test_expect_success 'upload-pack fails due to bad want (not tip)' '
 
 test_expect_success 'upload-pack fails due to error in pack-objects enumeration' '
 
-	printf "0032want %s\n00000009done\n0000" \
-		$(git rev-parse HEAD) >input &&
+	printf "%04xwant %s\n00000009done\n0000" \
+		$((hexsz + 10)) $(git rev-parse HEAD) >input &&
 	test_must_fail git upload-pack . <input >/dev/null 2>output.err &&
 	grep "bad tree object" output.err &&
 	grep "pack-objects died" output.err
diff --git a/third_party/git/t/t5534-push-signed.sh b/third_party/git/t/t5534-push-signed.sh
index 030331f1c5..af0385fb89 100755
--- a/third_party/git/t/t5534-push-signed.sh
+++ b/third_party/git/t/t5534-push-signed.sh
@@ -273,4 +273,26 @@ test_expect_success GPGSM 'fail without key and heed user.signingkey x509' '
 	test_cmp expect dst/push-cert-status
 '
 
+test_expect_success GPG 'failed atomic push does not execute GPG' '
+	prepare_dst &&
+	git -C dst config receive.certnonceseed sekrit &&
+	write_script gpg <<-EOF &&
+	# should check atomic push locally before running GPG.
+	exit 1
+	EOF
+	test_must_fail env PATH="$TRASH_DIRECTORY:$PATH" git push \
+			--signed --atomic --porcelain \
+			dst noop ff noff >out 2>err &&
+
+	test_i18ngrep ! "gpg failed to sign" err &&
+	cat >expect <<-EOF &&
+	To dst
+	=	refs/heads/noop:refs/heads/noop	[up to date]
+	!	refs/heads/ff:refs/heads/ff	[rejected] (atomic push failed)
+	!	refs/heads/noff:refs/heads/noff	[rejected] (non-fast-forward)
+	Done
+	EOF
+	test_cmp expect out
+'
+
 test_done
diff --git a/third_party/git/t/t5535-fetch-push-symref.sh b/third_party/git/t/t5535-fetch-push-symref.sh
index 8ed58d27f2..e8f6d233ff 100755
--- a/third_party/git/t/t5535-fetch-push-symref.sh
+++ b/third_party/git/t/t5535-fetch-push-symref.sh
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-test_description='avoiding conflicting update thru symref aliasing'
+test_description='avoiding conflicting update through symref aliasing'
 
 . ./test-lib.sh
 
diff --git a/third_party/git/t/t5537-fetch-shallow.sh b/third_party/git/t/t5537-fetch-shallow.sh
index 66f0b64d39..a55202d2d3 100755
--- a/third_party/git/t/t5537-fetch-shallow.sh
+++ b/third_party/git/t/t5537-fetch-shallow.sh
@@ -15,16 +15,17 @@ test_expect_success 'setup' '
 	commit 2 &&
 	commit 3 &&
 	commit 4 &&
-	git config --global transfer.fsckObjects true
+	git config --global transfer.fsckObjects true &&
+	test_oid_cache <<-\EOF
+	perl sha1:s/0034shallow %s/0036unshallow %s/
+	perl sha256:s/004cshallow %s/004eunshallow %s/
+	EOF
 '
 
 test_expect_success 'setup shallow clone' '
 	git clone --no-local --depth=2 .git shallow &&
 	git --git-dir=shallow/.git log --format=%s >actual &&
-	cat <<EOF >expect &&
-4
-3
-EOF
+	test_write_lines 4 3 >expect &&
 	test_cmp expect actual
 '
 
@@ -34,10 +35,7 @@ test_expect_success 'clone from shallow clone' '
 	cd shallow2 &&
 	git fsck &&
 	git log --format=%s >actual &&
-	cat <<EOF >expect &&
-4
-3
-EOF
+	test_write_lines 4 3 >expect &&
 	test_cmp expect actual
 	)
 '
@@ -52,11 +50,7 @@ test_expect_success 'fetch from shallow clone' '
 	git fetch &&
 	git fsck &&
 	git log --format=%s origin/master >actual &&
-	cat <<EOF >expect &&
-5
-4
-3
-EOF
+	test_write_lines 5 4 3 >expect &&
 	test_cmp expect actual
 	)
 '
@@ -71,10 +65,7 @@ test_expect_success 'fetch --depth from shallow clone' '
 	git fetch --depth=2 &&
 	git fsck &&
 	git log --format=%s origin/master >actual &&
-	cat <<EOF >expect &&
-6
-5
-EOF
+	test_write_lines 6 5 >expect &&
 	test_cmp expect actual
 	)
 '
@@ -85,12 +76,21 @@ test_expect_success 'fetch --unshallow from shallow clone' '
 	git fetch --unshallow &&
 	git fsck &&
 	git log --format=%s origin/master >actual &&
-	cat <<EOF >expect &&
-6
-5
-4
-3
-EOF
+	test_write_lines 6 5 4 3 >expect &&
+	test_cmp expect actual
+	)
+'
+
+test_expect_success 'fetch --unshallow from a full clone' '
+	git clone --no-local --depth=2 .git shallow3 &&
+	(
+	cd shallow3 &&
+	git log --format=%s >actual &&
+	test_write_lines 4 3 >expect &&
+	test_cmp expect actual &&
+	git -c fetch.writeCommitGraph fetch --unshallow &&
+	git log origin/master --format=%s >actual &&
+	test_write_lines 4 3 2 1 >expect &&
 	test_cmp expect actual
 	)
 '
@@ -107,15 +107,10 @@ test_expect_success 'fetch something upstream has but hidden by clients shallow
 	git fetch ../.git +refs/heads/master:refs/remotes/top/master &&
 	git fsck &&
 	git log --format=%s top/master >actual &&
-	cat <<EOF >expect &&
-add-1-back
-4
-3
-EOF
+	test_write_lines add-1-back 4 3 >expect &&
 	test_cmp expect actual
 	) &&
 	git --git-dir=shallow2/.git cat-file blob $(echo 1|git hash-object --stdin) >/dev/null
-
 '
 
 test_expect_success 'fetch that requires changes in .git/shallow is filtered' '
@@ -127,16 +122,12 @@ 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
-EOF
+	echo refs/remotes/shallow/no-shallow >expect.refs &&
 	test_cmp expect.refs actual.refs &&
 	git log --format=%s shallow/no-shallow >actual &&
-	cat <<EOF >expect &&
-no-shallow
-EOF
+	echo no-shallow >expect &&
 	test_cmp expect actual
 	)
 '
@@ -154,21 +145,44 @@ test_expect_success 'fetch --update-shallow' '
 	git fetch --update-shallow ../shallow/.git refs/heads/*:refs/remotes/shallow/* &&
 	git fsck &&
 	git for-each-ref --sort=refname --format="%(refname)" >actual.refs &&
-	cat <<EOF >expect.refs &&
-refs/remotes/shallow/master
-refs/remotes/shallow/no-shallow
-refs/tags/heavy-tag
-refs/tags/light-tag
-EOF
+	cat <<-\EOF >expect.refs &&
+	refs/remotes/shallow/master
+	refs/remotes/shallow/no-shallow
+	refs/tags/heavy-tag
+	refs/tags/light-tag
+	EOF
 	test_cmp expect.refs actual.refs &&
 	git log --format=%s shallow/master >actual &&
-	cat <<EOF >expect &&
-7
-6
-5
-4
-3
-EOF
+	test_write_lines 7 6 5 4 3 >expect &&
+	test_cmp expect actual
+	)
+'
+
+test_expect_success 'fetch --update-shallow (with fetch.writeCommitGraph)' '
+	(
+	cd shallow &&
+	git checkout master &&
+	commit 8 &&
+	git tag -m foo heavy-tag-for-graph HEAD^ &&
+	git tag light-tag-for-graph HEAD^:tracked
+	) &&
+	test_config -C notshallow fetch.writeCommitGraph true &&
+	(
+	cd notshallow &&
+	git fetch --update-shallow ../shallow/.git refs/heads/*:refs/remotes/shallow/* &&
+	git fsck &&
+	git for-each-ref --sort=refname --format="%(refname)" >actual.refs &&
+	cat <<-EOF >expect.refs &&
+	refs/remotes/shallow/master
+	refs/remotes/shallow/no-shallow
+	refs/tags/heavy-tag
+	refs/tags/heavy-tag-for-graph
+	refs/tags/light-tag
+	refs/tags/light-tag-for-graph
+	EOF
+	test_cmp expect.refs actual.refs &&
+	git log --format=%s shallow/master >actual &&
+	test_write_lines 8 7 6 5 4 3 >expect &&
 	test_cmp expect actual
 	)
 '
@@ -179,10 +193,7 @@ test_expect_success POSIXPERM,SANITY 'shallow fetch from a read-only repo' '
 	find read-only.git -print | xargs chmod -w &&
 	git clone --no-local --depth=2 read-only.git from-read-only &&
 	git --git-dir=from-read-only/.git log --format=%s >actual &&
-	cat >expect <<EOF &&
-add-1-back
-4
-EOF
+	test_write_lines add-1-back 4 >expect &&
 	test_cmp expect actual
 '
 
@@ -233,26 +244,29 @@ test_expect_success 'shallow fetches check connectivity before writing shallow f
 	git -C "$REPO" config protocol.version 2 &&
 	git -C client config protocol.version 2 &&
 
-	git -C client fetch --depth=2 "$HTTPD_URL/one_time_sed/repo" master:a_branch &&
+	git -C client fetch --depth=2 "$HTTPD_URL/one_time_perl/repo" master:a_branch &&
 
 	# Craft a situation in which the server sends back an unshallow request
 	# with an empty packfile. This is done by refetching with a shorter
 	# depth (to ensure that the packfile is empty), and overwriting the
 	# shallow line in the response with the unshallow line we want.
-	printf "s/0034shallow %s/0036unshallow %s/" \
+	printf "$(test_oid perl)" \
 	       "$(git -C "$REPO" rev-parse HEAD)" \
 	       "$(git -C "$REPO" rev-parse HEAD^)" \
-	       >"$HTTPD_ROOT_PATH/one-time-sed" &&
+	       >"$HTTPD_ROOT_PATH/one-time-perl" &&
 	test_must_fail env GIT_TEST_SIDEBAND_ALL=0 git -C client \
-		fetch --depth=1 "$HTTPD_URL/one_time_sed/repo" \
+		fetch --depth=1 "$HTTPD_URL/one_time_perl/repo" \
 		master:a_branch &&
 
-	# Ensure that the one-time-sed script was used.
-	! test -e "$HTTPD_ROOT_PATH/one-time-sed" &&
+	# Ensure that the one-time-perl script was used.
+	! test -e "$HTTPD_ROOT_PATH/one-time-perl" &&
 
 	# Ensure that the resulting repo is consistent, despite our failure to
 	# fetch.
 	git -C client fsck
 '
 
+# DO NOT add non-httpd-specific tests here, because the last part of this
+# test script is only executed when httpd is available and enabled.
+
 test_done
diff --git a/third_party/git/t/t5539-fetch-http-shallow.sh b/third_party/git/t/t5539-fetch-http-shallow.sh
index b4ad81f006..82aa99ae87 100755
--- a/third_party/git/t/t5539-fetch-http-shallow.sh
+++ b/third_party/git/t/t5539-fetch-http-shallow.sh
@@ -9,10 +9,12 @@ start_httpd
 commit() {
 	echo "$1" >tracked &&
 	git add tracked &&
+	test_tick &&
 	git commit -m "$1"
 }
 
 test_expect_success 'setup shallow clone' '
+	test_tick=1500000000 &&
 	commit 1 &&
 	commit 2 &&
 	commit 3 &&
@@ -48,7 +50,6 @@ EOF
 test_expect_success 'no shallow lines after receiving ACK ready' '
 	(
 		cd shallow &&
-		test_tick &&
 		for i in $(test_seq 15)
 		do
 			git checkout --orphan unrelated$i &&
@@ -66,10 +67,11 @@ test_expect_success 'no shallow lines after receiving ACK ready' '
 	(
 		cd clone &&
 		git checkout --orphan newnew &&
+		test_tick=1400000000 &&
 		test_commit new-too &&
 		# NEEDSWORK: If the overspecification of the expected result is reduced, we
 		# might be able to run this test in all protocol versions.
-		GIT_TRACE_PACKET="$TRASH_DIRECTORY/trace" GIT_TEST_PROTOCOL_VERSION= \
+		GIT_TRACE_PACKET="$TRASH_DIRECTORY/trace" GIT_TEST_PROTOCOL_VERSION=0 \
 			git fetch --depth=2 &&
 		grep "fetch-pack< ACK .* ready" ../trace &&
 		! grep "fetch-pack> done" ../trace
diff --git a/third_party/git/t/t5540-http-push-webdav.sh b/third_party/git/t/t5540-http-push-webdav.sh
index a094fd5e71..450321fddb 100755
--- a/third_party/git/t/t5540-http-push-webdav.sh
+++ b/third_party/git/t/t5540-http-push-webdav.sh
@@ -126,6 +126,22 @@ test_expect_success 'create and delete remote branch' '
 	test_must_fail git show-ref --verify refs/remotes/origin/dev
 '
 
+test_expect_success 'non-force push fails if not up to date' '
+	git init --bare "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo_conflict.git &&
+	git -C "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo_conflict.git update-server-info &&
+	git clone $HTTPD_URL/dumb/test_repo_conflict.git "$ROOT_PATH"/c1 &&
+	git clone $HTTPD_URL/dumb/test_repo_conflict.git "$ROOT_PATH"/c2 &&
+	test_commit -C "$ROOT_PATH/c1" path1 &&
+	git -C "$ROOT_PATH/c1" push origin HEAD &&
+	git -C "$ROOT_PATH/c2" pull &&
+	test_commit -C "$ROOT_PATH/c1" path2 &&
+	git -C "$ROOT_PATH/c1" push origin HEAD &&
+	test_commit -C "$ROOT_PATH/c2" path3 &&
+	git -C "$ROOT_PATH/c1" log --graph --all &&
+	git -C "$ROOT_PATH/c2" log --graph --all &&
+	test_must_fail git -C "$ROOT_PATH/c2" push origin HEAD
+'
+
 test_expect_success 'MKCOL sends directory names with trailing slashes' '
 
 	! grep "\"MKCOL.*[^/] HTTP/[^ ]*\"" < "$HTTPD_ROOT_PATH"/access.log
@@ -134,15 +150,13 @@ test_expect_success 'MKCOL sends directory names with trailing slashes' '
 
 x1="[0-9a-f]"
 x2="$x1$x1"
-x5="$x1$x1$x1$x1$x1"
-x38="$x5$x5$x5$x5$x5$x5$x5$x1$x1$x1"
-x40="$x38$x2"
+xtrunc=$(echo $OID_REGEX | sed -e "s/\[0-9a-f\]\[0-9a-f\]//")
 
 test_expect_success 'PUT and MOVE sends object to URLs with SHA-1 hash suffix' '
 	sed \
 		-e "s/PUT /OP /" \
 		-e "s/MOVE /OP /" \
-	    -e "s|/objects/$x2/${x38}_$x40|WANTED_PATH_REQUEST|" \
+	    -e "s|/objects/$x2/${xtrunc}_$OID_REGEX|WANTED_PATH_REQUEST|" \
 		"$HTTPD_ROOT_PATH"/access.log |
 	grep -e "\"OP .*WANTED_PATH_REQUEST HTTP/[.0-9]*\" 20[0-9] "
 
diff --git a/third_party/git/t/t5541-http-push-smart.sh b/third_party/git/t/t5541-http-push-smart.sh
index b86ddb60f2..187454f5dd 100755
--- a/third_party/git/t/t5541-http-push-smart.sh
+++ b/third_party/git/t/t5541-http-push-smart.sh
@@ -49,7 +49,7 @@ test_expect_success 'no empty path components' '
 
 	# NEEDSWORK: If the overspecification of the expected result is reduced, we
 	# might be able to run this test in all protocol versions.
-	if test -z "$GIT_TEST_PROTOCOL_VERSION"
+	if test "$GIT_TEST_PROTOCOL_VERSION" = 0
 	then
 		check_access_log exp
 	fi
@@ -135,7 +135,7 @@ EOF
 test_expect_success 'used receive-pack service' '
 	# NEEDSWORK: If the overspecification of the expected result is reduced, we
 	# might be able to run this test in all protocol versions.
-	if test -z "$GIT_TEST_PROTOCOL_VERSION"
+	if test "$GIT_TEST_PROTOCOL_VERSION" = 0
 	then
 		check_access_log exp
 	fi
@@ -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 &&
@@ -184,11 +187,13 @@ test_expect_success 'push --atomic also prevents branch creation, reports collat
 	test_config -C "$d" http.receivepack true &&
 	up="$HTTPD_URL"/smart/atomic-branches.git &&
 
-	# Tell "$up" about two branches for now
+	# Tell "$up" about three branches for now
 	test_commit atomic1 &&
 	test_commit atomic2 &&
 	git branch collateral &&
-	git push "$up" master collateral &&
+	git branch 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 &&
@@ -223,7 +228,46 @@ 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' '
+	# Use previously set up repository
+	d=$HTTPD_DOCUMENT_ROOT_PATH/atomic-branches.git &&
+	test_config -C "$d" http.receivepack true &&
+	up="$HTTPD_URL"/smart/atomic-branches.git &&
+
+	# break ref updates for other on the remote site
+	mkdir "$d/refs/heads/other.lock" &&
+
+	# add the new commit to other
+	git branch -f other collateral &&
+
+	# --atomic should cause entire push to be rejected
+	test_must_fail git push --atomic "$up" atomic other 2>output  &&
+
+	# the new branch should not have been created upstream
+	test_must_fail git -C "$d" show-ref --verify refs/heads/atomic &&
+
+	# upstream should still reflect atomic2, the last thing we pushed
+	# successfully
+	git rev-parse atomic2 >expected &&
+	# ...to other.
+	git -C "$d" rev-parse refs/heads/other >actual &&
+	test_cmp expected actual &&
+
+	# the new branch should not have been created upstream
+	test_must_fail git -C "$d" show-ref --verify refs/heads/atomic &&
+
+	# the failed refs should be indicated to the user
+	grep "^ ! .*rejected.* other -> other .*atomic transaction failed" output &&
+
+	# the collateral failure refs should be indicated to the user
+	grep "^ ! .*rejected.* atomic -> atomic .*atomic transaction failed" output
 '
 
 test_expect_success 'push --all can push to empty repo' '
@@ -262,7 +306,7 @@ test_expect_success TTY 'push shows progress when stderr is a tty' '
 	cd "$ROOT_PATH"/test_repo_clone &&
 	test_commit noisy &&
 	test_terminal git push >output 2>&1 &&
-	test_i18ngrep "Writing objects" output
+	test_i18ngrep "^Writing objects" output
 '
 
 test_expect_success TTY 'push --quiet silences status and progress' '
@@ -277,7 +321,7 @@ test_expect_success TTY 'push --no-progress silences progress but not status' '
 	test_commit no-progress &&
 	test_terminal git push --no-progress >output 2>&1 &&
 	test_i18ngrep "^To http" output &&
-	test_i18ngrep ! "Writing objects" output
+	test_i18ngrep ! "^Writing objects" output
 '
 
 test_expect_success 'push --progress shows progress to non-tty' '
@@ -285,7 +329,7 @@ test_expect_success 'push --progress shows progress to non-tty' '
 	test_commit progress &&
 	git push --progress >output 2>&1 &&
 	test_i18ngrep "^To http" output &&
-	test_i18ngrep "Writing objects" output
+	test_i18ngrep "^Writing objects" output
 '
 
 test_expect_success 'http push gives sane defaults to reflog' '
@@ -420,6 +464,36 @@ test_expect_success 'push status output scrubs password' '
 	grep "^To $HTTPD_URL/smart/test_repo.git" status
 '
 
+test_expect_success 'clone/fetch scrubs password from reflogs' '
+	cd "$ROOT_PATH" &&
+	git clone "$HTTPD_URL_USER_PASS/smart/test_repo.git" \
+		reflog-test &&
+	cd reflog-test &&
+	test_commit prepare-for-force-fetch &&
+	git switch -c away &&
+	git fetch "$HTTPD_URL_USER_PASS/smart/test_repo.git" \
+		+master:master &&
+	# should have been scrubbed down to vanilla URL
+	git log -g master >reflog &&
+	grep "$HTTPD_URL" reflog &&
+	! grep "$HTTPD_URL_USER_PASS" reflog
+'
+
+test_expect_success 'Non-ASCII branch name can be used with --force-with-lease' '
+	cd "$ROOT_PATH" &&
+	git clone "$HTTPD_URL_USER_PASS/smart/test_repo.git" non-ascii &&
+	cd non-ascii &&
+	git checkout -b rama-de-รกrbol &&
+	test_commit F &&
+	git push --force-with-lease origin rama-de-รกrbol &&
+	git ls-remote origin refs/heads/rama-de-รกrbol >actual &&
+	git ls-remote . refs/heads/rama-de-รกrbol >expect &&
+	test_cmp expect actual &&
+	git push --delete --force-with-lease origin rama-de-รกrbol &&
+	git ls-remote origin refs/heads/rama-de-รกrbol >actual &&
+	test_must_be_empty actual
+'
+
 test_expect_success 'colorize errors/hints' '
 	cd "$ROOT_PATH"/test_repo_clone &&
 	test_must_fail git -c color.transport=always -c color.advice=always \
diff --git a/third_party/git/t/t5543-atomic-push.sh b/third_party/git/t/t5543-atomic-push.sh
index 7079bcf9a0..620c30d58f 100755
--- a/third_party/git/t/t5543-atomic-push.sh
+++ b/third_party/git/t/t5543-atomic-push.sh
@@ -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/third_party/git/t/t5545-push-options.sh b/third_party/git/t/t5545-push-options.sh
index 6d1d59c9b1..38e6f7340e 100755
--- a/third_party/git/t/t5545-push-options.sh
+++ b/third_party/git/t/t5545-push-options.sh
@@ -115,7 +115,7 @@ test_expect_success 'push options and submodules' '
 
 	git -C parent submodule add ../upstream workbench &&
 	git -C parent/workbench remote add up ../../upstream &&
-	git -C parent commit -m "add submoule" &&
+	git -C parent commit -m "add submodule" &&
 
 	test_commit -C parent/workbench two &&
 	git -C parent add workbench &&
@@ -278,4 +278,7 @@ test_expect_success 'push options keep quoted characters intact (http)' '
 	test_cmp expect "$HTTPD_DOCUMENT_ROOT_PATH"/upstream.git/hooks/pre-receive.push_options
 '
 
+# DO NOT add non-httpd-specific tests here, because the last part of this
+# test script is only executed when httpd is available and enabled.
+
 test_done
diff --git a/third_party/git/t/t5548-push-porcelain.sh b/third_party/git/t/t5548-push-porcelain.sh
new file mode 100755
index 0000000000..1b19b3ef55
--- /dev/null
+++ b/third_party/git/t/t5548-push-porcelain.sh
@@ -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
diff --git a/third_party/git/t/t5550-http-fetch-dumb.sh b/third_party/git/t/t5550-http-fetch-dumb.sh
index b811d89cfd..483578b2d7 100755
--- a/third_party/git/t/t5550-http-fetch-dumb.sh
+++ b/third_party/git/t/t5550-http-fetch-dumb.sh
@@ -50,6 +50,24 @@ test_expect_success 'create password-protected repository' '
 	       "$HTTPD_DOCUMENT_ROOT_PATH/auth/dumb/repo.git"
 '
 
+test_expect_success 'create empty remote repository' '
+	git init --bare "$HTTPD_DOCUMENT_ROOT_PATH/empty.git" &&
+	(cd "$HTTPD_DOCUMENT_ROOT_PATH/empty.git" &&
+	 mkdir -p hooks &&
+	 write_script "hooks/post-update" <<-\EOF &&
+	 exec git update-server-info
+	EOF
+	 hooks/post-update
+	)
+'
+
+test_expect_success 'empty dumb HTTP repository has default hash algorithm' '
+	test_when_finished "rm -fr clone-empty" &&
+	git clone $HTTPD_URL/dumb/empty.git clone-empty &&
+	git -C clone-empty rev-parse --show-object-format >empty-format &&
+	test "$(cat empty-format)" = "$(test_oid algo)"
+'
+
 setup_askpass_helper
 
 test_expect_success 'cloning password-protected repository can fail' '
@@ -199,6 +217,28 @@ test_expect_success 'fetch packed objects' '
 	git clone $HTTPD_URL/dumb/repo_pack.git
 '
 
+test_expect_success 'http-fetch --packfile' '
+	# Arbitrary hash. Use rev-parse so that we get one of the correct
+	# length.
+	ARBITRARY=$(git -C "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git rev-parse HEAD) &&
+
+	git init packfileclient &&
+	p=$(cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git && ls objects/pack/pack-*.pack) &&
+	git -C packfileclient http-fetch --packfile=$ARBITRARY "$HTTPD_URL"/dumb/repo_pack.git/$p >out &&
+
+	grep "^keep.[0-9a-f]\{16,\}$" out &&
+	cut -c6- out >packhash &&
+
+	# Ensure that the expected files are generated
+	test -e "packfileclient/.git/objects/pack/pack-$(cat packhash).pack" &&
+	test -e "packfileclient/.git/objects/pack/pack-$(cat packhash).idx" &&
+	test -e "packfileclient/.git/objects/pack/pack-$(cat packhash).keep" &&
+
+	# Ensure that it has the HEAD of repo_pack, at least
+	HASH=$(git -C "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git rev-parse HEAD) &&
+	git -C packfileclient cat-file -e "$HASH"
+'
+
 test_expect_success 'fetch notices corrupt pack' '
 	cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad1.git &&
 	(cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad1.git &&
@@ -214,6 +254,14 @@ test_expect_success 'fetch notices corrupt pack' '
 	)
 '
 
+test_expect_success 'http-fetch --packfile with corrupt pack' '
+	rm -rf packfileclient &&
+	git init packfileclient &&
+	p=$(cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad1.git && ls objects/pack/pack-*.pack) &&
+	test_must_fail git -C packfileclient http-fetch --packfile \
+		"$HTTPD_URL"/dumb/repo_bad1.git/$p
+'
+
 test_expect_success 'fetch notices corrupt idx' '
 	cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad2.git &&
 	(cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad2.git &&
@@ -248,9 +296,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 +367,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' '
diff --git a/third_party/git/t/t5551-http-fetch-smart.sh b/third_party/git/t/t5551-http-fetch-smart.sh
index e38e543867..e40e9ed52f 100755
--- a/third_party/git/t/t5551-http-fetch-smart.sh
+++ b/third_party/git/t/t5551-http-fetch-smart.sh
@@ -43,7 +43,7 @@ test_expect_success 'clone http repository' '
 	< Cache-Control: no-cache, max-age=0, must-revalidate
 	< Content-Type: application/x-git-upload-pack-result
 	EOF
-	GIT_TRACE_CURL=true GIT_TEST_PROTOCOL_VERSION= \
+	GIT_TRACE_CURL=true GIT_TEST_PROTOCOL_VERSION=0 \
 		git clone --quiet $HTTPD_URL/smart/repo.git clone 2>err &&
 	test_cmp file clone/file &&
 	tr '\''\015'\'' Q <err |
@@ -84,7 +84,7 @@ test_expect_success 'clone http repository' '
 
 	# NEEDSWORK: If the overspecification of the expected result is reduced, we
 	# might be able to run this test in all protocol versions.
-	if test -z "$GIT_TEST_PROTOCOL_VERSION"
+	if test "$GIT_TEST_PROTOCOL_VERSION" = 0
 	then
 		sed -e "s/^> Accept-Encoding: .*/> Accept-Encoding: ENCODINGS/" \
 				actual >actual.smudged &&
@@ -113,7 +113,7 @@ test_expect_success 'used upload-pack service' '
 
 	# NEEDSWORK: If the overspecification of the expected result is reduced, we
 	# might be able to run this test in all protocol versions.
-	if test -z "$GIT_TEST_PROTOCOL_VERSION"
+	if test "$GIT_TEST_PROTOCOL_VERSION" = 0
 	then
 		check_access_log exp
 	fi
@@ -185,6 +185,40 @@ test_expect_success 'redirects send auth to new location' '
 	expect_askpass both user@host auth/smart/repo.git
 '
 
+test_expect_success 'GIT_TRACE_CURL redacts auth details' '
+	rm -rf redact-auth trace &&
+	set_askpass user@host pass@host &&
+	GIT_TRACE_CURL="$(pwd)/trace" git clone --bare "$HTTPD_URL/auth/smart/repo.git" redact-auth &&
+	expect_askpass both user@host &&
+
+	# Ensure that there is no "Basic" followed by a base64 string, but that
+	# the auth details are redacted
+	! grep "Authorization: Basic [0-9a-zA-Z+/]" trace &&
+	grep "Authorization: Basic <redacted>" trace
+'
+
+test_expect_success 'GIT_CURL_VERBOSE redacts auth details' '
+	rm -rf redact-auth trace &&
+	set_askpass user@host pass@host &&
+	GIT_CURL_VERBOSE=1 git clone --bare "$HTTPD_URL/auth/smart/repo.git" redact-auth 2>trace &&
+	expect_askpass both user@host &&
+
+	# Ensure that there is no "Basic" followed by a base64 string, but that
+	# the auth details are redacted
+	! grep "Authorization: Basic [0-9a-zA-Z+/]" trace &&
+	grep "Authorization: Basic <redacted>" trace
+'
+
+test_expect_success 'GIT_TRACE_CURL does not redact auth details if GIT_TRACE_REDACT=0' '
+	rm -rf redact-auth trace &&
+	set_askpass user@host pass@host &&
+	GIT_TRACE_REDACT=0 GIT_TRACE_CURL="$(pwd)/trace" \
+		git clone --bare "$HTTPD_URL/auth/smart/repo.git" redact-auth &&
+	expect_askpass both user@host &&
+
+	grep "Authorization: Basic [0-9a-zA-Z+/]" trace
+'
+
 test_expect_success 'disable dumb http on server' '
 	git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/repo.git" \
 		config http.getanyfile false
@@ -241,7 +275,7 @@ test_expect_success 'cookies stored in http.cookiefile when http.savecookies set
 
 	# NEEDSWORK: If the overspecification of the expected result is reduced, we
 	# might be able to run this test in all protocol versions.
-	if test -z "$GIT_TEST_PROTOCOL_VERSION"
+	if test "$GIT_TEST_PROTOCOL_VERSION" = 0
 	then
 		tail -3 cookies.txt | sort >cookies_tail.txt &&
 		test_cmp expect_cookies.txt cookies_tail.txt
@@ -336,7 +370,7 @@ test_expect_success 'test allowreachablesha1inwant with unreachable' '
 	git -C test_reachable.git remote add origin "$HTTPD_URL/smart/repo.git" &&
 	# Some protocol versions (e.g. 2) support fetching
 	# unadvertised objects, so restrict this test to v0.
-	test_must_fail env GIT_TEST_PROTOCOL_VERSION= \
+	test_must_fail env GIT_TEST_PROTOCOL_VERSION=0 \
 		git -C test_reachable.git fetch origin "$(git rev-parse HEAD)"
 '
 
@@ -358,7 +392,7 @@ test_expect_success 'test allowanysha1inwant with unreachable' '
 	git -C test_reachable.git remote add origin "$HTTPD_URL/smart/repo.git" &&
 	# Some protocol versions (e.g. 2) support fetching
 	# unadvertised objects, so restrict this test to v0.
-	test_must_fail env GIT_TEST_PROTOCOL_VERSION= \
+	test_must_fail env GIT_TEST_PROTOCOL_VERSION=0 \
 		git -C test_reachable.git fetch origin "$(git rev-parse HEAD)" &&
 
 	git -C "$server" config uploadpack.allowanysha1inwant 1 &&
@@ -430,27 +464,39 @@ test_expect_success 'fetch by SHA-1 without tag following' '
 		--no-tags origin $(cat bar_hash)
 '
 
-test_expect_success 'GIT_REDACT_COOKIES redacts cookies' '
+test_expect_success 'cookies are redacted by default' '
 	rm -rf clone &&
 	echo "Set-Cookie: Foo=1" >cookies &&
 	echo "Set-Cookie: Bar=2" >>cookies &&
-	GIT_TRACE_CURL=true GIT_REDACT_COOKIES=Bar,Baz \
+	GIT_TRACE_CURL=true \
 		git -c "http.cookieFile=$(pwd)/cookies" clone \
 		$HTTPD_URL/smart/repo.git clone 2>err &&
-	grep "Cookie:.*Foo=1" err &&
+	grep "Cookie:.*Foo=<redacted>" err &&
 	grep "Cookie:.*Bar=<redacted>" err &&
+	! grep "Cookie:.*Foo=1" err &&
 	! grep "Cookie:.*Bar=2" err
 '
 
-test_expect_success 'GIT_REDACT_COOKIES handles empty values' '
+test_expect_success 'empty values of cookies are also redacted' '
 	rm -rf clone &&
 	echo "Set-Cookie: Foo=" >cookies &&
-	GIT_TRACE_CURL=true GIT_REDACT_COOKIES=Foo \
+	GIT_TRACE_CURL=true \
 		git -c "http.cookieFile=$(pwd)/cookies" clone \
 		$HTTPD_URL/smart/repo.git clone 2>err &&
 	grep "Cookie:.*Foo=<redacted>" err
 '
 
+test_expect_success 'GIT_TRACE_REDACT=0 disables cookie redaction' '
+	rm -rf clone &&
+	echo "Set-Cookie: Foo=1" >cookies &&
+	echo "Set-Cookie: Bar=2" >>cookies &&
+	GIT_TRACE_REDACT=0 GIT_TRACE_CURL=true \
+		git -c "http.cookieFile=$(pwd)/cookies" clone \
+		$HTTPD_URL/smart/repo.git clone 2>err &&
+	grep "Cookie:.*Foo=1" err &&
+	grep "Cookie:.*Bar=2" err
+'
+
 test_expect_success 'GIT_TRACE_CURL_NO_DATA prevents data from being traced' '
 	rm -rf clone &&
 	GIT_TRACE_CURL=true \
diff --git a/third_party/git/t/t5552-skipping-fetch-negotiator.sh b/third_party/git/t/t5552-skipping-fetch-negotiator.sh
index 8a14be51a1..156c704040 100755
--- a/third_party/git/t/t5552-skipping-fetch-negotiator.sh
+++ b/third_party/git/t/t5552-skipping-fetch-negotiator.sh
@@ -60,29 +60,6 @@ test_expect_success 'commits with no parents are sent regardless of skip distanc
 	have_not_sent c6 c4 c3
 '
 
-test_expect_success 'unknown fetch.negotiationAlgorithm values error out' '
-	rm -rf server client trace &&
-	git init server &&
-	test_commit -C server to_fetch &&
-
-	git init client &&
-	test_commit -C client on_client &&
-	git -C client checkout on_client &&
-
-	test_config -C client fetch.negotiationAlgorithm invalid &&
-	test_must_fail git -C client fetch "$(pwd)/server" 2>err &&
-	test_i18ngrep "unknown fetch negotiation algorithm" err &&
-
-	# Explicit "default" value
-	test_config -C client fetch.negotiationAlgorithm default &&
-	git -C client -c fetch.negotiationAlgorithm=default fetch "$(pwd)/server" &&
-
-	# Implementation detail: If there is nothing to fetch, we will not error out
-	test_config -C client fetch.negotiationAlgorithm invalid &&
-	git -C client fetch "$(pwd)/server" 2>err &&
-	test_i18ngrep ! "unknown fetch negotiation algorithm" err
-'
-
 test_expect_success 'when two skips collide, favor the larger one' '
 	rm -rf server client trace &&
 	git init server &&
@@ -130,7 +107,11 @@ test_expect_success 'use ref advertisement to filter out commits' '
 
 	# The ref advertisement itself is filtered when protocol v2 is used, so
 	# use v0.
-	GIT_TEST_PROTOCOL_VERSION= trace_fetch client origin to_fetch &&
+	(
+		GIT_TEST_PROTOCOL_VERSION=0 &&
+		export GIT_TEST_PROTOCOL_VERSION &&
+		trace_fetch client origin to_fetch
+	) &&
 	have_sent c5 c4^ c2side &&
 	have_not_sent c4 c4^^ c4^^^
 '
@@ -192,7 +173,17 @@ test_expect_success 'do not send "have" with ancestors of commits that server AC
 	test_commit -C server commit-on-b1 &&
 
 	test_config -C client fetch.negotiationalgorithm skipping &&
-	trace_fetch client "$(pwd)/server" to_fetch &&
+
+	# NEEDSWORK: The number of "have"s sent depends on whether the transport
+	# is stateful. If the overspecification of the result were reduced, this
+	# test could be used for both stateful and stateless transports.
+	(
+		# Force protocol v0, in which local transport is stateful (in
+		# protocol v2 it is stateless).
+		GIT_TEST_PROTOCOL_VERSION=0 &&
+		export GIT_TEST_PROTOCOL_VERSION &&
+		trace_fetch client "$(pwd)/server" to_fetch
+	) &&
 	grep "  fetch" trace &&
 
 	# fetch-pack sends 2 requests each containing 16 "have" lines before
diff --git a/third_party/git/t/t5553-set-upstream.sh b/third_party/git/t/t5553-set-upstream.sh
new file mode 100755
index 0000000000..7622981cbf
--- /dev/null
+++ b/third_party/git/t/t5553-set-upstream.sh
@@ -0,0 +1,178 @@
+#!/bin/sh
+
+test_description='"git fetch/pull --set-upstream" basic tests.'
+. ./test-lib.sh
+
+check_config () {
+	printf "%s\n" "$2" "$3" >"expect.$1" &&
+	{
+		git config "branch.$1.remote" && git config "branch.$1.merge"
+	} >"actual.$1" &&
+	test_cmp "expect.$1" "actual.$1"
+}
+
+check_config_missing () {
+	test_expect_code 1 git config "branch.$1.remote" &&
+	test_expect_code 1 git config "branch.$1.merge"
+}
+
+clear_config () {
+	for branch in "$@"; do
+		test_might_fail git config --unset-all "branch.$branch.remote"
+		test_might_fail git config --unset-all "branch.$branch.merge"
+	done
+}
+
+ensure_fresh_upstream () {
+	rm -rf parent && git init --bare parent
+}
+
+test_expect_success 'setup bare parent fetch' '
+	ensure_fresh_upstream &&
+	git remote add upstream parent
+'
+
+test_expect_success 'setup commit on master and other fetch' '
+	test_commit one &&
+	git push upstream master &&
+	git checkout -b other &&
+	test_commit two &&
+	git push upstream other
+'
+
+# tests for fetch --set-upstream
+
+test_expect_success 'fetch --set-upstream does not set upstream w/o branch' '
+	clear_config master other &&
+	git checkout master &&
+	git fetch --set-upstream upstream &&
+	check_config_missing master &&
+	check_config_missing other
+'
+
+test_expect_success 'fetch --set-upstream upstream master sets branch master but not other' '
+	clear_config master other &&
+	git fetch --set-upstream upstream master &&
+	check_config master upstream refs/heads/master &&
+	check_config_missing other
+'
+
+test_expect_success 'fetch --set-upstream upstream other sets branch other' '
+	clear_config master other &&
+	git fetch --set-upstream upstream other &&
+	check_config master upstream refs/heads/other &&
+	check_config_missing other
+'
+
+test_expect_success 'fetch --set-upstream master:other does not set the branch other2' '
+	clear_config other2 &&
+	git fetch --set-upstream upstream master:other2 &&
+	check_config_missing other2
+'
+
+test_expect_success 'fetch --set-upstream http://nosuchdomain.example.com fails with invalid url' '
+	# master explicitly not cleared, we check that it is not touched from previous value
+	clear_config other other2 &&
+	test_must_fail git fetch --set-upstream http://nosuchdomain.example.com &&
+	check_config master upstream refs/heads/other &&
+	check_config_missing other &&
+	check_config_missing other2
+'
+
+test_expect_success 'fetch --set-upstream with valid URL sets upstream to URL' '
+	clear_config other other2 &&
+	url="file://$PWD" &&
+	git fetch --set-upstream "$url" &&
+	check_config master "$url" HEAD &&
+	check_config_missing other &&
+	check_config_missing other2
+'
+
+# tests for pull --set-upstream
+
+test_expect_success 'setup bare parent pull' '
+	git remote rm upstream &&
+	ensure_fresh_upstream &&
+	git remote add upstream parent
+'
+
+test_expect_success 'setup commit on master and other pull' '
+	test_commit three &&
+	git push --tags upstream master &&
+	test_commit four &&
+	git push upstream other
+'
+
+test_expect_success 'pull --set-upstream upstream master sets branch master but not other' '
+	clear_config master other &&
+	git pull --set-upstream upstream master &&
+	check_config master upstream refs/heads/master &&
+	check_config_missing other
+'
+
+test_expect_success 'pull --set-upstream master:other2 does not set the branch other2' '
+	clear_config other2 &&
+	git pull --set-upstream upstream master:other2 &&
+	check_config_missing other2
+'
+
+test_expect_success 'pull --set-upstream upstream other sets branch master' '
+	clear_config master other &&
+	git pull --set-upstream upstream other &&
+	check_config master upstream refs/heads/other &&
+	check_config_missing other
+'
+
+test_expect_success 'pull --set-upstream upstream tag does not set the tag' '
+	clear_config three &&
+	git pull --tags --set-upstream upstream three &&
+	check_config_missing three
+'
+
+test_expect_success 'pull --set-upstream http://nosuchdomain.example.com fails with invalid url' '
+	# master explicitly not cleared, we check that it is not touched from previous value
+	clear_config other other2 three &&
+	test_must_fail git pull --set-upstream http://nosuchdomain.example.com &&
+	check_config master upstream refs/heads/other &&
+	check_config_missing other &&
+	check_config_missing other2 &&
+	check_config_missing three
+'
+
+test_expect_success 'pull --set-upstream upstream HEAD sets branch HEAD' '
+	clear_config master other &&
+	git pull --set-upstream upstream HEAD &&
+	check_config master upstream HEAD &&
+	git checkout other &&
+	git pull --set-upstream upstream HEAD &&
+	check_config other upstream HEAD
+'
+
+test_expect_success 'pull --set-upstream upstream with more than one branch does nothing' '
+	clear_config master three &&
+	git pull --set-upstream upstream master three &&
+	check_config_missing master &&
+	check_config_missing three
+'
+
+test_expect_success 'pull --set-upstream with valid URL sets upstream to URL' '
+	clear_config master other other2 &&
+	git checkout master &&
+	url="file://$PWD" &&
+	git pull --set-upstream "$url" &&
+	check_config master "$url" HEAD &&
+	check_config_missing other &&
+	check_config_missing other2
+'
+
+test_expect_success 'pull --set-upstream with valid URL and branch sets branch' '
+	clear_config master other other2 &&
+	git checkout master &&
+	url="file://$PWD" &&
+	git pull --set-upstream "$url" master &&
+	check_config master "$url" refs/heads/master &&
+	check_config_missing other &&
+	check_config_missing other2
+'
+
+test_done
diff --git a/third_party/git/t/t5554-noop-fetch-negotiator.sh b/third_party/git/t/t5554-noop-fetch-negotiator.sh
new file mode 100755
index 0000000000..2ac7b5859e
--- /dev/null
+++ b/third_party/git/t/t5554-noop-fetch-negotiator.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+test_description='test noop fetch negotiator'
+. ./test-lib.sh
+
+test_expect_success 'noop negotiator does not emit any "have"' '
+	rm -f trace &&
+
+	test_create_repo server &&
+	test_commit -C server to_fetch &&
+
+	test_create_repo client &&
+	test_commit -C client we_have &&
+
+	test_config -C client fetch.negotiationalgorithm noop &&
+	GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch "$(pwd)/server" &&
+
+	! grep "fetch> have" trace &&
+	grep "fetch> done" trace
+'
+
+test_done
diff --git a/third_party/git/t/t5562-http-backend-content-length.sh b/third_party/git/t/t5562-http-backend-content-length.sh
index f0f425b2cf..e5d3d15ba8 100755
--- a/third_party/git/t/t5562-http-backend-content-length.sh
+++ b/third_party/git/t/t5562-http-backend-content-length.sh
@@ -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" "$_z40" "$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 object-format=%s\\n" \
+			"$ZERO_OID" "$hash_next" "$(test_oid algo)" | packetize &&
+		printf 0000 &&
+		echo "$hash_next" | git pack-objects --stdout
+	} >push_body &&
 	test_copy_bytes 10 <push_body >push_body.trunc &&
 	: >empty_body
 '
diff --git a/third_party/git/t/t5572-pull-submodule.sh b/third_party/git/t/t5572-pull-submodule.sh
index f916729a12..1d75e3b12b 100755
--- a/third_party/git/t/t5572-pull-submodule.sh
+++ b/third_party/git/t/t5572-pull-submodule.sh
@@ -13,34 +13,38 @@ reset_branch_to_HEAD () {
 
 git_pull () {
 	reset_branch_to_HEAD "$1" &&
-	git pull
+	may_only_be_test_must_fail "$2" &&
+	$2 git pull
 }
 
 # pulls without conflicts
-test_submodule_switch "git_pull"
+test_submodule_switch_func "git_pull"
 
 git_pull_ff () {
 	reset_branch_to_HEAD "$1" &&
-	git pull --ff
+	may_only_be_test_must_fail "$2" &&
+	$2 git pull --ff
 }
 
-test_submodule_switch "git_pull_ff"
+test_submodule_switch_func "git_pull_ff"
 
 git_pull_ff_only () {
 	reset_branch_to_HEAD "$1" &&
-	git pull --ff-only
+	may_only_be_test_must_fail "$2" &&
+	$2 git pull --ff-only
 }
 
-test_submodule_switch "git_pull_ff_only"
+test_submodule_switch_func "git_pull_ff_only"
 
 git_pull_noff () {
 	reset_branch_to_HEAD "$1" &&
-	git pull --no-ff
+	may_only_be_test_must_fail "$2" &&
+	$2 git pull --no-ff
 }
 
 KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1
 KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES=1
-test_submodule_switch "git_pull_noff"
+test_submodule_switch_func "git_pull_noff"
 
 test_expect_success 'pull --recurse-submodule setup' '
 	test_create_repo child &&
diff --git a/third_party/git/t/t5573-pull-verify-signatures.sh b/third_party/git/t/t5573-pull-verify-signatures.sh
index 3e9876e197..a53dd8550d 100755
--- a/third_party/git/t/t5573-pull-verify-signatures.sh
+++ b/third_party/git/t/t5573-pull-verify-signatures.sh
@@ -60,6 +60,27 @@ test_expect_success GPG 'pull commit with untrusted signature with --verify-sign
 	test_i18ngrep "has an untrusted GPG signature" pullerror
 '
 
+test_expect_success GPG 'pull commit with untrusted signature with --verify-signatures and minTrustLevel=ultimate' '
+	test_when_finished "git reset --hard && git checkout initial" &&
+	test_config gpg.minTrustLevel ultimate &&
+	test_must_fail git pull --ff-only --verify-signatures untrusted 2>pullerror &&
+	test_i18ngrep "has an untrusted GPG signature" pullerror
+'
+
+test_expect_success GPG 'pull commit with untrusted signature with --verify-signatures and minTrustLevel=marginal' '
+	test_when_finished "git reset --hard && git checkout initial" &&
+	test_config gpg.minTrustLevel marginal &&
+	test_must_fail git pull --ff-only --verify-signatures untrusted 2>pullerror &&
+	test_i18ngrep "has an untrusted GPG signature" pullerror
+'
+
+test_expect_success GPG 'pull commit with untrusted signature with --verify-signatures and minTrustLevel=undefined' '
+	test_when_finished "git reset --hard && git checkout initial" &&
+	test_config gpg.minTrustLevel undefined &&
+	git pull --ff-only --verify-signatures untrusted >pulloutput &&
+	test_i18ngrep "has a good GPG signature" pulloutput
+'
+
 test_expect_success GPG 'pull signed commit with --verify-signatures' '
 	test_when_finished "git reset --hard && git checkout initial" &&
 	git pull --verify-signatures signed >pulloutput &&
@@ -79,10 +100,53 @@ test_expect_success GPG 'pull commit with bad signature with --no-verify-signatu
 '
 
 test_expect_success GPG 'pull unsigned commit into unborn branch' '
+	test_when_finished "rm -rf empty-repo" &&
 	git init empty-repo &&
 	test_must_fail \
 		git -C empty-repo pull --verify-signatures ..  2>pullerror &&
 	test_i18ngrep "does not have a GPG signature" pullerror
 '
 
+test_expect_success GPG 'pull commit into unborn branch with bad signature and --verify-signatures' '
+	test_when_finished "rm -rf empty-repo" &&
+	git init empty-repo &&
+	test_must_fail \
+		git -C empty-repo pull --ff-only --verify-signatures ../bad 2>pullerror &&
+	test_i18ngrep "has a bad GPG signature" pullerror
+'
+
+test_expect_success GPG 'pull commit into unborn branch with untrusted signature and --verify-signatures' '
+	test_when_finished "rm -rf empty-repo" &&
+	git init empty-repo &&
+	test_must_fail \
+		git -C empty-repo pull --ff-only --verify-signatures ../untrusted 2>pullerror &&
+	test_i18ngrep "has an untrusted GPG signature" pullerror
+'
+
+test_expect_success GPG 'pull commit into unborn branch with untrusted signature and --verify-signatures and minTrustLevel=ultimate' '
+	test_when_finished "rm -rf empty-repo" &&
+	git init empty-repo &&
+	test_config_global gpg.minTrustLevel ultimate &&
+	test_must_fail \
+		git -C empty-repo pull --ff-only --verify-signatures ../untrusted 2>pullerror &&
+	test_i18ngrep "has an untrusted GPG signature" pullerror
+'
+
+test_expect_success GPG 'pull commit into unborn branch with untrusted signature and --verify-signatures and minTrustLevel=marginal' '
+	test_when_finished "rm -rf empty-repo" &&
+	git init empty-repo &&
+	test_config_global gpg.minTrustLevel marginal &&
+	test_must_fail \
+		git -C empty-repo pull --ff-only --verify-signatures ../untrusted 2>pullerror &&
+	test_i18ngrep "has an untrusted GPG signature" pullerror
+'
+
+test_expect_success GPG 'pull commit into unborn branch with untrusted signature and --verify-signatures and minTrustLevel=undefined' '
+	test_when_finished "rm -rf empty-repo" &&
+	git init empty-repo &&
+	test_config_global gpg.minTrustLevel undefined &&
+	git -C empty-repo pull --ff-only --verify-signatures ../untrusted >pulloutput &&
+	test_i18ngrep "has a good GPG signature" pulloutput
+'
+
 test_done
diff --git a/third_party/git/t/t5580-clone-push-unc.sh b/third_party/git/t/t5580-unc-paths.sh
index b3c8a92450..cf768b3a27 100755
--- a/third_party/git/t/t5580-clone-push-unc.sh
+++ b/third_party/git/t/t5580-unc-paths.sh
@@ -40,11 +40,23 @@ test_expect_success clone '
 	git clone "file://$UNCPATH" clone
 '
 
+test_expect_success 'clone without file://' '
+	git clone "$UNCPATH" clone-without-file
+'
+
 test_expect_success 'clone with backslashed path' '
 	BACKSLASHED="$(echo "$UNCPATH" | tr / \\\\)" &&
 	git clone "$BACKSLASHED" backslashed
 '
 
+test_expect_success fetch '
+	git init to-fetch &&
+	(
+		cd to-fetch &&
+		git fetch "$UNCPATH" master
+	)
+'
+
 test_expect_success push '
 	(
 		cd clone &&
@@ -58,7 +70,7 @@ test_expect_success push '
 
 test_expect_success MINGW 'remote nick cannot contain backslashes' '
 	BACKSLASHED="$(winpwd | tr / \\\\)" &&
-	git ls-remote "$BACKSLASHED" >out 2>err &&
+	git ls-remote "$BACKSLASHED" 2>err &&
 	test_i18ngrep ! "unable to access" err
 '
 
diff --git a/third_party/git/t/t5581-http-curl-verbose.sh b/third_party/git/t/t5581-http-curl-verbose.sh
index 5129b0724f..927aad0820 100755
--- a/third_party/git/t/t5581-http-curl-verbose.sh
+++ b/third_party/git/t/t5581-http-curl-verbose.sh
@@ -20,7 +20,7 @@ test_expect_success 'failure in git-upload-pack is shown' '
 	test_might_fail env GIT_CURL_VERBOSE=1 \
 		git clone "$HTTPD_URL/error_git_upload_pack/smart/repo.git" \
 		2>curl_log &&
-	grep "< HTTP/1.1 500 Intentional Breakage" curl_log
+	grep "<= Recv header: HTTP/1.1 500 Intentional Breakage" curl_log
 '
 
 test_done
diff --git a/third_party/git/t/t5582-fetch-negative-refspec.sh b/third_party/git/t/t5582-fetch-negative-refspec.sh
new file mode 100755
index 0000000000..8c61e28fec
--- /dev/null
+++ b/third_party/git/t/t5582-fetch-negative-refspec.sh
@@ -0,0 +1,189 @@
+#!/bin/sh
+# Copyright (c) 2020, Jacob Keller.
+
+test_description='"git fetch" with negative refspecs.
+
+'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+	echo >file original &&
+	git add file &&
+	git commit -a -m original
+'
+
+test_expect_success "clone and setup child repos" '
+	git clone . one &&
+	(
+		cd one &&
+		echo >file updated by one &&
+		git commit -a -m "updated by one" &&
+		git switch -c alternate &&
+		echo >file updated again by one &&
+		git commit -a -m "updated by one again" &&
+		git switch master
+	) &&
+	git clone . two &&
+	(
+		cd two &&
+		git config branch.master.remote one &&
+		git config remote.one.url ../one/.git/ &&
+		git config remote.one.fetch +refs/heads/*:refs/remotes/one/* &&
+		git config --add remote.one.fetch ^refs/heads/alternate
+	) &&
+	git clone . three
+'
+
+test_expect_success "fetch one" '
+	echo >file updated by origin &&
+	git commit -a -m "updated by origin" &&
+	(
+		cd two &&
+		test_must_fail git rev-parse --verify refs/remotes/one/alternate &&
+		git fetch one &&
+		test_must_fail git rev-parse --verify refs/remotes/one/alternate &&
+		git rev-parse --verify refs/remotes/one/master &&
+		mine=$(git rev-parse refs/remotes/one/master) &&
+		his=$(cd ../one && git rev-parse refs/heads/master) &&
+		test "z$mine" = "z$his"
+	)
+'
+
+test_expect_success "fetch with negative refspec on commandline" '
+	echo >file updated by origin again &&
+	git commit -a -m "updated by origin again" &&
+	(
+		cd three &&
+		alternate_in_one=$(cd ../one && git rev-parse refs/heads/alternate) &&
+		echo $alternate_in_one >expect &&
+		git fetch ../one/.git refs/heads/*:refs/remotes/one/* ^refs/heads/master &&
+		cut -f -1 .git/FETCH_HEAD >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success "fetch with negative sha1 refspec fails" '
+	echo >file updated by origin yet again &&
+	git commit -a -m "updated by origin yet again" &&
+	(
+		cd three &&
+		master_in_one=$(cd ../one && git rev-parse refs/heads/master) &&
+		test_must_fail git fetch ../one/.git refs/heads/*:refs/remotes/one/* ^$master_in_one
+	)
+'
+
+test_expect_success "fetch with negative pattern refspec" '
+	echo >file updated by origin once more &&
+	git commit -a -m "updated by origin once more" &&
+	(
+		cd three &&
+		alternate_in_one=$(cd ../one && git rev-parse refs/heads/alternate) &&
+		echo $alternate_in_one >expect &&
+		git fetch ../one/.git refs/heads/*:refs/remotes/one/* ^refs/heads/m* &&
+		cut -f -1 .git/FETCH_HEAD >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success "fetch with negative pattern refspec does not expand prefix" '
+	echo >file updated by origin another time &&
+	git commit -a -m "updated by origin another time" &&
+	(
+		cd three &&
+		alternate_in_one=$(cd ../one && git rev-parse refs/heads/alternate) &&
+		master_in_one=$(cd ../one && git rev-parse refs/heads/master) &&
+		echo $alternate_in_one >expect &&
+		echo $master_in_one >>expect &&
+		git fetch ../one/.git refs/heads/*:refs/remotes/one/* ^master &&
+		cut -f -1 .git/FETCH_HEAD >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success "fetch with negative refspec avoids duplicate conflict" '
+	cd "$D" &&
+	(
+		cd one &&
+		git branch dups/a &&
+		git branch dups/b &&
+		git branch dups/c &&
+		git branch other/a &&
+		git rev-parse --verify refs/heads/other/a >../expect &&
+		git rev-parse --verify refs/heads/dups/b >>../expect &&
+		git rev-parse --verify refs/heads/dups/c >>../expect
+	) &&
+	(
+		cd three &&
+		git fetch ../one/.git ^refs/heads/dups/a refs/heads/dups/*:refs/dups/* refs/heads/other/a:refs/dups/a &&
+		git rev-parse --verify refs/dups/a >../actual &&
+		git rev-parse --verify refs/dups/b >>../actual &&
+		git rev-parse --verify refs/dups/c >>../actual
+	) &&
+	test_cmp expect actual
+'
+
+test_expect_success "push --prune with negative refspec" '
+	(
+		cd two &&
+		git branch prune/a &&
+		git branch prune/b &&
+		git branch prune/c &&
+		git push ../three refs/heads/prune/* &&
+		git branch -d prune/a &&
+		git branch -d prune/b &&
+		git push --prune ../three refs/heads/prune/* ^refs/heads/prune/b
+	) &&
+	(
+		cd three &&
+		test_write_lines b c >expect &&
+		git for-each-ref --format="%(refname:lstrip=3)" refs/heads/prune/ >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success "push --prune with negative refspec apply to the destination" '
+	(
+		cd two &&
+		git branch ours/a &&
+		git branch ours/b &&
+		git branch ours/c &&
+		git push ../three refs/heads/ours/*:refs/heads/theirs/* &&
+		git branch -d ours/a &&
+		git branch -d ours/b &&
+		git push --prune ../three refs/heads/ours/*:refs/heads/theirs/* ^refs/heads/theirs/b
+	) &&
+	(
+		cd three &&
+		test_write_lines b c >expect &&
+		git for-each-ref --format="%(refname:lstrip=3)" refs/heads/theirs/ >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success "fetch --prune with negative refspec" '
+	(
+		cd two &&
+		git branch fetch/a &&
+		git branch fetch/b &&
+		git branch fetch/c
+	) &&
+	(
+		cd three &&
+		git fetch ../two/.git refs/heads/fetch/*:refs/heads/copied/*
+	) &&
+	(
+		cd two &&
+		git branch -d fetch/a &&
+		git branch -d fetch/b
+	) &&
+	(
+		cd three &&
+		test_write_lines b c >expect &&
+		git fetch -v ../two/.git --prune refs/heads/fetch/*:refs/heads/copied/* ^refs/heads/fetch/b &&
+		git for-each-ref --format="%(refname:lstrip=3)" refs/heads/copied/ >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_done
diff --git a/third_party/git/t/t5601-clone.sh b/third_party/git/t/t5601-clone.sh
index 37d76808d4..7df3c5373a 100755
--- a/third_party/git/t/t5601-clone.sh
+++ b/third_party/git/t/t5601-clone.sh
@@ -271,7 +271,9 @@ test_expect_success 'fetch from gitfile parent' '
 
 test_expect_success 'clone separate gitdir where target already exists' '
 	rm -rf dst &&
-	test_must_fail git clone --separate-git-dir realgitdir src dst
+	echo foo=bar >>realgitdir/config &&
+	test_must_fail git clone --separate-git-dir realgitdir src dst &&
+	grep foo=bar realgitdir/config
 '
 
 test_expect_success 'clone --reference from original' '
@@ -434,7 +436,6 @@ test_expect_success 'double quoted plink.exe in GIT_SSH_COMMAND' '
 	expect_ssh "-v -P 123" myhost src
 '
 
-SQ="'"
 test_expect_success 'single quoted plink.exe in GIT_SSH_COMMAND' '
 	copy_ssh_wrapper_as "$TRASH_DIRECTORY/plink.exe" &&
 	GIT_SSH_COMMAND="$SQ$TRASH_DIRECTORY/plink.exe$SQ -v" \
@@ -630,16 +631,30 @@ test_expect_success CASE_INSENSITIVE_FS 'colliding file detection' '
 	test_i18ngrep "the following paths have collided" icasefs/warning
 '
 
+test_expect_success 'clone with GIT_DEFAULT_HASH' '
+	(
+		sane_unset GIT_DEFAULT_HASH &&
+		git init --object-format=sha1 test-sha1 &&
+		git init --object-format=sha256 test-sha256
+	) &&
+	test_commit -C test-sha1 foo &&
+	test_commit -C test-sha256 foo &&
+	GIT_DEFAULT_HASH=sha1 git clone test-sha256 test-clone-sha256 &&
+	GIT_DEFAULT_HASH=sha256 git clone test-sha1 test-clone-sha1 &&
+	git -C test-clone-sha1 status &&
+	git -C test-clone-sha256 status
+'
+
 partial_clone_server () {
 	       SERVER="$1" &&
 
 	rm -rf "$SERVER" client &&
 	test_create_repo "$SERVER" &&
 	test_commit -C "$SERVER" one &&
-	HASH1=$(git hash-object "$SERVER/one.t") &&
+	HASH1=$(git -C "$SERVER" hash-object one.t) &&
 	git -C "$SERVER" revert HEAD &&
 	test_commit -C "$SERVER" two &&
-	HASH2=$(git hash-object "$SERVER/two.t") &&
+	HASH2=$(git -C "$SERVER" hash-object two.t) &&
 	test_config -C "$SERVER" uploadpack.allowfilter 1 &&
 	test_config -C "$SERVER" uploadpack.allowanysha1inwant 1
 }
@@ -654,7 +669,8 @@ partial_clone () {
 	git -C client fsck &&
 
 	# Ensure that unneeded blobs are not inadvertently fetched.
-	test_config -C client extensions.partialclone "not a remote" &&
+	test_config -C client remote.origin.promisor "false" &&
+	git -C client config --unset remote.origin.partialclonefilter &&
 	test_must_fail git -C client cat-file -e "$HASH1" &&
 
 	# But this blob was fetched, because clone performs an initial checkout
@@ -667,7 +683,8 @@ test_expect_success 'partial clone' '
 
 test_expect_success 'partial clone with -o' '
 	partial_clone_server server &&
-	git clone -o blah --filter=blob:limit=0 "file://$(pwd)/server" client
+	git clone -o blah --filter=blob:limit=0 "file://$(pwd)/server" client &&
+	test_cmp_config -C client "blob:limit=0" --get-all remote.blah.partialclonefilter
 '
 
 test_expect_success 'partial clone: warn if server does not support object filtering' '
@@ -702,7 +719,7 @@ test_expect_success 'batch missing blob request during checkout' '
 	# Ensure that there is only one negotiation by checking that there is
 	# only "done" line sent. ("done" marks the end of negotiation.)
 	GIT_TRACE_PACKET="$(pwd)/trace" git -C client checkout HEAD^ &&
-	grep "git> done" trace >done_lines &&
+	grep "fetch> done" trace >done_lines &&
 	test_line_count = 1 done_lines
 '
 
@@ -739,4 +756,7 @@ test_expect_success 'partial clone using HTTP' '
 	partial_clone "$HTTPD_DOCUMENT_ROOT_PATH/server" "$HTTPD_URL/smart/server"
 '
 
+# DO NOT add non-httpd-specific tests here, because the last part of this
+# test script is only executed when httpd is available and enabled.
+
 test_done
diff --git a/third_party/git/t/t5604-clone-reference.sh b/third_party/git/t/t5604-clone-reference.sh
index 4894237ab8..2f7be23044 100755
--- a/third_party/git/t/t5604-clone-reference.sh
+++ b/third_party/git/t/t5604-clone-reference.sh
@@ -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 &&
 	(
@@ -326,15 +326,16 @@ test_expect_success SYMLINKS 'clone repo with symlinked or unknown files at obje
 	for raw in $(ls T*.raw)
 	do
 		sed -e "s!/../!/Y/!; s![0-9a-f]\{38,\}!Z!" -e "/commit-graph/d" \
-		    -e "/multi-pack-index/d" <$raw >$raw.de-sha || return 1
+		    -e "/multi-pack-index/d" <$raw >$raw.de-sha-1 &&
+		sort $raw.de-sha-1 >$raw.de-sha || return 1
 	done &&
 
 	cat >expected-files <<-EOF &&
 	./Y/Z
 	./Y/Z
+	./Y/Z
 	./a-loose-dir/Z
 	./an-object
-	./Y/Z
 	./info/packs
 	./pack/pack-Z.idx
 	./pack/pack-Z.pack
diff --git a/third_party/git/t/t5606-clone-options.sh b/third_party/git/t/t5606-clone-options.sh
index 9e24ec88e6..e69427f881 100755
--- a/third_party/git/t/t5606-clone-options.sh
+++ b/third_party/git/t/t5606-clone-options.sh
@@ -35,4 +35,28 @@ test_expect_success 'redirected clone -v does show progress' '
 
 '
 
+test_expect_success 'chooses correct default initial branch name' '
+	git init --bare empty &&
+	git -c init.defaultBranch=up clone empty whats-up &&
+	test refs/heads/up = $(git -C whats-up symbolic-ref HEAD) &&
+	test refs/heads/up = $(git -C whats-up config branch.up.merge)
+'
+
+test_expect_success 'guesses initial branch name correctly' '
+	git init --initial-branch=guess initial-branch &&
+	test_commit -C initial-branch no-spoilers &&
+	git -C initial-branch branch abc guess &&
+	git clone initial-branch is-it &&
+	test refs/heads/guess = $(git -C is-it symbolic-ref HEAD) &&
+
+	git -c init.defaultBranch=none init --bare no-head &&
+	git -C initial-branch push ../no-head guess abc &&
+	git clone no-head is-it2 &&
+	test_must_fail git -C is-it2 symbolic-ref refs/remotes/origin/HEAD &&
+	git -C no-head update-ref --no-deref HEAD refs/heads/guess &&
+	git -c init.defaultBranch=guess clone no-head is-it3 &&
+	test refs/remotes/origin/guess = \
+		$(git -C is-it3 symbolic-ref refs/remotes/origin/HEAD)
+'
+
 test_done
diff --git a/third_party/git/t/t5607-clone-bundle.sh b/third_party/git/t/t5607-clone-bundle.sh
index 2a0fb15cf1..26985f4b44 100755
--- a/third_party/git/t/t5607-clone-bundle.sh
+++ b/third_party/git/t/t5607-clone-bundle.sh
@@ -4,6 +4,10 @@ test_description='some bundle related tests'
 . ./test-lib.sh
 
 test_expect_success 'setup' '
+	test_oid_cache <<-EOF &&
+	version sha1:2
+	version sha256:3
+	EOF
 	test_commit initial &&
 	test_tick &&
 	git tag -m tag tag &&
@@ -16,7 +20,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
 '
 
@@ -64,7 +68,7 @@ test_expect_success 'ridiculously long subject in boundary' '
 	test -s heads &&
 	git fetch long-subject-bundle.bdl &&
 	sed -n "/^-/{p;q;}" long-subject-bundle.bdl >boundary &&
-	grep "^-[0-9a-f]\\{40\\} " boundary
+	grep "^-$OID_REGEX " boundary
 '
 
 test_expect_success 'prerequisites with an empty commit message' '
@@ -83,4 +87,42 @@ test_expect_success 'failed bundle creation does not leave cruft' '
 	test_path_is_missing fail.bundle.lock
 '
 
+test_expect_success 'fetch SHA-1 from bundle' '
+	test_create_repo foo &&
+	test_commit -C foo x &&
+	git -C foo bundle create tip.bundle -1 master &&
+	git -C foo rev-parse HEAD >hash &&
+
+	# Exercise to ensure that fetching a SHA-1 from a bundle works with no
+	# errors
+	git fetch --no-tags foo/tip.bundle "$(cat hash)"
+'
+
+test_expect_success 'git bundle uses expected default format' '
+	git bundle create bundle HEAD^.. &&
+	head -n1 bundle | grep "^# v$(test_oid version) git bundle$"
+'
+
+test_expect_success 'git bundle v3 has expected contents' '
+	git branch side HEAD &&
+	git bundle create --version=3 bundle HEAD^..side &&
+	head -n2 bundle >actual &&
+	cat >expect <<-EOF &&
+	# v3 git bundle
+	@object-format=$(test_oid algo)
+	EOF
+	test_cmp expect actual &&
+	git bundle verify bundle
+'
+
+test_expect_success 'git bundle v3 rejects unknown capabilities' '
+	cat >new <<-EOF &&
+	# v3 git bundle
+	@object-format=$(test_oid algo)
+	@unknown=silly
+	EOF
+	test_must_fail git bundle verify new 2>output &&
+	test_i18ngrep "unknown capability .unknown=silly." output
+'
+
 test_done
diff --git a/third_party/git/t/t5608-clone-2gb.sh b/third_party/git/t/t5608-clone-2gb.sh
index 2c6bc07344..4c476d2fa1 100755
--- a/third_party/git/t/t5608-clone-2gb.sh
+++ b/third_party/git/t/t5608-clone-2gb.sh
@@ -3,14 +3,13 @@
 test_description='Test cloning a repository larger than 2 gigabyte'
 . ./test-lib.sh
 
-if test -z "$GIT_TEST_CLONE_2GB"
+if ! test_bool_env GIT_TEST_CLONE_2GB false
 then
-	say 'Skipping expensive 2GB clone test; enable it with GIT_TEST_CLONE_2GB=t'
-else
-	test_set_prereq CLONE_2GB
+	skip_all='expensive 2GB clone test; enable with GIT_TEST_CLONE_2GB=true'
+	test_done
 fi
 
-test_expect_success CLONE_2GB 'setup' '
+test_expect_success 'setup' '
 
 	git config pack.compression 0 &&
 	git config pack.depth 0 &&
@@ -38,13 +37,13 @@ test_expect_success CLONE_2GB 'setup' '
 
 '
 
-test_expect_success CLONE_2GB 'clone - bare' '
+test_expect_success 'clone - bare' '
 
 	git clone --bare --no-hardlinks . clone-bare
 
 '
 
-test_expect_success CLONE_2GB 'clone - with worktree, file:// protocol' '
+test_expect_success 'clone - with worktree, file:// protocol' '
 
 	git clone "file://$(pwd)" clone-wt
 
diff --git a/third_party/git/t/t5611-clone-config.sh b/third_party/git/t/t5611-clone-config.sh
index 60c1ba951b..8e0fd39823 100755
--- a/third_party/git/t/t5611-clone-config.sh
+++ b/third_party/git/t/t5611-clone-config.sh
@@ -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
diff --git a/third_party/git/t/t5612-clone-refspec.sh b/third_party/git/t/t5612-clone-refspec.sh
index e36ac01661..e3b436d8ae 100755
--- a/third_party/git/t/t5612-clone-refspec.sh
+++ b/third_party/git/t/t5612-clone-refspec.sh
@@ -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
diff --git a/third_party/git/t/t5616-partial-clone.sh b/third_party/git/t/t5616-partial-clone.sh
index b91ef548f8..f4d49d8335 100755
--- a/third_party/git/t/t5616-partial-clone.sh
+++ b/third_party/git/t/t5616-partial-clone.sh
@@ -42,8 +42,16 @@ test_expect_success 'do partial clone 1' '
 
 	test_cmp expect_1.oids observed.oids &&
 	test "$(git -C pc1 config --local core.repositoryformatversion)" = "1" &&
-	test "$(git -C pc1 config --local extensions.partialclone)" = "origin" &&
-	test "$(git -C pc1 config --local core.partialclonefilter)" = "blob:none"
+	test "$(git -C pc1 config --local remote.origin.promisor)" = "true" &&
+	test "$(git -C pc1 config --local remote.origin.partialclonefilter)" = "blob:none"
+'
+
+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-parse --verify HEAD >headhash &&
+	grep "$(cat headhash) HEAD" $(cat promisorlist) &&
+	grep "$(cat headhash) refs/heads/master" $(cat promisorlist)
 '
 
 # checkout master to force dynamic object fetch of blobs at HEAD.
@@ -155,6 +163,22 @@ test_expect_success 'manual prefetch of missing objects' '
 	test_line_count = 0 observed.oids
 '
 
+test_expect_success 'partial clone with transfer.fsckobjects=1 works with submodules' '
+	test_create_repo submodule &&
+	test_commit -C submodule mycommit &&
+
+	test_create_repo src_with_sub &&
+	test_config -C src_with_sub uploadpack.allowfilter 1 &&
+	test_config -C src_with_sub uploadpack.allowanysha1inwant 1 &&
+
+	git -C src_with_sub submodule add "file://$(pwd)/submodule" mysub &&
+	git -C src_with_sub commit -m "commit with submodule" &&
+
+	git -c transfer.fsckobjects=1 \
+		clone --filter="blob:none" "file://$(pwd)/src_with_sub" dst &&
+	test_when_finished rm -rf dst
+'
+
 test_expect_success 'partial clone with transfer.fsckobjects=1 uses index-pack --fsck-objects' '
 	git init src &&
 	test_commit -C src x &&
@@ -208,6 +232,58 @@ test_expect_success 'use fsck before and after manually fetching a missing subtr
 	test_cmp unique_types.expected unique_types.observed
 '
 
+test_expect_success 'implicitly construct combine: filter with repeated flags' '
+	GIT_TRACE=$(pwd)/trace git clone --bare \
+		--filter=blob:none --filter=tree:1 \
+		"file://$(pwd)/srv.bare" pc2 &&
+	grep "trace:.* git pack-objects .*--filter=combine:blob:none+tree:1" \
+		trace &&
+	git -C pc2 rev-list --objects --missing=allow-any HEAD >objects &&
+
+	# We should have gotten some root trees.
+	grep " $" objects &&
+	# Should not have gotten any non-root trees or blobs.
+	! grep " ." objects &&
+
+	xargs -n 1 git -C pc2 cat-file -t <objects >types &&
+	sort -u types >unique_types.actual &&
+	test_write_lines commit tree >unique_types.expected &&
+	test_cmp unique_types.expected unique_types.actual
+'
+
+test_expect_success 'upload-pack fails banned object filters' '
+	test_config -C srv.bare uploadpackfilter.blob:none.allow false &&
+	test_must_fail ok=sigpipe git clone --no-checkout --filter=blob:none \
+		"file://$(pwd)/srv.bare" pc3 2>err &&
+	test_i18ngrep "filter '\''blob:none'\'' not supported" err
+'
+
+test_expect_success 'upload-pack fails banned combine object filters' '
+	test_config -C srv.bare uploadpackfilter.allow false &&
+	test_config -C srv.bare uploadpackfilter.combine.allow true &&
+	test_config -C srv.bare uploadpackfilter.tree.allow true &&
+	test_config -C srv.bare uploadpackfilter.blob:none.allow false &&
+	test_must_fail ok=sigpipe git clone --no-checkout --filter=tree:1 \
+		--filter=blob:none "file://$(pwd)/srv.bare" pc3 2>err &&
+	test_i18ngrep "filter '\''blob:none'\'' not supported" err
+'
+
+test_expect_success 'upload-pack fails banned object filters with fallback' '
+	test_config -C srv.bare uploadpackfilter.allow false &&
+	test_must_fail ok=sigpipe git clone --no-checkout --filter=blob:none \
+		"file://$(pwd)/srv.bare" pc3 2>err &&
+	test_i18ngrep "filter '\''blob:none'\'' not supported" err
+'
+
+test_expect_success 'upload-pack limits tree depth filters' '
+	test_config -C srv.bare uploadpackfilter.allow false &&
+	test_config -C srv.bare uploadpackfilter.tree.allow true &&
+	test_config -C srv.bare uploadpackfilter.tree.maxDepth 0 &&
+	test_must_fail ok=sigpipe git clone --no-checkout --filter=tree:1 \
+		"file://$(pwd)/srv.bare" pc3 2>err &&
+	test_i18ngrep "tree filter allows max depth 0, but got 1" err
+'
+
 test_expect_success 'partial clone fetches blobs pointed to by refs even if normally filtered out' '
 	rm -rf src dst &&
 	git init src &&
@@ -241,6 +317,218 @@ test_expect_success 'fetch what is specified on CLI even if already promised' '
 	! grep "?$(cat blob)" missing_after
 '
 
+test_expect_success 'setup src repo for sparse filter' '
+	git init sparse-src &&
+	git -C sparse-src config --local uploadpack.allowfilter 1 &&
+	git -C sparse-src config --local uploadpack.allowanysha1inwant 1 &&
+	test_commit -C sparse-src one &&
+	test_commit -C sparse-src two &&
+	echo /one.t >sparse-src/only-one &&
+	git -C sparse-src add . &&
+	git -C sparse-src commit -m "add sparse checkout files"
+'
+
+test_expect_success 'partial clone with sparse filter succeeds' '
+	rm -rf dst.git &&
+	git clone --no-local --bare \
+		  --filter=sparse:oid=master:only-one \
+		  sparse-src dst.git &&
+	(
+		cd dst.git &&
+		git rev-list --objects --missing=print HEAD >out &&
+		grep "^$(git rev-parse HEAD:one.t)" out &&
+		grep "^?$(git rev-parse HEAD:two.t)" out
+	)
+'
+
+test_expect_success 'partial clone with unresolvable sparse filter fails cleanly' '
+	rm -rf dst.git &&
+	test_must_fail git clone --no-local --bare \
+				 --filter=sparse:oid=master:no-such-name \
+				 sparse-src dst.git 2>err &&
+	test_i18ngrep "unable to access sparse blob in .master:no-such-name" err &&
+	test_must_fail git clone --no-local --bare \
+				 --filter=sparse:oid=master \
+				 sparse-src dst.git 2>err &&
+	test_i18ngrep "unable to parse sparse filter data in" err
+'
+
+setup_triangle () {
+	rm -rf big-blob.txt server client promisor-remote &&
+
+	printf "line %d\n" $(test_seq 1 100) >big-blob.txt &&
+
+	# Create a server with 2 commits: a commit with a big tree and a child
+	# commit with an incremental change. Also, create a partial clone
+	# client that only contains the first commit.
+	git init server &&
+	git -C server config --local uploadpack.allowfilter 1 &&
+	for i in $(test_seq 1 100)
+	do
+		echo "make the tree big" >server/file$i &&
+		git -C server add file$i
+	done &&
+	git -C server commit -m "initial" &&
+	git clone --bare --filter=tree:0 "file://$(pwd)/server" client &&
+	echo another line >>server/file1 &&
+	git -C server commit -am "incremental change" &&
+
+	# Create a promisor remote that only contains the tree and blob from
+	# the first commit.
+	git init promisor-remote &&
+	git -C server config --local uploadpack.allowanysha1inwant 1 &&
+	TREE_HASH=$(git -C server rev-parse HEAD~1^{tree}) &&
+	git -C promisor-remote fetch --keep "file://$(pwd)/server" "$TREE_HASH" &&
+	git -C promisor-remote count-objects -v >object-count &&
+	test_i18ngrep "count: 0" object-count &&
+	test_i18ngrep "in-pack: 2" object-count &&
+
+	# Set it as the promisor remote of client. Thus, whenever
+	# the client lazy fetches, the lazy fetch will succeed only if it is
+	# for this tree or blob.
+	test_commit -C promisor-remote one && # so that ref advertisement is not empty
+	git -C promisor-remote config --local uploadpack.allowanysha1inwant 1 &&
+	git -C client remote set-url origin "file://$(pwd)/promisor-remote"
+}
+
+# NEEDSWORK: The tests beginning with "fetch lazy-fetches" below only
+# test that "fetch" avoid fetching trees and blobs, but not commits or
+# tags. Revisit this if Git is ever taught to support partial clones
+# with commits and/or tags filtered out.
+
+test_expect_success 'fetch lazy-fetches only to resolve deltas' '
+	setup_triangle &&
+
+	# Exercise to make sure it works. Git will not fetch anything from the
+	# promisor remote other than for the big tree (because it needs to
+	# resolve the delta).
+	GIT_TRACE_PACKET="$(pwd)/trace" git -C client \
+		fetch "file://$(pwd)/server" master &&
+
+	# Verify the assumption that the client needed to fetch the delta base
+	# to resolve the delta.
+	git -C server rev-parse HEAD~1^{tree} >hash &&
+	grep "want $(cat hash)" trace
+'
+
+test_expect_success 'fetch lazy-fetches only to resolve deltas, protocol v2' '
+	setup_triangle &&
+
+	git -C server config --local protocol.version 2 &&
+	git -C client config --local protocol.version 2 &&
+	git -C promisor-remote config --local protocol.version 2 &&
+
+	# Exercise to make sure it works. Git will not fetch anything from the
+	# promisor remote other than for the big blob (because it needs to
+	# resolve the delta).
+	GIT_TRACE_PACKET="$(pwd)/trace" git -C client \
+		fetch "file://$(pwd)/server" master &&
+
+	# Verify that protocol version 2 was used.
+	grep "fetch< version 2" trace &&
+
+	# Verify the assumption that the client needed to fetch the delta base
+	# to resolve the delta.
+	git -C server rev-parse HEAD~1^{tree} >hash &&
+	grep "want $(cat hash)" trace
+'
+
+test_expect_success 'fetch does not lazy-fetch missing targets of its refs' '
+	rm -rf server client trace &&
+
+	test_create_repo server &&
+	test_config -C server uploadpack.allowfilter 1 &&
+	test_config -C server uploadpack.allowanysha1inwant 1 &&
+	test_commit -C server foo &&
+
+	git clone --filter=blob:none "file://$(pwd)/server" client &&
+	# Make all refs point to nothing by deleting all objects.
+	rm client/.git/objects/pack/* &&
+
+	test_commit -C server bar &&
+	GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch \
+		--no-tags --recurse-submodules=no \
+		origin refs/tags/bar &&
+	FOO_HASH=$(git -C server rev-parse foo) &&
+	! grep "want $FOO_HASH" trace
+'
+
+# The following two tests must be in this order. It is important that
+# the srv.bare repository did not have tags during clone, but has tags
+# in the fetch.
+
+test_expect_success 'verify fetch succeeds when asking for new tags' '
+	git clone --filter=blob:none "file://$(pwd)/srv.bare" tag-test &&
+	for i in I J K
+	do
+		test_commit -C src $i &&
+		git -C src branch $i || return 1
+	done &&
+	git -C srv.bare fetch --tags origin +refs/heads/*:refs/heads/* &&
+	git -C tag-test -c protocol.version=2 fetch --tags origin
+'
+
+test_expect_success 'verify fetch downloads only one pack when updating refs' '
+	git clone --filter=blob:none "file://$(pwd)/srv.bare" pack-test &&
+	ls pack-test/.git/objects/pack/*pack >pack-list &&
+	test_line_count = 2 pack-list &&
+	for i in A B C
+	do
+		test_commit -C src $i &&
+		git -C src branch $i || return 1
+	done &&
+	git -C srv.bare fetch origin +refs/heads/*:refs/heads/* &&
+	git -C pack-test fetch origin &&
+	ls pack-test/.git/objects/pack/*pack >pack-list &&
+	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_expect_success 'fetch from a partial clone, protocol v0' '
+	rm -rf server client trace &&
+
+	# Pretend that the server is a partial clone
+	git init server &&
+	git -C server remote add a_remote "file://$(pwd)/" &&
+	test_config -C server core.repositoryformatversion 1 &&
+	test_config -C server extensions.partialclone a_remote &&
+	test_config -C server protocol.version 0 &&
+	test_commit -C server foo &&
+
+	# Fetch from the server
+	git init client &&
+	test_config -C client protocol.version 0 &&
+	test_commit -C client bar &&
+	GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch "file://$(pwd)/server" &&
+	! grep "version 2" trace
+'
+
+test_expect_success 'fetch from a partial clone, protocol v2' '
+	rm -rf server client trace &&
+
+	# Pretend that the server is a partial clone
+	git init server &&
+	git -C server remote add a_remote "file://$(pwd)/" &&
+	test_config -C server core.repositoryformatversion 1 &&
+	test_config -C server extensions.partialclone a_remote &&
+	test_config -C server protocol.version 2 &&
+	test_commit -C server foo &&
+
+	# Fetch from the server
+	git init client &&
+	test_config -C client protocol.version 2 &&
+	test_commit -C client bar &&
+	GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch "file://$(pwd)/server" &&
+	grep "version 2" trace
+'
+
 . "$TEST_DIRECTORY"/lib-httpd.sh
 start_httpd
 
@@ -255,14 +543,18 @@ intersperse () {
 	sed 's/\(..\)/'$1'\1/g'
 }
 
-# Create a one-time-sed command to replace the existing packfile with $1.
+# Create a one-time-perl command to replace the existing packfile with $1.
 replace_packfile () {
 	# The protocol requires that the packfile be sent in sideband 1, hence
 	# the extra \x01 byte at the beginning.
-	printf "1,/packfile/!c %04x\\\\x01%s0000" \
-		"$(($(wc -c <$1) + 5))" \
-		"$(hex_unpack <$1 | intersperse '\\x')" \
-		>"$HTTPD_ROOT_PATH/one-time-sed"
+	cp $1 "$HTTPD_ROOT_PATH/one-time-pack" &&
+	echo 'if (/packfile/) {
+		print;
+		my $length = -s "one-time-pack";
+		printf "%04x\x01", $length + 5;
+		print `cat one-time-pack` . "0000";
+		last
+	}' >"$HTTPD_ROOT_PATH/one-time-perl"
 }
 
 test_expect_success 'upon cloning, check that all refs point to objects' '
@@ -286,16 +578,16 @@ test_expect_success 'upon cloning, check that all refs point to objects' '
 	# \x01 byte at the beginning.
 	replace_packfile incomplete.pack &&
 
-	# Use protocol v2 because the sed command looks for the "packfile"
+	# Use protocol v2 because the perl command looks for the "packfile"
 	# section header.
 	test_config -C "$SERVER" protocol.version 2 &&
 	test_must_fail git -c protocol.version=2 clone \
-		--filter=blob:none $HTTPD_URL/one_time_sed/server repo 2>err &&
+		--filter=blob:none $HTTPD_URL/one_time_perl/server repo 2>err &&
 
 	test_i18ngrep "did not send all necessary objects" err &&
 
-	# Ensure that the one-time-sed script was used.
-	! test -e "$HTTPD_ROOT_PATH/one-time-sed"
+	# Ensure that the one-time-perl script was used.
+	! test -e "$HTTPD_ROOT_PATH/one-time-perl"
 '
 
 test_expect_success 'when partial cloning, tolerate server not sending target of tag' '
@@ -326,17 +618,17 @@ test_expect_success 'when partial cloning, tolerate server not sending target of
 	# \x01 byte at the beginning.
 	replace_packfile incomplete.pack &&
 
-	# Use protocol v2 because the sed command looks for the "packfile"
+	# Use protocol v2 because the perl command looks for the "packfile"
 	# section header.
 	test_config -C "$SERVER" protocol.version 2 &&
 
 	# Exercise to make sure it works.
 	git -c protocol.version=2 clone \
-		--filter=blob:none $HTTPD_URL/one_time_sed/server repo 2> err &&
+		--filter=blob:none $HTTPD_URL/one_time_perl/server repo 2> err &&
 	! grep "missing object referenced by" err &&
 
-	# Ensure that the one-time-sed script was used.
-	! test -e "$HTTPD_ROOT_PATH/one-time-sed"
+	# Ensure that the one-time-perl script was used.
+	! test -e "$HTTPD_ROOT_PATH/one-time-perl"
 '
 
 test_expect_success 'tolerate server sending REF_DELTA against missing promisor objects' '
@@ -359,7 +651,7 @@ test_expect_success 'tolerate server sending REF_DELTA against missing promisor
 
 	# Clone. The client has deltabase_have but not deltabase_missing.
 	git -c protocol.version=2 clone --no-checkout \
-		--filter=blob:none $HTTPD_URL/one_time_sed/server repo &&
+		--filter=blob:none $HTTPD_URL/one_time_perl/server repo &&
 	git -C repo hash-object -w -- "$SERVER/have.txt" &&
 
 	# Sanity check to ensure that the client does not have
@@ -400,7 +692,7 @@ test_expect_success 'tolerate server sending REF_DELTA against missing promisor
 
 	replace_packfile thin.pack &&
 
-	# Use protocol v2 because the sed command looks for the "packfile"
+	# Use protocol v2 because the perl command looks for the "packfile"
 	# section header.
 	test_config -C "$SERVER" protocol.version 2 &&
 
@@ -413,8 +705,11 @@ test_expect_success 'tolerate server sending REF_DELTA against missing promisor
 	grep "want $(cat deltabase_missing)" trace &&
 	! grep "want $(cat deltabase_have)" trace &&
 
-	# Ensure that the one-time-sed script was used.
-	! test -e "$HTTPD_ROOT_PATH/one-time-sed"
+	# Ensure that the one-time-perl script was used.
+	! test -e "$HTTPD_ROOT_PATH/one-time-perl"
 '
 
+# DO NOT add non-httpd-specific tests here, because the last part of this
+# test script is only executed when httpd is available and enabled.
+
 test_done
diff --git a/third_party/git/t/t5617-clone-submodules-remote.sh b/third_party/git/t/t5617-clone-submodules-remote.sh
index 37fcce9c40..1a041df10b 100755
--- a/third_party/git/t/t5617-clone-submodules-remote.sh
+++ b/third_party/git/t/t5617-clone-submodules-remote.sh
@@ -14,7 +14,8 @@ test_expect_success 'setup' '
 		cd sub &&
 		git init &&
 		test_commit subcommit1 &&
-		git tag sub_when_added_to_super
+		git tag sub_when_added_to_super &&
+		git branch other
 	) &&
 	git submodule add "file://$pwd/sub" sub &&
 	git commit -m "add submodule" &&
@@ -51,4 +52,14 @@ test_expect_success 'check the default is --no-remote-submodules' '
 	)
 '
 
+test_expect_success 'clone with --single-branch' '
+	test_when_finished "rm -rf super_clone" &&
+	git clone --recurse-submodules --single-branch "file://$pwd/." super_clone &&
+	(
+		cd super_clone/sub &&
+		git rev-parse --verify origin/master &&
+		test_must_fail git rev-parse --verify origin/other
+	)
+'
+
 test_done
diff --git a/third_party/git/t/t5700-protocol-v1.sh b/third_party/git/t/t5700-protocol-v1.sh
index 7c9511c593..022901b9eb 100755
--- a/third_party/git/t/t5700-protocol-v1.sh
+++ b/third_party/git/t/t5700-protocol-v1.sh
@@ -5,7 +5,8 @@ test_description='test git wire-protocol transition'
 TEST_NO_CREATE_REPO=1
 
 # This is a protocol-specific test.
-GIT_TEST_PROTOCOL_VERSION=
+GIT_TEST_PROTOCOL_VERSION=0
+export GIT_TEST_PROTOCOL_VERSION
 
 . ./test-lib.sh
 
@@ -292,4 +293,7 @@ test_expect_success 'push with http:// using protocol v1' '
 	grep "git< version 1" log
 '
 
+# DO NOT add non-httpd-specific tests here, because the last part of this
+# test script is only executed when httpd is available and enabled.
+
 test_done
diff --git a/third_party/git/t/t5701-git-serve.sh b/third_party/git/t/t5701-git-serve.sh
index ffb9613885..a1f5fdc9fd 100755
--- a/third_party/git/t/t5701-git-serve.sh
+++ b/third_party/git/t/t5701-git-serve.sh
@@ -5,12 +5,17 @@ test_description='test protocol v2 server commands'
 . ./test-lib.sh
 
 test_expect_success 'test capability advertisement' '
+	test_oid_cache <<-EOF &&
+	wrong_algo sha1:sha256
+	wrong_algo sha256:sha1
+	EOF
 	cat >expect <<-EOF &&
 	version 2
 	agent=git/$(git version | cut -d" " -f3)
 	ls-refs
 	fetch=shallow
 	server-option
+	object-format=$(test_oid algo)
 	0000
 	EOF
 
@@ -45,6 +50,7 @@ test_expect_success 'request invalid capability' '
 test_expect_success 'request with no command' '
 	test-tool pkt-line pack >in <<-EOF &&
 	agent=git/test
+	object-format=$(test_oid algo)
 	0000
 	EOF
 	test_must_fail test-tool serve-v2 --stateless-rpc 2>err <in &&
@@ -54,6 +60,7 @@ test_expect_success 'request with no command' '
 test_expect_success 'request invalid command' '
 	test-tool pkt-line pack >in <<-EOF &&
 	command=foo
+	object-format=$(test_oid algo)
 	agent=git/test
 	0000
 	EOF
@@ -61,6 +68,17 @@ test_expect_success 'request invalid command' '
 	test_i18ngrep "invalid command" err
 '
 
+test_expect_success 'wrong object-format' '
+	test-tool pkt-line pack >in <<-EOF &&
+	command=fetch
+	agent=git/test
+	object-format=$(test_oid wrong_algo)
+	0000
+	EOF
+	test_must_fail test-tool serve-v2 --stateless-rpc 2>err <in &&
+	test_i18ngrep "mismatched object format" err
+'
+
 # Test the basics of ls-refs
 #
 test_expect_success 'setup some refs and tags' '
@@ -74,6 +92,7 @@ test_expect_success 'setup some refs and tags' '
 test_expect_success 'basics of ls-refs' '
 	test-tool pkt-line pack >in <<-EOF &&
 	command=ls-refs
+	object-format=$(test_oid algo)
 	0000
 	EOF
 
@@ -96,6 +115,7 @@ test_expect_success 'basics of ls-refs' '
 test_expect_success 'basic ref-prefixes' '
 	test-tool pkt-line pack >in <<-EOF &&
 	command=ls-refs
+	object-format=$(test_oid algo)
 	0001
 	ref-prefix refs/heads/master
 	ref-prefix refs/tags/one
@@ -116,6 +136,7 @@ test_expect_success 'basic ref-prefixes' '
 test_expect_success 'refs/heads prefix' '
 	test-tool pkt-line pack >in <<-EOF &&
 	command=ls-refs
+	object-format=$(test_oid algo)
 	0001
 	ref-prefix refs/heads/
 	0000
@@ -136,6 +157,7 @@ test_expect_success 'refs/heads prefix' '
 test_expect_success 'peel parameter' '
 	test-tool pkt-line pack >in <<-EOF &&
 	command=ls-refs
+	object-format=$(test_oid algo)
 	0001
 	peel
 	ref-prefix refs/tags/
@@ -157,6 +179,7 @@ test_expect_success 'peel parameter' '
 test_expect_success 'symrefs parameter' '
 	test-tool pkt-line pack >in <<-EOF &&
 	command=ls-refs
+	object-format=$(test_oid algo)
 	0001
 	symrefs
 	ref-prefix refs/heads/
@@ -178,6 +201,7 @@ test_expect_success 'symrefs parameter' '
 test_expect_success 'sending server-options' '
 	test-tool pkt-line pack >in <<-EOF &&
 	command=ls-refs
+	object-format=$(test_oid algo)
 	server-option=hello
 	server-option=world
 	0001
@@ -200,6 +224,7 @@ test_expect_success 'unexpected lines are not allowed in fetch request' '
 
 	test-tool pkt-line pack >in <<-EOF &&
 	command=fetch
+	object-format=$(test_oid algo)
 	0001
 	this-is-not-a-command
 	0000
diff --git a/third_party/git/t/t5702-protocol-v2.sh b/third_party/git/t/t5702-protocol-v2.sh
index 011b81d4fc..7d5b17909b 100755
--- a/third_party/git/t/t5702-protocol-v2.sh
+++ b/third_party/git/t/t5702-protocol-v2.sh
@@ -32,7 +32,7 @@ test_expect_success 'list refs with git:// using protocol v2' '
 	test_cmp expect actual
 '
 
-test_expect_success 'ref advertisment is filtered with ls-remote using protocol v2' '
+test_expect_success 'ref advertisement is filtered with ls-remote using protocol v2' '
 	test_when_finished "rm -f log" &&
 
 	GIT_TRACE_PACKET="$(pwd)/log" git -c protocol.version=2 \
@@ -154,7 +154,7 @@ test_expect_success 'list refs with file:// using protocol v2' '
 	test_cmp expect actual
 '
 
-test_expect_success 'ref advertisment is filtered with ls-remote using protocol v2' '
+test_expect_success 'ref advertisement is filtered with ls-remote using protocol v2' '
 	test_when_finished "rm -f log" &&
 
 	GIT_TRACE_PACKET="$(pwd)/log" git -c protocol.version=2 \
@@ -225,7 +225,7 @@ test_expect_success 'fetch with file:// using protocol v2' '
 	grep "fetch< version 2" log
 '
 
-test_expect_success 'ref advertisment is filtered during fetch using protocol v2' '
+test_expect_success 'ref advertisement is filtered during fetch using protocol v2' '
 	test_when_finished "rm -f log" &&
 
 	test_commit -C file_parent three &&
@@ -348,7 +348,6 @@ test_expect_success 'partial fetch' '
 	rm -rf client "$(pwd)/trace" &&
 	git init client &&
 	SERVER="file://$(pwd)/server" &&
-	test_config -C client extensions.partialClone "$SERVER" &&
 
 	GIT_TRACE_PACKET="$(pwd)/trace" git -C client -c protocol.version=2 \
 		fetch --filter=blob:none "$SERVER" master:refs/heads/other &&
@@ -394,6 +393,7 @@ test_expect_success 'even with handcrafted request, filter does not work if not
 	# Custom request that tries to filter even though it is not advertised.
 	test-tool pkt-line pack >in <<-EOF &&
 	command=fetch
+	object-format=$(test_oid algo)
 	0001
 	want $(git -C server rev-parse master)
 	filter blob:none
@@ -586,6 +586,53 @@ test_expect_success 'clone with http:// using protocol v2' '
 	! grep "Send header: Transfer-Encoding: chunked" log
 '
 
+test_expect_success 'clone repository with http:// using protocol v2 with incomplete pktline length' '
+	test_when_finished "rm -f log" &&
+
+	git init "$HTTPD_DOCUMENT_ROOT_PATH/incomplete_length" &&
+	test_commit -C "$HTTPD_DOCUMENT_ROOT_PATH/incomplete_length" file &&
+
+	test_must_fail env GIT_TRACE_PACKET="$(pwd)/log" GIT_TRACE_CURL="$(pwd)/log" git -c protocol.version=2 \
+		clone "$HTTPD_URL/smart/incomplete_length" incomplete_length_child 2>err &&
+
+	# Client requested to use protocol v2
+	grep "Git-Protocol: version=2" log &&
+	# Server responded using protocol v2
+	grep "git< version 2" log &&
+	# Client reported appropriate failure
+	test_i18ngrep "bytes of length header were received" err
+'
+
+test_expect_success 'clone repository with http:// using protocol v2 with incomplete pktline body' '
+	test_when_finished "rm -f log" &&
+
+	git init "$HTTPD_DOCUMENT_ROOT_PATH/incomplete_body" &&
+	test_commit -C "$HTTPD_DOCUMENT_ROOT_PATH/incomplete_body" file &&
+
+	test_must_fail env GIT_TRACE_PACKET="$(pwd)/log" GIT_TRACE_CURL="$(pwd)/log" git -c protocol.version=2 \
+		clone "$HTTPD_URL/smart/incomplete_body" incomplete_body_child 2>err &&
+
+	# Client requested to use protocol v2
+	grep "Git-Protocol: version=2" log &&
+	# Server responded using protocol v2
+	grep "git< version 2" log &&
+	# Client reported appropriate failure
+	test_i18ngrep "bytes of body are still expected" err
+'
+
+test_expect_success 'clone with http:// using protocol v2 and invalid parameters' '
+	test_when_finished "rm -f log" &&
+
+	test_must_fail env GIT_TRACE_PACKET="$(pwd)/log" GIT_TRACE_CURL="$(pwd)/log" \
+		git -c protocol.version=2 \
+		clone --shallow-since=20151012 "$HTTPD_URL/smart/http_parent" http_child_invalid &&
+
+	# Client requested to use protocol v2
+	grep "Git-Protocol: version=2" log &&
+	# Server responded using protocol v2
+	grep "git< version 2" log
+'
+
 test_expect_success 'clone big repository with http:// using protocol v2' '
 	test_when_finished "rm -f log" &&
 
@@ -631,6 +678,19 @@ test_expect_success 'fetch with http:// using protocol v2' '
 	grep "git< version 2" log
 '
 
+test_expect_success 'fetch with http:// by hash without tag following with protocol v2 does not list refs' '
+	test_when_finished "rm -f log" &&
+
+	test_commit -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" two_a &&
+	git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" rev-parse two_a >two_a_hash &&
+
+	GIT_TRACE_PACKET="$(pwd)/log" git -C http_child -c protocol.version=2 \
+		fetch --no-tags origin $(cat two_a_hash) &&
+
+	grep "fetch< version 2" log &&
+	! grep "fetch> command=ls-refs" log
+'
+
 test_expect_success 'fetch from namespaced repo respects namespaces' '
 	test_when_finished "rm -f log" &&
 
@@ -652,6 +712,18 @@ test_expect_success 'fetch from namespaced repo respects namespaces' '
 	test_cmp expect actual
 '
 
+test_expect_success 'ls-remote with v2 http sends only one POST' '
+	test_when_finished "rm -f log" &&
+
+	git ls-remote "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" >expect &&
+	GIT_TRACE_CURL="$(pwd)/log" git -c protocol.version=2 \
+		ls-remote "$HTTPD_URL/smart/http_parent" >actual &&
+	test_cmp expect actual &&
+
+	grep "Send header: POST" log >posts &&
+	test_line_count = 1 posts
+'
+
 test_expect_success 'push with http:// and a config of v2 does not request v2' '
 	test_when_finished "rm -f log" &&
 	# Till v2 for push is designed, make sure that if a client has
@@ -669,9 +741,9 @@ test_expect_success 'push with http:// and a config of v2 does not request v2' '
 	git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" log -1 --format=%s client_branch >expect &&
 	test_cmp expect actual &&
 
-	# Client didnt request to use protocol v2
+	# Client did not request to use protocol v2
 	! grep "Git-Protocol: version=2" log &&
-	# Server didnt respond using protocol v2
+	# Server did not respond using protocol v2
 	! grep "git< version 2" log
 '
 
@@ -687,11 +759,11 @@ test_expect_success 'when server sends "ready", expect DELIM' '
 
 	# After "ready" in the acknowledgments section, pretend that a FLUSH
 	# (0000) was sent instead of a DELIM (0001).
-	printf "/ready/,$ s/0001/0000/" \
-		>"$HTTPD_ROOT_PATH/one-time-sed" &&
+	printf "\$ready = 1 if /ready/; \$ready && s/0001/0000/" \
+		>"$HTTPD_ROOT_PATH/one-time-perl" &&
 
 	test_must_fail git -C http_child -c protocol.version=2 \
-		fetch "$HTTPD_URL/one_time_sed/http_parent" 2> err &&
+		fetch "$HTTPD_URL/one_time_perl/http_parent" 2> err &&
 	test_i18ngrep "expected packfile to be sent after .ready." err
 '
 
@@ -712,15 +784,159 @@ test_expect_success 'when server does not send "ready", expect FLUSH' '
 
 	# After the acknowledgments section, pretend that a DELIM
 	# (0001) was sent instead of a FLUSH (0000).
-	printf "/acknowledgments/,$ s/0000/0001/" \
-		>"$HTTPD_ROOT_PATH/one-time-sed" &&
+	printf "\$ack = 1 if /acknowledgments/; \$ack && s/0000/0001/" \
+		>"$HTTPD_ROOT_PATH/one-time-perl" &&
 
 	test_must_fail env GIT_TRACE_PACKET="$(pwd)/log" git -C http_child \
 		-c protocol.version=2 \
-		fetch "$HTTPD_URL/one_time_sed/http_parent" 2> err &&
+		fetch "$HTTPD_URL/one_time_perl/http_parent" 2> err &&
 	grep "fetch< .*acknowledgments" log &&
 	! grep "fetch< .*ready" log &&
 	test_i18ngrep "expected no other sections to be sent after no .ready." err
 '
 
+configure_exclusion () {
+	git -C "$1" hash-object "$2" >objh &&
+	git -C "$1" pack-objects "$HTTPD_DOCUMENT_ROOT_PATH/mypack" <objh >packh &&
+	git -C "$1" config --add \
+		"uploadpack.blobpackfileuri" \
+		"$(cat objh) $(cat packh) $HTTPD_URL/dumb/mypack-$(cat packh).pack" &&
+	cat objh
+}
+
+test_expect_success 'part of packfile response provided as URI' '
+	P="$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+	rm -rf "$P" http_child log &&
+
+	git init "$P" &&
+	git -C "$P" config "uploadpack.allowsidebandall" "true" &&
+
+	echo my-blob >"$P/my-blob" &&
+	git -C "$P" add my-blob &&
+	echo other-blob >"$P/other-blob" &&
+	git -C "$P" add other-blob &&
+	git -C "$P" commit -m x &&
+
+	configure_exclusion "$P" my-blob >h &&
+	configure_exclusion "$P" other-blob >h2 &&
+
+	GIT_TRACE=1 GIT_TRACE_PACKET="$(pwd)/log" GIT_TEST_SIDEBAND_ALL=1 \
+	git -c protocol.version=2 \
+		-c fetch.uriprotocols=http,https \
+		clone "$HTTPD_URL/smart/http_parent" http_child &&
+
+	# Ensure that my-blob and other-blob are in separate packfiles.
+	for idx in http_child/.git/objects/pack/*.idx
+	do
+		git verify-pack --object-format=$(test_oid algo) --verbose $idx >out &&
+		{
+			grep "^[0-9a-f]\{16,\} " out || :
+		} >out.objectlist &&
+		if test_line_count = 1 out.objectlist
+		then
+			if grep $(cat h) out
+			then
+				>hfound
+			fi &&
+			if grep $(cat h2) out
+			then
+				>h2found
+			fi
+		fi
+	done &&
+	test -f hfound &&
+	test -f h2found &&
+
+	# Ensure that there are exactly 6 files (3 .pack and 3 .idx).
+	ls http_child/.git/objects/pack/* >filelist &&
+	test_line_count = 6 filelist
+'
+
+test_expect_success 'fetching with valid packfile URI but invalid hash fails' '
+	P="$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+	rm -rf "$P" http_child log &&
+
+	git init "$P" &&
+	git -C "$P" config "uploadpack.allowsidebandall" "true" &&
+
+	echo my-blob >"$P/my-blob" &&
+	git -C "$P" add my-blob &&
+	echo other-blob >"$P/other-blob" &&
+	git -C "$P" add other-blob &&
+	git -C "$P" commit -m x &&
+
+	configure_exclusion "$P" my-blob >h &&
+	# Configure a URL for other-blob. Just reuse the hash of the object as
+	# the hash of the packfile, since the hash does not matter for this
+	# test as long as it is not the hash of the pack, and it is of the
+	# expected length.
+	git -C "$P" hash-object other-blob >objh &&
+	git -C "$P" pack-objects "$HTTPD_DOCUMENT_ROOT_PATH/mypack" <objh >packh &&
+	git -C "$P" config --add \
+		"uploadpack.blobpackfileuri" \
+		"$(cat objh) $(cat objh) $HTTPD_URL/dumb/mypack-$(cat packh).pack" &&
+
+	test_must_fail env GIT_TEST_SIDEBAND_ALL=1 \
+		git -c protocol.version=2 \
+		-c fetch.uriprotocols=http,https \
+		clone "$HTTPD_URL/smart/http_parent" http_child 2>err &&
+	test_i18ngrep "pack downloaded from.*does not match expected hash" err
+'
+
+test_expect_success 'packfile-uri with transfer.fsckobjects' '
+	P="$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+	rm -rf "$P" http_child log &&
+
+	git init "$P" &&
+	git -C "$P" config "uploadpack.allowsidebandall" "true" &&
+
+	echo my-blob >"$P/my-blob" &&
+	git -C "$P" add my-blob &&
+	git -C "$P" commit -m x &&
+
+	configure_exclusion "$P" my-blob >h &&
+
+	sane_unset GIT_TEST_SIDEBAND_ALL &&
+	git -c protocol.version=2 -c transfer.fsckobjects=1 \
+		-c fetch.uriprotocols=http,https \
+		clone "$HTTPD_URL/smart/http_parent" http_child &&
+
+	# Ensure that there are exactly 4 files (2 .pack and 2 .idx).
+	ls http_child/.git/objects/pack/* >filelist &&
+	test_line_count = 4 filelist
+'
+
+test_expect_success 'packfile-uri with transfer.fsckobjects fails on bad object' '
+	P="$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+	rm -rf "$P" http_child log &&
+
+	git init "$P" &&
+	git -C "$P" config "uploadpack.allowsidebandall" "true" &&
+
+	cat >bogus-commit <<-EOF &&
+	tree $EMPTY_TREE
+	author Bugs Bunny 1234567890 +0000
+	committer Bugs Bunny <bugs@bun.ni> 1234567890 +0000
+
+	This commit object intentionally broken
+	EOF
+	BOGUS=$(git -C "$P" hash-object -t commit -w --stdin <bogus-commit) &&
+	git -C "$P" branch bogus-branch "$BOGUS" &&
+
+	echo my-blob >"$P/my-blob" &&
+	git -C "$P" add my-blob &&
+	git -C "$P" commit -m x &&
+
+	configure_exclusion "$P" my-blob >h &&
+
+	sane_unset GIT_TEST_SIDEBAND_ALL &&
+	test_must_fail git -c protocol.version=2 -c transfer.fsckobjects=1 \
+		-c fetch.uriprotocols=http,https \
+		clone "$HTTPD_URL/smart/http_parent" http_child 2>error &&
+	test_i18ngrep "invalid author/committer line - missing email" error
+'
+
+# DO NOT add non-httpd-specific tests here, because the last part of this
+# test script is only executed when httpd is available and enabled.
+
 test_done
diff --git a/third_party/git/t/t5703-upload-pack-ref-in-want.sh b/third_party/git/t/t5703-upload-pack-ref-in-want.sh
index de4b6106ef..d9ecf0f4a9 100755
--- a/third_party/git/t/t5703-upload-pack-ref-in-want.sh
+++ b/third_party/git/t/t5703-upload-pack-ref-in-want.sh
@@ -13,19 +13,27 @@ 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 | grep commit | cut -c-40 | sort >actual_commits
+	git verify-pack -v o.idx >objs &&
+	grep commit objs | cut -d" " -f1 | sort >actual_commits
 }
 
 check_output () {
 	get_actual_refs &&
 	test_cmp expected_refs actual_refs &&
 	get_actual_commits &&
-	test_cmp expected_commits actual_commits
+	sort expected_commits >sorted_commits &&
+	test_cmp sorted_commits actual_commits
+}
+
+write_command () {
+	echo "command=$1"
+
+	if test "$(test_oid algo)" != sha1
+	then
+		echo "object-format=$(test_oid algo)"
+	fi
 }
 
 # c(o/foo) d(o/bar)
@@ -49,20 +57,23 @@ test_expect_success 'setup repository' '
 
 test_expect_success 'config controls ref-in-want advertisement' '
 	test-tool serve-v2 --advertise-capabilities >out &&
-	! grep -a ref-in-want out &&
+	perl -ne "/ref-in-want/ and print" out >out.filter &&
+	test_must_be_empty out.filter &&
 
 	git config uploadpack.allowRefInWant false &&
 	test-tool serve-v2 --advertise-capabilities >out &&
-	! grep -a ref-in-want out &&
+	perl -ne "/ref-in-want/ and print" out >out.filter &&
+	test_must_be_empty out.filter &&
 
 	git config uploadpack.allowRefInWant true &&
 	test-tool serve-v2 --advertise-capabilities >out &&
-	grep -a ref-in-want out
+	perl -ne "/ref-in-want/ and print" out >out.filter &&
+	test_file_not_empty out.filter
 '
 
 test_expect_success 'invalid want-ref line' '
 	test-tool pkt-line pack >in <<-EOF &&
-	command=fetch
+	$(write_command fetch)
 	0001
 	no-progress
 	want-ref refs/heads/non-existent
@@ -75,17 +86,19 @@ test_expect_success 'invalid want-ref line' '
 '
 
 test_expect_success 'basic want-ref' '
+	oid=$(git rev-parse f) &&
 	cat >expected_refs <<-EOF &&
-	$(git rev-parse f) refs/heads/master
+	$oid refs/heads/master
 	EOF
-	git rev-parse f | sort >expected_commits &&
+	git rev-parse f >expected_commits &&
 
+	oid=$(git rev-parse a) &&
 	test-tool pkt-line pack >in <<-EOF &&
-	command=fetch
+	$(write_command fetch)
 	0001
 	no-progress
 	want-ref refs/heads/master
-	have $(git rev-parse a)
+	have $oid
 	done
 	0000
 	EOF
@@ -95,19 +108,22 @@ test_expect_success 'basic want-ref' '
 '
 
 test_expect_success 'multiple want-ref lines' '
+	oid_c=$(git rev-parse c) &&
+	oid_d=$(git rev-parse d) &&
 	cat >expected_refs <<-EOF &&
-	$(git rev-parse c) refs/heads/o/foo
-	$(git rev-parse d) refs/heads/o/bar
+	$oid_c refs/heads/o/foo
+	$oid_d refs/heads/o/bar
 	EOF
-	git rev-parse c d | sort >expected_commits &&
+	git rev-parse c d >expected_commits &&
 
+	oid=$(git rev-parse b) &&
 	test-tool pkt-line pack >in <<-EOF &&
-	command=fetch
+	$(write_command fetch)
 	0001
 	no-progress
 	want-ref refs/heads/o/foo
 	want-ref refs/heads/o/bar
-	have $(git rev-parse b)
+	have $oid
 	done
 	0000
 	EOF
@@ -117,13 +133,14 @@ test_expect_success 'multiple want-ref lines' '
 '
 
 test_expect_success 'mix want and want-ref' '
+	oid=$(git rev-parse f) &&
 	cat >expected_refs <<-EOF &&
-	$(git rev-parse f) refs/heads/master
+	$oid refs/heads/master
 	EOF
-	git rev-parse e f | sort >expected_commits &&
+	git rev-parse e f >expected_commits &&
 
 	test-tool pkt-line pack >in <<-EOF &&
-	command=fetch
+	$(write_command fetch)
 	0001
 	no-progress
 	want-ref refs/heads/master
@@ -138,17 +155,19 @@ test_expect_success 'mix want and want-ref' '
 '
 
 test_expect_success 'want-ref with ref we already have commit for' '
+	oid=$(git rev-parse c) &&
 	cat >expected_refs <<-EOF &&
-	$(git rev-parse c) refs/heads/o/foo
+	$oid refs/heads/o/foo
 	EOF
 	>expected_commits &&
 
+	oid=$(git rev-parse c) &&
 	test-tool pkt-line pack >in <<-EOF &&
-	command=fetch
+	$(write_command fetch)
 	0001
 	no-progress
 	want-ref refs/heads/o/foo
-	have $(git rev-parse c)
+	have $oid
 	done
 	0000
 	EOF
@@ -157,106 +176,6 @@ test_expect_success 'want-ref with ref we already have commit for' '
 	check_output
 '
 
-. "$TEST_DIRECTORY"/lib-httpd.sh
-start_httpd
-
-REPO="$HTTPD_DOCUMENT_ROOT_PATH/repo"
-LOCAL_PRISTINE="$(pwd)/local_pristine"
-
-test_expect_success 'setup repos for change-while-negotiating test' '
-	(
-		git init "$REPO" &&
-		cd "$REPO" &&
-		>.git/git-daemon-export-ok &&
-		test_commit m1 &&
-		git tag -d m1 &&
-
-		# Local repo with many commits (so that negotiation will take
-		# more than 1 request/response pair)
-		git clone "http://127.0.0.1:$LIB_HTTPD_PORT/smart/repo" "$LOCAL_PRISTINE" &&
-		cd "$LOCAL_PRISTINE" &&
-		git checkout -b side &&
-		test_commit_bulk --id=s 33 &&
-
-		# Add novel commits to upstream
-		git checkout master &&
-		cd "$REPO" &&
-		test_commit m2 &&
-		test_commit m3 &&
-		git tag -d m2 m3
-	) &&
-	git -C "$LOCAL_PRISTINE" remote set-url origin "http://127.0.0.1:$LIB_HTTPD_PORT/one_time_sed/repo" &&
-	git -C "$LOCAL_PRISTINE" config protocol.version 2
-'
-
-inconsistency () {
-	# Simulate that the server initially reports $2 as the ref
-	# corresponding to $1, and after that, $1 as the ref corresponding to
-	# $1. This corresponds to the real-life situation where the server's
-	# repository appears to change during negotiation, for example, when
-	# different servers in a load-balancing arrangement serve (stateless)
-	# RPCs during a single negotiation.
-	printf "s/%s/%s/" \
-	       $(git -C "$REPO" rev-parse $1 | tr -d "\n") \
-	       $(git -C "$REPO" rev-parse $2 | tr -d "\n") \
-	       >"$HTTPD_ROOT_PATH/one-time-sed"
-}
-
-test_expect_success 'server is initially ahead - no ref in want' '
-	git -C "$REPO" config uploadpack.allowRefInWant false &&
-	rm -rf local &&
-	cp -r "$LOCAL_PRISTINE" local &&
-	inconsistency master 1234567890123456789012345678901234567890 &&
-	test_must_fail git -C local fetch 2>err &&
-	test_i18ngrep "fatal: remote error: upload-pack: not our ref" err
-'
-
-test_expect_success 'server is initially ahead - ref in want' '
-	git -C "$REPO" config uploadpack.allowRefInWant true &&
-	rm -rf local &&
-	cp -r "$LOCAL_PRISTINE" local &&
-	inconsistency master 1234567890123456789012345678901234567890 &&
-	git -C local fetch &&
-
-	git -C "$REPO" rev-parse --verify master >expected &&
-	git -C local rev-parse --verify refs/remotes/origin/master >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'server is initially behind - no ref in want' '
-	git -C "$REPO" config uploadpack.allowRefInWant false &&
-	rm -rf local &&
-	cp -r "$LOCAL_PRISTINE" local &&
-	inconsistency master "master^" &&
-	git -C local fetch &&
-
-	git -C "$REPO" rev-parse --verify "master^" >expected &&
-	git -C local rev-parse --verify refs/remotes/origin/master >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'server is initially behind - ref in want' '
-	git -C "$REPO" config uploadpack.allowRefInWant true &&
-	rm -rf local &&
-	cp -r "$LOCAL_PRISTINE" local &&
-	inconsistency master "master^" &&
-	git -C local fetch &&
-
-	git -C "$REPO" rev-parse --verify "master" >expected &&
-	git -C local rev-parse --verify refs/remotes/origin/master >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'server loses a ref - ref in want' '
-	git -C "$REPO" config uploadpack.allowRefInWant true &&
-	rm -rf local &&
-	cp -r "$LOCAL_PRISTINE" local &&
-	echo "s/master/raster/" >"$HTTPD_ROOT_PATH/one-time-sed" &&
-	test_must_fail git -C local fetch 2>err &&
-
-	test_i18ngrep "fatal: remote error: unknown ref refs/heads/raster" err
-'
-
 REPO="$(pwd)/repo"
 LOCAL_PRISTINE="$(pwd)/local_pristine"
 
@@ -311,13 +230,14 @@ test_expect_success 'fetching with exact OID' '
 
 	rm -rf local &&
 	cp -r "$LOCAL_PRISTINE" local &&
+	oid=$(git -C "$REPO" rev-parse d) &&
 	GIT_TRACE_PACKET="$(pwd)/log" git -C local fetch origin \
-		$(git -C "$REPO" rev-parse d):refs/heads/actual &&
+		"$oid":refs/heads/actual &&
 
 	git -C "$REPO" rev-parse "d" >expected &&
 	git -C local rev-parse refs/heads/actual >actual &&
 	test_cmp expected actual &&
-	grep "want $(git -C "$REPO" rev-parse d)" log
+	grep "want $oid" log
 '
 
 test_expect_success 'fetching multiple refs' '
@@ -339,13 +259,14 @@ test_expect_success 'fetching ref and exact OID' '
 
 	rm -rf local &&
 	cp -r "$LOCAL_PRISTINE" local &&
+	oid=$(git -C "$REPO" rev-parse b) &&
 	GIT_TRACE_PACKET="$(pwd)/log" git -C local fetch origin \
-		master $(git -C "$REPO" rev-parse b):refs/heads/actual &&
+		master "$oid":refs/heads/actual &&
 
 	git -C "$REPO" rev-parse "master" "b" >expected &&
 	git -C local rev-parse refs/remotes/origin/master refs/heads/actual >actual &&
 	test_cmp expected actual &&
-	grep "want $(git -C "$REPO" rev-parse b)" log &&
+	grep "want $oid" log &&
 	grep "want-ref refs/heads/master" log
 '
 
@@ -372,4 +293,107 @@ test_expect_success 'fetching with wildcard that matches multiple refs' '
 	grep "want-ref refs/heads/o/bar" log
 '
 
+. "$TEST_DIRECTORY"/lib-httpd.sh
+start_httpd
+
+REPO="$HTTPD_DOCUMENT_ROOT_PATH/repo"
+LOCAL_PRISTINE="$(pwd)/local_pristine"
+
+test_expect_success 'setup repos for change-while-negotiating test' '
+	(
+		git init "$REPO" &&
+		cd "$REPO" &&
+		>.git/git-daemon-export-ok &&
+		test_commit m1 &&
+		git tag -d m1 &&
+
+		# Local repo with many commits (so that negotiation will take
+		# more than 1 request/response pair)
+		rm -rf "$LOCAL_PRISTINE" &&
+		git clone "http://127.0.0.1:$LIB_HTTPD_PORT/smart/repo" "$LOCAL_PRISTINE" &&
+		cd "$LOCAL_PRISTINE" &&
+		git checkout -b side &&
+		test_commit_bulk --id=s 33 &&
+
+		# Add novel commits to upstream
+		git checkout master &&
+		cd "$REPO" &&
+		test_commit m2 &&
+		test_commit m3 &&
+		git tag -d m2 m3
+	) &&
+	git -C "$LOCAL_PRISTINE" remote set-url origin "http://127.0.0.1:$LIB_HTTPD_PORT/one_time_perl/repo" &&
+	git -C "$LOCAL_PRISTINE" config protocol.version 2
+'
+
+inconsistency () {
+	# Simulate that the server initially reports $2 as the ref
+	# corresponding to $1, and after that, $1 as the ref corresponding to
+	# $1. This corresponds to the real-life situation where the server's
+	# repository appears to change during negotiation, for example, when
+	# different servers in a load-balancing arrangement serve (stateless)
+	# RPCs during a single negotiation.
+	oid1=$(git -C "$REPO" rev-parse $1) &&
+	oid2=$(git -C "$REPO" rev-parse $2) &&
+	echo "s/$oid1/$oid2/" >"$HTTPD_ROOT_PATH/one-time-perl"
+}
+
+test_expect_success 'server is initially ahead - no ref in want' '
+	git -C "$REPO" config uploadpack.allowRefInWant false &&
+	rm -rf local &&
+	cp -r "$LOCAL_PRISTINE" local &&
+	inconsistency master $(test_oid numeric) &&
+	test_must_fail git -C local fetch 2>err &&
+	test_i18ngrep "fatal: remote error: upload-pack: not our ref" err
+'
+
+test_expect_success 'server is initially ahead - ref in want' '
+	git -C "$REPO" config uploadpack.allowRefInWant true &&
+	rm -rf local &&
+	cp -r "$LOCAL_PRISTINE" local &&
+	inconsistency master $(test_oid numeric) &&
+	git -C local fetch &&
+
+	git -C "$REPO" rev-parse --verify master >expected &&
+	git -C local rev-parse --verify refs/remotes/origin/master >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'server is initially behind - no ref in want' '
+	git -C "$REPO" config uploadpack.allowRefInWant false &&
+	rm -rf local &&
+	cp -r "$LOCAL_PRISTINE" local &&
+	inconsistency master "master^" &&
+	git -C local fetch &&
+
+	git -C "$REPO" rev-parse --verify "master^" >expected &&
+	git -C local rev-parse --verify refs/remotes/origin/master >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'server is initially behind - ref in want' '
+	git -C "$REPO" config uploadpack.allowRefInWant true &&
+	rm -rf local &&
+	cp -r "$LOCAL_PRISTINE" local &&
+	inconsistency master "master^" &&
+	git -C local fetch &&
+
+	git -C "$REPO" rev-parse --verify "master" >expected &&
+	git -C local rev-parse --verify refs/remotes/origin/master >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'server loses a ref - ref in want' '
+	git -C "$REPO" config uploadpack.allowRefInWant true &&
+	rm -rf local &&
+	cp -r "$LOCAL_PRISTINE" local &&
+	echo "s/master/raster/" >"$HTTPD_ROOT_PATH/one-time-perl" &&
+	test_must_fail git -C local fetch 2>err &&
+
+	test_i18ngrep "fatal: remote error: unknown ref refs/heads/raster" err
+'
+
+# DO NOT add non-httpd-specific tests here, because the last part of this
+# test script is only executed when httpd is available and enabled.
+
 test_done
diff --git a/third_party/git/t/t5704-protocol-violations.sh b/third_party/git/t/t5704-protocol-violations.sh
new file mode 100755
index 0000000000..5c941949b9
--- /dev/null
+++ b/third_party/git/t/t5704-protocol-violations.sh
@@ -0,0 +1,35 @@
+#!/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 &&
+		packetize "object-format=$(test_oid algo)" &&
+		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 &&
+		packetize "object-format=$(test_oid algo)" &&
+		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
diff --git a/third_party/git/t/t5801-remote-helpers.sh b/third_party/git/t/t5801-remote-helpers.sh
index 2d6c4a281e..0f04b6cddb 100755
--- a/third_party/git/t/t5801-remote-helpers.sh
+++ b/third_party/git/t/t5801-remote-helpers.sh
@@ -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' '
@@ -247,7 +253,6 @@ clean_mark () {
 test_expect_success 'proper failure checks for fetching' '
 	(cd local &&
 	test_must_fail env GIT_REMOTE_TESTGIT_FAILURE=1 git fetch 2>error &&
-	cat error &&
 	test_i18ngrep -q "error while running fast-import" error
 	)
 '
diff --git a/third_party/git/t/t5801/git-remote-testgit b/third_party/git/t/t5801/git-remote-testgit
index 6b9f0b5dc7..1544d6dc6b 100755
--- a/third_party/git/t/t5801/git-remote-testgit
+++ b/third_party/git/t/t5801/git-remote-testgit
@@ -52,9 +52,11 @@ do
 		test -n "$GIT_REMOTE_TESTGIT_SIGNED_TAGS" && echo "signed-tags"
 		test -n "$GIT_REMOTE_TESTGIT_NO_PRIVATE_UPDATE" && echo "no-private-update"
 		echo 'option'
+		echo 'object-format'
 		echo
 		;;
 	list)
+		echo ":object-format $(git rev-parse --show-object-format=storage)"
 		git for-each-ref --format='? %(refname)' 'refs/heads/' 'refs/tags/'
 		head=$(git symbolic-ref HEAD)
 		echo "@$head HEAD"
@@ -139,6 +141,10 @@ do
 			test $val = "true" && force="true" || force=
 			echo "ok"
 			;;
+		object-format)
+			test $val = "true" && object_format="true" || object_format=
+			echo "ok"
+			;;
 		*)
 			echo "unsupported"
 			;;
diff --git a/third_party/git/t/t6000-rev-list-misc.sh b/third_party/git/t/t6000-rev-list-misc.sh
index 52a9e38d66..fc4d55dcb2 100755
--- a/third_party/git/t/t6000-rev-list-misc.sh
+++ b/third_party/git/t/t6000-rev-list-misc.sh
@@ -8,6 +8,7 @@ test_expect_success setup '
 	echo content1 >wanted_file &&
 	echo content2 >unwanted_file &&
 	git add wanted_file unwanted_file &&
+	test_tick &&
 	git commit -m one
 '
 
@@ -21,6 +22,7 @@ test_expect_success 'rev-list --objects with pathspecs and deeper paths' '
 	mkdir foo &&
 	>foo/file &&
 	git add foo/file &&
+	test_tick &&
 	git commit -m two &&
 
 	git rev-list --objects HEAD -- foo >output &&
@@ -69,6 +71,7 @@ test_expect_success '--no-object-names and --object-names are last-one-wins' '
 '
 
 test_expect_success 'rev-list A..B and rev-list ^A B are the same' '
+	test_tick &&
 	git commit --allow-empty -m another &&
 	git tag -a -m "annotated" v1.0 &&
 	git rev-list --objects ^v1.0^ v1.0 >expect &&
@@ -84,10 +87,10 @@ test_expect_success 'propagate uninteresting flag down correctly' '
 test_expect_success 'symleft flag bit is propagated down from tag' '
 	git log --format="%m %s" --left-right v1.0...master >actual &&
 	cat >expect <<-\EOF &&
-	> two
-	> one
 	< another
 	< that
+	> two
+	> one
 	EOF
 	test_cmp expect actual
 '
@@ -104,13 +107,16 @@ test_expect_success 'rev-list can show index objects' '
 	#   - we do not show the root tree; since we updated the index, it
 	#     does not have a valid cache tree
 	#
-	cat >expect <<-\EOF &&
-	8e4020bb5a8d8c873b25de15933e75cc0fc275df one
-	d9d3a7417b9605cfd88ee6306b28dadc29e6ab08 only-in-index
-	9200b628cf9dc883a85a7abc8d6e6730baee589c two
-	EOF
 	echo only-in-index >only-in-index &&
 	test_when_finished "git reset --hard" &&
+	rev1=$(git rev-parse HEAD:one) &&
+	rev2=$(git rev-parse HEAD:two) &&
+	revi=$(git hash-object only-in-index) &&
+	cat >expect <<-EOF &&
+	$rev1 one
+	$revi only-in-index
+	$rev2 two
+	EOF
 	git add only-in-index &&
 	git rev-list --objects --indexed-objects >actual &&
 	test_cmp expect actual
@@ -122,8 +128,8 @@ test_expect_success 'rev-list can negate index objects' '
 	test_cmp expect actual
 '
 
-test_expect_success '--bisect and --first-parent can not be combined' '
-	test_must_fail git rev-list --bisect --first-parent HEAD
+test_expect_success '--bisect and --first-parent can be combined' '
+	git rev-list --bisect --first-parent HEAD
 '
 
 test_expect_success '--header shows a NUL after each commit' '
@@ -140,4 +146,24 @@ test_expect_success '--header shows a NUL after each commit' '
 	test_cmp expect actual
 '
 
+test_expect_success 'rev-list --end-of-options' '
+	git update-ref refs/heads/--output=yikes HEAD &&
+	git rev-list --end-of-options --output=yikes >actual &&
+	test_path_is_missing yikes &&
+	git rev-list HEAD >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success 'rev-list --count' '
+	count=$(git rev-list --count HEAD) &&
+	git rev-list HEAD >actual &&
+	test_line_count = $count actual
+'
+
+test_expect_success 'rev-list --count --objects' '
+	count=$(git rev-list --count --objects HEAD) &&
+	git rev-list --objects HEAD >actual &&
+	test_line_count = $count actual
+'
+
 test_done
diff --git a/third_party/git/t/t6002-rev-list-bisect.sh b/third_party/git/t/t6002-rev-list-bisect.sh
index a661408038..b95a0212ad 100755
--- a/third_party/git/t/t6002-rev-list-bisect.sh
+++ b/third_party/git/t/t6002-rev-list-bisect.sh
@@ -263,4 +263,49 @@ test_expect_success 'rev-parse --bisect can default to good/bad refs' '
 	test_cmp expect.sorted actual.sorted
 '
 
+test_output_expect_success '--bisect --first-parent' 'git rev-list --bisect --first-parent E ^F' <<EOF
+e4
+EOF
+
+test_output_expect_success '--first-parent' 'git rev-list --first-parent E ^F' <<EOF
+E
+e1
+e2
+e3
+e4
+e5
+e6
+e7
+e8
+EOF
+
+test_output_expect_success '--bisect-vars --first-parent' 'git rev-list --bisect-vars --first-parent E ^F' <<EOF
+bisect_rev='e5'
+bisect_nr=4
+bisect_good=4
+bisect_bad=3
+bisect_all=9
+bisect_steps=2
+EOF
+
+test_expect_success '--bisect-all --first-parent' '
+	cat >expect.unsorted <<-EOF &&
+	$(git rev-parse E) (tag: E, dist=0)
+	$(git rev-parse e1) (tag: e1, dist=1)
+	$(git rev-parse e2) (tag: e2, dist=2)
+	$(git rev-parse e3) (tag: e3, dist=3)
+	$(git rev-parse e4) (tag: e4, dist=4)
+	$(git rev-parse e5) (tag: e5, dist=4)
+	$(git rev-parse e6) (tag: e6, dist=3)
+	$(git rev-parse e7) (tag: e7, dist=2)
+	$(git rev-parse e8) (tag: e8, dist=1)
+	EOF
+
+	# expect results to be ordered by distance (descending),
+	# commit hash (ascending)
+	sort -k4,4r -k1,1 expect.unsorted >expect &&
+	git rev-list --bisect-all --first-parent E ^F >actual &&
+	test_cmp expect actual
+'
+
 test_done
diff --git a/third_party/git/t/t6006-rev-list-format.sh b/third_party/git/t/t6006-rev-list-format.sh
index da113d975b..bc95da8a5f 100755
--- a/third_party/git/t/t6006-rev-list-format.sh
+++ b/third_party/git/t/t6006-rev-list-format.sh
@@ -109,31 +109,35 @@ commit $head1
 EOF
 
 # we don't test relative here
-test_format author %an%n%ae%n%ad%n%aD%n%at <<EOF
+test_format author %an%n%ae%n%al%n%ad%n%aD%n%at <<EOF
 commit $head2
-A U Thor
-author@example.com
+$GIT_AUTHOR_NAME
+$GIT_AUTHOR_EMAIL
+$TEST_AUTHOR_LOCALNAME
 Thu Apr 7 15:13:13 2005 -0700
 Thu, 7 Apr 2005 15:13:13 -0700
 1112911993
 commit $head1
-A U Thor
-author@example.com
+$GIT_AUTHOR_NAME
+$GIT_AUTHOR_EMAIL
+$TEST_AUTHOR_LOCALNAME
 Thu Apr 7 15:13:13 2005 -0700
 Thu, 7 Apr 2005 15:13:13 -0700
 1112911993
 EOF
 
-test_format committer %cn%n%ce%n%cd%n%cD%n%ct <<EOF
+test_format committer %cn%n%ce%n%cl%n%cd%n%cD%n%ct <<EOF
 commit $head2
-C O Mitter
-committer@example.com
+$GIT_COMMITTER_NAME
+$GIT_COMMITTER_EMAIL
+$TEST_COMMITTER_LOCALNAME
 Thu Apr 7 15:13:13 2005 -0700
 Thu, 7 Apr 2005 15:13:13 -0700
 1112911993
 commit $head1
-C O Mitter
-committer@example.com
+$GIT_COMMITTER_NAME
+$GIT_COMMITTER_EMAIL
+$TEST_COMMITTER_LOCALNAME
 Thu Apr 7 15:13:13 2005 -0700
 Thu, 7 Apr 2005 15:13:13 -0700
 1112911993
@@ -410,7 +414,7 @@ test_expect_success 'empty email' '
 	test_tick &&
 	C=$(GIT_AUTHOR_EMAIL= git commit-tree HEAD^{tree} </dev/null) &&
 	A=$(git show --pretty=format:%an,%ae,%ad%n -s $C) &&
-	verbose test "$A" = "A U Thor,,Thu Apr 7 15:14:13 2005 -0700"
+	verbose test "$A" = "$GIT_AUTHOR_NAME,,Thu Apr 7 15:14:13 2005 -0700"
 '
 
 test_expect_success 'del LF before empty (1)' '
@@ -459,9 +463,10 @@ test_expect_success '--abbrev' '
 '
 
 test_expect_success '%H is not affected by --abbrev-commit' '
+	expected=$(($(test_oid hexsz) + 1)) &&
 	git log -1 --format=%H --abbrev-commit --abbrev=20 HEAD >actual &&
 	len=$(wc -c <actual) &&
-	test $len = 41
+	test $len = $expected
 '
 
 test_expect_success '%h is not affected by --abbrev-commit' '
@@ -495,15 +500,14 @@ test_expect_success '%gd shortens ref name' '
 '
 
 test_expect_success 'reflog identity' '
-	echo "C O Mitter:committer@example.com" >expect &&
+	echo "$GIT_COMMITTER_NAME:$GIT_COMMITTER_EMAIL" >expect &&
 	git log -g -1 --format="%gn:%ge" >actual &&
 	test_cmp expect actual
 '
 
 test_expect_success 'oneline with empty message' '
-	git commit -m "dummy" --allow-empty &&
-	git commit -m "dummy" --allow-empty &&
-	git filter-branch --msg-filter "sed -e s/dummy//" HEAD^^.. &&
+	git commit --allow-empty --cleanup=verbatim -m "$LF" &&
+	git commit --allow-empty --allow-empty-message &&
 	git rev-list --oneline HEAD >test.txt &&
 	test_line_count = 5 test.txt &&
 	git rev-list --oneline --graph HEAD >testg.txt &&
diff --git a/third_party/git/t/t6011-rev-list-with-bad-commit.sh b/third_party/git/t/t6011-rev-list-with-bad-commit.sh
index 545b461e51..bad02cf5b8 100755
--- a/third_party/git/t/t6011-rev-list-with-bad-commit.sh
+++ b/third_party/git/t/t6011-rev-list-with-bad-commit.sh
@@ -42,7 +42,7 @@ test_expect_success 'corrupt second commit object' \
    '
 
 test_expect_success 'rev-list should fail' '
-	test_must_fail env GIT_TEST_COMMIT_GRAPH=0 git rev-list --all > /dev/null
+	test_must_fail env GIT_TEST_COMMIT_GRAPH=0 git -c core.commitGraph=false rev-list --all > /dev/null
 '
 
 test_expect_success 'git repack _MUST_ fail' \
diff --git a/third_party/git/t/t6012-rev-list-simplify.sh b/third_party/git/t/t6012-rev-list-simplify.sh
index a10f0df02b..7fc10f8593 100755
--- a/third_party/git/t/t6012-rev-list-simplify.sh
+++ b/third_party/git/t/t6012-rev-list-simplify.sh
@@ -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 topic &&
+
+	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 topic &&
+	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 topic &&
+	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 topic &&
+	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
diff --git a/third_party/git/t/t6016-rev-list-graph-simplify-history.sh b/third_party/git/t/t6016-rev-list-graph-simplify-history.sh
index f7181d1d6a..f5e6e92f5b 100755
--- a/third_party/git/t/t6016-rev-list-graph-simplify-history.sh
+++ b/third_party/git/t/t6016-rev-list-graph-simplify-history.sh
@@ -67,11 +67,10 @@ test_expect_success '--graph --all' '
 	echo "| * $C4" >> expected &&
 	echo "| * $C3" >> expected &&
 	echo "* | $A5" >> expected &&
-	echo "| |     " >> expected &&
-	echo "|  \\    " >> expected &&
-	echo "*-. \\   $A4" >> expected &&
-	echo "|\\ \\ \\  " >> expected &&
-	echo "| | |/  " >> expected &&
+	echo "| |   " >> expected &&
+	echo "|  \\  " >> expected &&
+	echo "*-. | $A4" >> expected &&
+	echo "|\\ \\| " >> expected &&
 	echo "| | * $C2" >> expected &&
 	echo "| | * $C1" >> expected &&
 	echo "| * | $B2" >> expected &&
@@ -97,11 +96,10 @@ test_expect_success '--graph --simplify-by-decoration' '
 	echo "| * $C4" >> expected &&
 	echo "| * $C3" >> expected &&
 	echo "* | $A5" >> expected &&
-	echo "| |     " >> expected &&
-	echo "|  \\    " >> expected &&
-	echo "*-. \\   $A4" >> expected &&
-	echo "|\\ \\ \\  " >> expected &&
-	echo "| | |/  " >> expected &&
+	echo "| |   " >> expected &&
+	echo "|  \\  " >> expected &&
+	echo "*-. | $A4" >> expected &&
+	echo "|\\ \\| " >> expected &&
 	echo "| | * $C2" >> expected &&
 	echo "| | * $C1" >> expected &&
 	echo "| * | $B2" >> expected &&
@@ -131,9 +129,8 @@ test_expect_success '--graph --simplify-by-decoration prune branch B' '
 	echo "| * $C4" >> expected &&
 	echo "| * $C3" >> expected &&
 	echo "* | $A5" >> expected &&
-	echo "* |   $A4" >> expected &&
-	echo "|\\ \\  " >> expected &&
-	echo "| |/  " >> expected &&
+	echo "* | $A4" >> expected &&
+	echo "|\\| " >> expected &&
 	echo "| * $C2" >> expected &&
 	echo "| * $C1" >> expected &&
 	echo "* | $A3" >> expected &&
@@ -151,9 +148,8 @@ test_expect_success '--graph --full-history -- bar.txt' '
 	echo "|\\  " >> expected &&
 	echo "| * $C4" >> expected &&
 	echo "* | $A5" >> expected &&
-	echo "* |   $A4" >> expected &&
-	echo "|\\ \\  " >> expected &&
-	echo "| |/  " >> expected &&
+	echo "* | $A4" >> expected &&
+	echo "|\\| " >> expected &&
 	echo "* | $A3" >> expected &&
 	echo "|/  " >> expected &&
 	echo "* $A2" >> expected &&
@@ -255,7 +251,7 @@ test_expect_success '--graph --boundary ^C3' '
 	echo "* | | | $A3" >> expected &&
 	echo "o | | | $A2" >> expected &&
 	echo "|/ / /  " >> expected &&
-	echo "o | | $A1" >> expected &&
+	echo "o / / $A1" >> expected &&
 	echo " / /  " >> expected &&
 	echo "| o $C3" >> expected &&
 	echo "|/  " >> expected &&
diff --git a/third_party/git/t/t6018-rev-list-glob.sh b/third_party/git/t/t6018-rev-list-glob.sh
index bb5aeac07f..fe2f3cec3d 100755
--- a/third_party/git/t/t6018-rev-list-glob.sh
+++ b/third_party/git/t/t6018-rev-list-glob.sh
@@ -32,11 +32,11 @@ test_expect_success 'setup' '
 	git checkout -b someref master &&
 	commit some &&
 	git checkout master &&
-	commit master2 &&
+	commit topic_2 &&
 	git tag foo/bar master &&
-	commit master3 &&
+	commit topic_3 &&
 	git update-ref refs/remotes/foo/baz master &&
-	commit master4 &&
+	commit topic_4 &&
 	git update-ref refs/remotes/upstream/one subspace/one &&
 	git update-ref refs/remotes/upstream/two subspace/two &&
 	git update-ref refs/remotes/upstream/x subspace-x &&
@@ -345,6 +345,11 @@ test_expect_success 'rev-list should succeed with empty output with empty glob'
 	test_must_be_empty actual
 '
 
+test_expect_success 'rev-list should succeed with empty output when ignoring missing' '
+	git rev-list --ignore-missing $ZERO_OID >actual &&
+	test_must_be_empty actual
+'
+
 test_expect_success 'shortlog accepts --glob/--tags/--remotes' '
 
 	compare shortlog "subspace/one subspace/two" --branches=subspace &&
diff --git a/third_party/git/t/t6019-rev-list-ancestry-path.sh b/third_party/git/t/t6019-rev-list-ancestry-path.sh
index beadaf6cca..353f84313f 100755
--- a/third_party/git/t/t6019-rev-list-ancestry-path.sh
+++ b/third_party/git/t/t6019-rev-list-ancestry-path.sh
@@ -143,14 +143,14 @@ test_expect_success 'setup criss-cross' '
 test_expect_success 'criss-cross: rev-list --ancestry-path cb..bc' '
 	(cd criss-cross &&
 	 git rev-list --ancestry-path xcb..xbc > actual &&
-	 test -z "$(cat actual)")
+	 test_must_be_empty actual)
 '
 
 # no commits in repository descend from cb
 test_expect_success 'criss-cross: rev-list --ancestry-path --all ^cb' '
 	(cd criss-cross &&
 	 git rev-list --ancestry-path --all ^xcb > actual &&
-	 test -z "$(cat actual)")
+	 test_must_be_empty actual)
 '
 
 test_done
diff --git a/third_party/git/t/t6021-merge-criss-cross.sh b/third_party/git/t/t6021-merge-criss-cross.sh
deleted file mode 100755
index 213deecab1..0000000000
--- a/third_party/git/t/t6021-merge-criss-cross.sh
+++ /dev/null
@@ -1,96 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2005 Fredrik Kuivinen
-#
-
-# See http://marc.info/?l=git&m=111463358500362&w=2 for a
-# nice description of what this is about.
-
-
-test_description='Test criss-cross merge'
-. ./test-lib.sh
-
-test_expect_success 'prepare repository' \
-'echo "1
-2
-3
-4
-5
-6
-7
-8
-9" > file &&
-git add file &&
-git commit -m "Initial commit" file &&
-git branch A &&
-git branch B &&
-git checkout A &&
-echo "1
-2
-3
-4
-5
-6
-7
-8 changed in B8, branch A
-9" > file &&
-git commit -m "B8" file &&
-git checkout B &&
-echo "1
-2
-3 changed in C3, branch B
-4
-5
-6
-7
-8
-9
-" > file &&
-git commit -m "C3" file &&
-git branch C3 &&
-git merge -m "pre E3 merge" A &&
-echo "1
-2
-3 changed in E3, branch B. New file size
-4
-5
-6
-7
-8 changed in B8, branch A
-9
-" > file &&
-git commit -m "E3" file &&
-git checkout A &&
-git merge -m "pre D8 merge" C3 &&
-echo "1
-2
-3 changed in C3, branch B
-4
-5
-6
-7
-8 changed in D8, branch A. New file size 2
-9" > file &&
-git commit -m D8 file'
-
-test_expect_success 'Criss-cross merge' 'git merge -m "final merge" B'
-
-cat > file-expect <<EOF
-1
-2
-3 changed in E3, branch B. New file size
-4
-5
-6
-7
-8 changed in D8, branch A. New file size 2
-9
-EOF
-
-test_expect_success 'Criss-cross merge result' 'cmp file file-expect'
-
-test_expect_success 'Criss-cross merge fails (-s resolve)' \
-'git reset --hard A^ &&
-test_must_fail git merge -s resolve -m "final merge" B'
-
-test_done
diff --git a/third_party/git/t/t6023-merge-file.sh b/third_party/git/t/t6023-merge-file.sh
deleted file mode 100755
index 51ee887a77..0000000000
--- a/third_party/git/t/t6023-merge-file.sh
+++ /dev/null
@@ -1,362 +0,0 @@
-#!/bin/sh
-
-test_description='RCS merge replacement: merge-file'
-. ./test-lib.sh
-
-cat > orig.txt << EOF
-Dominus regit me,
-et nihil mihi deerit.
-In loco pascuae ibi me collocavit,
-super aquam refectionis educavit me;
-animam meam convertit,
-deduxit me super semitas jusitiae,
-propter nomen suum.
-EOF
-
-cat > new1.txt << EOF
-Dominus regit me,
-et nihil mihi deerit.
-In loco pascuae ibi me collocavit,
-super aquam refectionis educavit me;
-animam meam convertit,
-deduxit me super semitas jusitiae,
-propter nomen suum.
-Nam et si ambulavero in medio umbrae mortis,
-non timebo mala, quoniam tu mecum es:
-virga tua et baculus tuus ipsa me consolata sunt.
-EOF
-
-cat > new2.txt << EOF
-Dominus regit me, et nihil mihi deerit.
-In loco pascuae ibi me collocavit,
-super aquam refectionis educavit me;
-animam meam convertit,
-deduxit me super semitas jusitiae,
-propter nomen suum.
-EOF
-
-cat > new3.txt << EOF
-DOMINUS regit me,
-et nihil mihi deerit.
-In loco pascuae ibi me collocavit,
-super aquam refectionis educavit me;
-animam meam convertit,
-deduxit me super semitas jusitiae,
-propter nomen suum.
-EOF
-
-cat > new4.txt << EOF
-Dominus regit me, et nihil mihi deerit.
-In loco pascuae ibi me collocavit,
-super aquam refectionis educavit me;
-animam meam convertit,
-deduxit me super semitas jusitiae,
-EOF
-printf "propter nomen suum." >> new4.txt
-
-test_expect_success 'merge with no changes' '
-	cp orig.txt test.txt &&
-	git merge-file test.txt orig.txt orig.txt &&
-	test_cmp test.txt orig.txt
-'
-
-cp new1.txt test.txt
-test_expect_success "merge without conflict" \
-	"git merge-file test.txt orig.txt new2.txt"
-
-test_expect_success 'works in subdirectory' '
-	mkdir dir &&
-	cp new1.txt dir/a.txt &&
-	cp orig.txt dir/o.txt &&
-	cp new2.txt dir/b.txt &&
-	( cd dir && git merge-file a.txt o.txt b.txt ) &&
-	test_path_is_missing a.txt
-'
-
-cp new1.txt test.txt
-test_expect_success "merge without conflict (--quiet)" \
-	"git merge-file --quiet test.txt orig.txt new2.txt"
-
-cp new1.txt test2.txt
-test_expect_failure "merge without conflict (missing LF at EOF)" \
-	"git merge-file test2.txt orig.txt new4.txt"
-
-test_expect_failure "merge result added missing LF" \
-	"test_cmp test.txt test2.txt"
-
-cp new4.txt test3.txt
-test_expect_success "merge without conflict (missing LF at EOF, away from change in the other file)" \
-	"git merge-file --quiet test3.txt new2.txt new3.txt"
-
-cat > expect.txt << EOF
-DOMINUS regit me,
-et nihil mihi deerit.
-In loco pascuae ibi me collocavit,
-super aquam refectionis educavit me;
-animam meam convertit,
-deduxit me super semitas jusitiae,
-EOF
-printf "propter nomen suum." >> expect.txt
-
-test_expect_success "merge does not add LF away of change" \
-	"test_cmp expect.txt test3.txt"
-
-cp test.txt backup.txt
-test_expect_success "merge with conflicts" \
-	"test_must_fail git merge-file test.txt orig.txt new3.txt"
-
-cat > expect.txt << EOF
-<<<<<<< test.txt
-Dominus regit me, et nihil mihi deerit.
-=======
-DOMINUS regit me,
-et nihil mihi deerit.
->>>>>>> new3.txt
-In loco pascuae ibi me collocavit,
-super aquam refectionis educavit me;
-animam meam convertit,
-deduxit me super semitas jusitiae,
-propter nomen suum.
-Nam et si ambulavero in medio umbrae mortis,
-non timebo mala, quoniam tu mecum es:
-virga tua et baculus tuus ipsa me consolata sunt.
-EOF
-
-test_expect_success "expected conflict markers" "test_cmp expect.txt test.txt"
-
-cp backup.txt test.txt
-
-cat > expect.txt << EOF
-Dominus regit me, et nihil mihi deerit.
-In loco pascuae ibi me collocavit,
-super aquam refectionis educavit me;
-animam meam convertit,
-deduxit me super semitas jusitiae,
-propter nomen suum.
-Nam et si ambulavero in medio umbrae mortis,
-non timebo mala, quoniam tu mecum es:
-virga tua et baculus tuus ipsa me consolata sunt.
-EOF
-test_expect_success "merge conflicting with --ours" \
-	"git merge-file --ours test.txt orig.txt new3.txt && test_cmp expect.txt test.txt"
-cp backup.txt test.txt
-
-cat > expect.txt << EOF
-DOMINUS regit me,
-et nihil mihi deerit.
-In loco pascuae ibi me collocavit,
-super aquam refectionis educavit me;
-animam meam convertit,
-deduxit me super semitas jusitiae,
-propter nomen suum.
-Nam et si ambulavero in medio umbrae mortis,
-non timebo mala, quoniam tu mecum es:
-virga tua et baculus tuus ipsa me consolata sunt.
-EOF
-test_expect_success "merge conflicting with --theirs" \
-	"git merge-file --theirs test.txt orig.txt new3.txt && test_cmp expect.txt test.txt"
-cp backup.txt test.txt
-
-cat > expect.txt << EOF
-Dominus regit me, et nihil mihi deerit.
-DOMINUS regit me,
-et nihil mihi deerit.
-In loco pascuae ibi me collocavit,
-super aquam refectionis educavit me;
-animam meam convertit,
-deduxit me super semitas jusitiae,
-propter nomen suum.
-Nam et si ambulavero in medio umbrae mortis,
-non timebo mala, quoniam tu mecum es:
-virga tua et baculus tuus ipsa me consolata sunt.
-EOF
-test_expect_success "merge conflicting with --union" \
-	"git merge-file --union test.txt orig.txt new3.txt && test_cmp expect.txt test.txt"
-cp backup.txt test.txt
-
-test_expect_success "merge with conflicts, using -L" \
-	"test_must_fail git merge-file -L 1 -L 2 test.txt orig.txt new3.txt"
-
-cat > expect.txt << EOF
-<<<<<<< 1
-Dominus regit me, et nihil mihi deerit.
-=======
-DOMINUS regit me,
-et nihil mihi deerit.
->>>>>>> new3.txt
-In loco pascuae ibi me collocavit,
-super aquam refectionis educavit me;
-animam meam convertit,
-deduxit me super semitas jusitiae,
-propter nomen suum.
-Nam et si ambulavero in medio umbrae mortis,
-non timebo mala, quoniam tu mecum es:
-virga tua et baculus tuus ipsa me consolata sunt.
-EOF
-
-test_expect_success "expected conflict markers, with -L" \
-	"test_cmp expect.txt test.txt"
-
-sed "s/ tu / TU /" < new1.txt > new5.txt
-test_expect_success "conflict in removed tail" \
-	"test_must_fail git merge-file -p orig.txt new1.txt new5.txt > out"
-
-cat > expect << EOF
-Dominus regit me,
-et nihil mihi deerit.
-In loco pascuae ibi me collocavit,
-super aquam refectionis educavit me;
-animam meam convertit,
-deduxit me super semitas jusitiae,
-propter nomen suum.
-<<<<<<< orig.txt
-=======
-Nam et si ambulavero in medio umbrae mortis,
-non timebo mala, quoniam TU mecum es:
-virga tua et baculus tuus ipsa me consolata sunt.
->>>>>>> new5.txt
-EOF
-
-test_expect_success "expected conflict markers" "test_cmp expect out"
-
-test_expect_success 'binary files cannot be merged' '
-	test_must_fail git merge-file -p \
-		orig.txt "$TEST_DIRECTORY"/test-binary-1.png new1.txt 2> merge.err &&
-	grep "Cannot merge binary files" merge.err
-'
-
-sed -e "s/deerit.\$/deerit;/" -e "s/me;\$/me./" < new5.txt > new6.txt
-sed -e "s/deerit.\$/deerit,/" -e "s/me;\$/me,/" < new5.txt > new7.txt
-
-test_expect_success 'MERGE_ZEALOUS simplifies non-conflicts' '
-
-	test_must_fail git merge-file -p new6.txt new5.txt new7.txt > output &&
-	test 1 = $(grep ======= < output | wc -l)
-
-'
-
-sed -e 's/deerit./&%%%%/' -e "s/locavit,/locavit;/"< new6.txt | tr '%' '\012' > new8.txt
-sed -e 's/deerit./&%%%%/' -e "s/locavit,/locavit --/" < new7.txt | tr '%' '\012' > new9.txt
-
-test_expect_success 'ZEALOUS_ALNUM' '
-
-	test_must_fail git merge-file -p \
-		new8.txt new5.txt new9.txt > merge.out &&
-	test 1 = $(grep ======= < merge.out | wc -l)
-
-'
-
-cat >expect <<\EOF
-Dominus regit me,
-<<<<<<< new8.txt
-et nihil mihi deerit;
-
-
-
-
-In loco pascuae ibi me collocavit;
-super aquam refectionis educavit me.
-||||||| new5.txt
-et nihil mihi deerit.
-In loco pascuae ibi me collocavit,
-super aquam refectionis educavit me;
-=======
-et nihil mihi deerit,
-
-
-
-
-In loco pascuae ibi me collocavit --
-super aquam refectionis educavit me,
->>>>>>> new9.txt
-animam meam convertit,
-deduxit me super semitas jusitiae,
-propter nomen suum.
-Nam et si ambulavero in medio umbrae mortis,
-non timebo mala, quoniam TU mecum es:
-virga tua et baculus tuus ipsa me consolata sunt.
-EOF
-
-test_expect_success '"diff3 -m" style output (1)' '
-	test_must_fail git merge-file -p --diff3 \
-		new8.txt new5.txt new9.txt >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success '"diff3 -m" style output (2)' '
-	git config merge.conflictstyle diff3 &&
-	test_must_fail git merge-file -p \
-		new8.txt new5.txt new9.txt >actual &&
-	test_cmp expect actual
-'
-
-cat >expect <<\EOF
-Dominus regit me,
-<<<<<<<<<< new8.txt
-et nihil mihi deerit;
-
-
-
-
-In loco pascuae ibi me collocavit;
-super aquam refectionis educavit me.
-|||||||||| new5.txt
-et nihil mihi deerit.
-In loco pascuae ibi me collocavit,
-super aquam refectionis educavit me;
-==========
-et nihil mihi deerit,
-
-
-
-
-In loco pascuae ibi me collocavit --
-super aquam refectionis educavit me,
->>>>>>>>>> new9.txt
-animam meam convertit,
-deduxit me super semitas jusitiae,
-propter nomen suum.
-Nam et si ambulavero in medio umbrae mortis,
-non timebo mala, quoniam TU mecum es:
-virga tua et baculus tuus ipsa me consolata sunt.
-EOF
-
-test_expect_success 'marker size' '
-	test_must_fail git merge-file -p --marker-size=10 \
-		new8.txt new5.txt new9.txt >actual &&
-	test_cmp expect actual
-'
-
-printf "line1\nline2\nline3" >nolf-orig.txt
-printf "line1\nline2\nline3x" >nolf-diff1.txt
-printf "line1\nline2\nline3y" >nolf-diff2.txt
-
-test_expect_success 'conflict at EOF without LF resolved by --ours' \
-	'git merge-file -p --ours nolf-diff1.txt nolf-orig.txt nolf-diff2.txt >output.txt &&
-	 printf "line1\nline2\nline3x" >expect.txt &&
-	 test_cmp expect.txt output.txt'
-
-test_expect_success 'conflict at EOF without LF resolved by --theirs' \
-	'git merge-file -p --theirs nolf-diff1.txt nolf-orig.txt nolf-diff2.txt >output.txt &&
-	 printf "line1\nline2\nline3y" >expect.txt &&
-	 test_cmp expect.txt output.txt'
-
-test_expect_success 'conflict at EOF without LF resolved by --union' \
-	'git merge-file -p --union nolf-diff1.txt nolf-orig.txt nolf-diff2.txt >output.txt &&
-	 printf "line1\nline2\nline3x\nline3y" >expect.txt &&
-	 test_cmp expect.txt output.txt'
-
-test_expect_success 'conflict sections match existing line endings' '
-	printf "1\\r\\n2\\r\\n3" >crlf-orig.txt &&
-	printf "1\\r\\n2\\r\\n4" >crlf-diff1.txt &&
-	printf "1\\r\\n2\\r\\n5" >crlf-diff2.txt &&
-	test_must_fail git -c core.eol=crlf merge-file -p \
-		crlf-diff1.txt crlf-orig.txt crlf-diff2.txt >crlf.txt &&
-	test $(tr "\015" Q <crlf.txt | grep "^[<=>].*Q$" | wc -l) = 3 &&
-	test $(tr "\015" Q <crlf.txt | grep "[345]Q$" | wc -l) = 3 &&
-	test_must_fail git -c core.eol=crlf merge-file -p \
-		nolf-diff1.txt nolf-orig.txt nolf-diff2.txt >nolf.txt &&
-	test $(tr "\015" Q <nolf.txt | grep "^[<=>].*Q$" | wc -l) = 0
-'
-
-test_done
diff --git a/third_party/git/t/t6024-recursive-merge.sh b/third_party/git/t/t6024-recursive-merge.sh
deleted file mode 100755
index 27c7de90ce..0000000000
--- a/third_party/git/t/t6024-recursive-merge.sh
+++ /dev/null
@@ -1,122 +0,0 @@
-#!/bin/sh
-
-test_description='Test merge without common ancestors'
-. ./test-lib.sh
-
-# This scenario is based on a real-world repository of Shawn Pearce.
-
-# 1 - A - D - F
-#   \   X   /
-#     B   X
-#       X   \
-# 2 - C - E - G
-
-GIT_COMMITTER_DATE="2006-12-12 23:28:00 +0100"
-export GIT_COMMITTER_DATE
-
-test_expect_success "setup tests" '
-echo 1 > a1 &&
-git add a1 &&
-GIT_AUTHOR_DATE="2006-12-12 23:00:00" git commit -m 1 a1 &&
-
-git checkout -b A master &&
-echo A > a1 &&
-GIT_AUTHOR_DATE="2006-12-12 23:00:01" git commit -m A a1 &&
-
-git checkout -b B master &&
-echo B > a1 &&
-GIT_AUTHOR_DATE="2006-12-12 23:00:02" git commit -m B a1 &&
-
-git checkout -b D A &&
-git rev-parse B > .git/MERGE_HEAD &&
-echo D > a1 &&
-git update-index a1 &&
-GIT_AUTHOR_DATE="2006-12-12 23:00:03" git commit -m D &&
-
-git symbolic-ref HEAD refs/heads/other &&
-echo 2 > a1 &&
-GIT_AUTHOR_DATE="2006-12-12 23:00:04" git commit -m 2 a1 &&
-
-git checkout -b C &&
-echo C > a1 &&
-GIT_AUTHOR_DATE="2006-12-12 23:00:05" git commit -m C a1 &&
-
-git checkout -b E C &&
-git rev-parse B > .git/MERGE_HEAD &&
-echo E > a1 &&
-git update-index a1 &&
-GIT_AUTHOR_DATE="2006-12-12 23:00:06" git commit -m E &&
-
-git checkout -b G E &&
-git rev-parse A > .git/MERGE_HEAD &&
-echo G > a1 &&
-git update-index a1 &&
-GIT_AUTHOR_DATE="2006-12-12 23:00:07" git commit -m G &&
-
-git checkout -b F D &&
-git rev-parse C > .git/MERGE_HEAD &&
-echo F > a1 &&
-git update-index a1 &&
-GIT_AUTHOR_DATE="2006-12-12 23:00:08" git commit -m F
-'
-
-test_expect_success 'combined merge conflicts' '
-	test_must_fail env GIT_TEST_COMMIT_GRAPH=0 git merge -m final G
-'
-
-cat > expect << EOF
-<<<<<<< HEAD
-F
-=======
-G
->>>>>>> G
-EOF
-
-test_expect_success "result contains a conflict" "test_cmp expect a1"
-
-git ls-files --stage > out
-cat > expect << EOF
-100644 ec3fe2a791706733f2d8fa7ad45d9a9672031f5e 1	a1
-100644 cf84443e49e1b366fac938711ddf4be2d4d1d9e9 2	a1
-100644 fd7923529855d0b274795ae3349c5e0438333979 3	a1
-EOF
-
-test_expect_success "virtual trees were processed" "test_cmp expect out"
-
-test_expect_success 'refuse to merge binary files' '
-	git reset --hard &&
-	printf "\0" > binary-file &&
-	git add binary-file &&
-	git commit -m binary &&
-	git checkout G &&
-	printf "\0\0" > binary-file &&
-	git add binary-file &&
-	git commit -m binary2 &&
-	test_must_fail git merge F > merge.out 2> merge.err &&
-	grep "Cannot merge binary files: binary-file (HEAD vs. F)" merge.err
-'
-
-test_expect_success 'mark rename/delete as unmerged' '
-
-	git reset --hard &&
-	git checkout -b delete &&
-	git rm a1 &&
-	test_tick &&
-	git commit -m delete &&
-	git checkout -b rename HEAD^ &&
-	git mv a1 a2 &&
-	test_tick &&
-	git commit -m rename &&
-	test_must_fail git merge delete &&
-	test 1 = $(git ls-files --unmerged | wc -l) &&
-	git rev-parse --verify :2:a2 &&
-	test_must_fail git rev-parse --verify :3:a2 &&
-	git checkout -f delete &&
-	test_must_fail git merge rename &&
-	test 1 = $(git ls-files --unmerged | wc -l) &&
-	test_must_fail git rev-parse --verify :2:a2 &&
-	git rev-parse --verify :3:a2
-
-'
-
-test_done
diff --git a/third_party/git/t/t6025-merge-symlinks.sh b/third_party/git/t/t6025-merge-symlinks.sh
deleted file mode 100755
index 433c4de08f..0000000000
--- a/third_party/git/t/t6025-merge-symlinks.sh
+++ /dev/null
@@ -1,61 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2007 Johannes Sixt
-#
-
-test_description='merging symlinks on filesystem w/o symlink support.
-
-This tests that git merge-recursive writes merge results as plain files
-if core.symlinks is false.'
-
-. ./test-lib.sh
-
-test_expect_success \
-'setup' '
-git config core.symlinks false &&
-> file &&
-git add file &&
-git commit -m initial &&
-git branch b-symlink &&
-git branch b-file &&
-l=$(printf file | git hash-object -t blob -w --stdin) &&
-echo "120000 $l	symlink" | git update-index --index-info &&
-git commit -m master &&
-git checkout b-symlink &&
-l=$(printf file-different | git hash-object -t blob -w --stdin) &&
-echo "120000 $l	symlink" | git update-index --index-info &&
-git commit -m b-symlink &&
-git checkout b-file &&
-echo plain-file > symlink &&
-git add symlink &&
-git commit -m b-file'
-
-test_expect_success \
-'merge master into b-symlink, which has a different symbolic link' '
-git checkout b-symlink &&
-test_must_fail git merge master'
-
-test_expect_success \
-'the merge result must be a file' '
-test -f symlink'
-
-test_expect_success \
-'merge master into b-file, which has a file instead of a symbolic link' '
-git reset --hard && git checkout b-file &&
-test_must_fail git merge master'
-
-test_expect_success \
-'the merge result must be a file' '
-test -f symlink'
-
-test_expect_success \
-'merge b-file, which has a file instead of a symbolic link, into master' '
-git reset --hard &&
-git checkout master &&
-test_must_fail git merge b-file'
-
-test_expect_success \
-'the merge result must be a file' '
-test -f symlink'
-
-test_done
diff --git a/third_party/git/t/t6030-bisect-porcelain.sh b/third_party/git/t/t6030-bisect-porcelain.sh
index bdc42e9440..aa226381be 100755
--- a/third_party/git/t/t6030-bisect-porcelain.sh
+++ b/third_party/git/t/t6030-bisect-porcelain.sh
@@ -82,6 +82,13 @@ test_expect_success 'bisect fails if given any junk instead of revs' '
 	git bisect bad $HASH4
 '
 
+test_expect_success 'bisect start without -- takes unknown arg as pathspec' '
+	git bisect reset &&
+	git bisect start foo bar &&
+	grep foo ".git/BISECT_NAMES" &&
+	grep bar ".git/BISECT_NAMES"
+'
+
 test_expect_success 'bisect reset: back in the master branch' '
 	git bisect reset &&
 	echo "* master" > branch.expect &&
@@ -148,7 +155,7 @@ test_expect_success 'bisect start: no ".git/BISECT_START" created if junk rev' '
 	test_must_fail git bisect start $HASH4 foo -- &&
 	git branch > branch.output &&
 	grep "* other" branch.output > /dev/null &&
-	test_must_fail test -e .git/BISECT_START
+	test_path_is_missing .git/BISECT_START
 '
 
 test_expect_success 'bisect start: existing ".git/BISECT_START" not modified if junk rev' '
@@ -166,7 +173,7 @@ test_expect_success 'bisect start: no ".git/BISECT_START" if mistaken rev' '
 	test_must_fail git bisect start $HASH1 $HASH4 -- &&
 	git branch > branch.output &&
 	grep "* other" branch.output > /dev/null &&
-	test_must_fail test -e .git/BISECT_START
+	test_path_is_missing .git/BISECT_START
 '
 
 test_expect_success 'bisect start: no ".git/BISECT_START" if checkout error' '
@@ -175,7 +182,7 @@ test_expect_success 'bisect start: no ".git/BISECT_START" if checkout error' '
 	git branch &&
 	git branch > branch.output &&
 	grep "* other" branch.output > /dev/null &&
-	test_must_fail test -e .git/BISECT_START &&
+	test_path_is_missing .git/BISECT_START &&
 	test -z "$(git for-each-ref "refs/bisect/*")" &&
 	git checkout HEAD hello
 '
@@ -243,32 +250,30 @@ test_expect_success 'bisect skip: with commit both bad and skipped' '
 '
 
 # We want to automatically find the commit that
-# introduced "Another" into hello.
-test_expect_success \
-    '"git bisect run" simple case' \
-    'echo "#"\!"/bin/sh" > test_script.sh &&
-     echo "grep Another hello > /dev/null" >> test_script.sh &&
-     echo "test \$? -ne 0" >> test_script.sh &&
-     chmod +x test_script.sh &&
-     git bisect start &&
-     git bisect good $HASH1 &&
-     git bisect bad $HASH4 &&
-     git bisect run ./test_script.sh > my_bisect_log.txt &&
-     grep "$HASH3 is the first bad commit" my_bisect_log.txt &&
-     git bisect reset'
+# added "Another" into hello.
+test_expect_success '"git bisect run" simple case' '
+	write_script test_script.sh <<-\EOF &&
+	! grep Another hello >/dev/null
+	EOF
+	git bisect start &&
+	git bisect good $HASH1 &&
+	git bisect bad $HASH4 &&
+	git bisect run ./test_script.sh >my_bisect_log.txt &&
+	grep "$HASH3 is the first bad commit" my_bisect_log.txt &&
+	git bisect reset
+'
 
 # We want to automatically find the commit that
-# introduced "Ciao" into hello.
-test_expect_success \
-    '"git bisect run" with more complex "git bisect start"' \
-    'echo "#"\!"/bin/sh" > test_script.sh &&
-     echo "grep Ciao hello > /dev/null" >> test_script.sh &&
-     echo "test \$? -ne 0" >> test_script.sh &&
-     chmod +x test_script.sh &&
-     git bisect start $HASH4 $HASH1 &&
-     git bisect run ./test_script.sh > my_bisect_log.txt &&
-     grep "$HASH4 is the first bad commit" my_bisect_log.txt &&
-     git bisect reset'
+# added "Ciao" into hello.
+test_expect_success '"git bisect run" with more complex "git bisect start"' '
+	write_script test_script.sh <<-\EOF &&
+	! grep Ciao hello >/dev/null
+	EOF
+	git bisect start $HASH4 $HASH1 &&
+	git bisect run ./test_script.sh >my_bisect_log.txt &&
+	grep "$HASH4 is the first bad commit" my_bisect_log.txt &&
+	git bisect reset
+'
 
 # $HASH1 is good, $HASH5 is bad, we skip $HASH3
 # but $HASH4 is good,
@@ -295,24 +300,17 @@ HASH6=
 test_expect_success 'bisect run & skip: cannot tell between 2' '
 	add_line_into_file "6: Yet a line." hello &&
 	HASH6=$(git rev-parse --verify HEAD) &&
-	echo "#"\!"/bin/sh" > test_script.sh &&
-	echo "sed -ne \\\$p hello | grep Ciao > /dev/null && exit 125" >> test_script.sh &&
-	echo "grep line hello > /dev/null" >> test_script.sh &&
-	echo "test \$? -ne 0" >> test_script.sh &&
-	chmod +x test_script.sh &&
+	write_script test_script.sh <<-\EOF &&
+	sed -ne \$p hello | grep Ciao >/dev/null && exit 125
+	! grep line hello >/dev/null
+	EOF
 	git bisect start $HASH6 $HASH1 &&
-	if git bisect run ./test_script.sh > my_bisect_log.txt
-	then
-		echo Oops, should have failed.
-		false
-	else
-		test $? -eq 2 &&
-		grep "first bad commit could be any of" my_bisect_log.txt &&
-		! grep $HASH3 my_bisect_log.txt &&
-		! grep $HASH6 my_bisect_log.txt &&
-		grep $HASH4 my_bisect_log.txt &&
-		grep $HASH5 my_bisect_log.txt
-	fi
+	test_expect_code 2 git bisect run ./test_script.sh >my_bisect_log.txt &&
+	grep "first bad commit could be any of" my_bisect_log.txt &&
+	! grep $HASH3 my_bisect_log.txt &&
+	! grep $HASH6 my_bisect_log.txt &&
+	grep $HASH4 my_bisect_log.txt &&
+	grep $HASH5 my_bisect_log.txt
 '
 
 HASH7=
@@ -320,14 +318,13 @@ test_expect_success 'bisect run & skip: find first bad' '
 	git bisect reset &&
 	add_line_into_file "7: Should be the last line." hello &&
 	HASH7=$(git rev-parse --verify HEAD) &&
-	echo "#"\!"/bin/sh" > test_script.sh &&
-	echo "sed -ne \\\$p hello | grep Ciao > /dev/null && exit 125" >> test_script.sh &&
-	echo "sed -ne \\\$p hello | grep day > /dev/null && exit 125" >> test_script.sh &&
-	echo "grep Yet hello > /dev/null" >> test_script.sh &&
-	echo "test \$? -ne 0" >> test_script.sh &&
-	chmod +x test_script.sh &&
+	write_script test_script.sh <<-\EOF &&
+	sed -ne \$p hello | grep Ciao >/dev/null && exit 125
+	sed -ne \$p hello | grep day >/dev/null && exit 125
+	! grep Yet hello >/dev/null
+	EOF
 	git bisect start $HASH7 $HASH1 &&
-	git bisect run ./test_script.sh > my_bisect_log.txt &&
+	git bisect run ./test_script.sh >my_bisect_log.txt &&
 	grep "$HASH6 is the first bad commit" my_bisect_log.txt
 '
 
@@ -458,6 +455,24 @@ test_expect_success 'many merge bases creation' '
 	grep "$SIDE_HASH5" merge_bases.txt
 '
 
+# We want to automatically find the merge that
+# added "line" into hello.
+test_expect_success '"git bisect run --first-parent" simple case' '
+	git rev-list --first-parent $B_HASH ^$HASH4 >first_parent_chain.txt &&
+	write_script test_script.sh <<-\EOF &&
+	grep $(git rev-parse HEAD) first_parent_chain.txt || exit -1
+	! grep line hello >/dev/null
+	EOF
+	git bisect start --first-parent &&
+	test_path_is_file ".git/BISECT_FIRST_PARENT" &&
+	git bisect good $HASH4 &&
+	git bisect bad $B_HASH &&
+	git bisect run ./test_script.sh >my_bisect_log.txt &&
+	grep "$B_HASH is the first bad commit" my_bisect_log.txt &&
+	git bisect reset &&
+	test_path_is_missing .git/BISECT_FIRST_PARENT
+'
+
 test_expect_success 'good merge bases when good and bad are siblings' '
 	git bisect start "$B_HASH" "$A_HASH" > my_bisect_log.txt &&
 	test_i18ngrep "merge base must be tested" my_bisect_log.txt &&
@@ -482,10 +497,10 @@ test_expect_success 'optimized merge base checks' '
 	git bisect good > my_bisect_log2.txt &&
 	test -f ".git/BISECT_ANCESTORS_OK" &&
 	test "$HASH6" = $(git rev-parse --verify HEAD) &&
-	git bisect bad > my_bisect_log3.txt &&
+	git bisect bad &&
 	git bisect good "$A_HASH" > my_bisect_log4.txt &&
 	test_i18ngrep "merge base must be tested" my_bisect_log4.txt &&
-	test_must_fail test -f ".git/BISECT_ANCESTORS_OK"
+	test_path_is_missing ".git/BISECT_ANCESTORS_OK"
 '
 
 # This creates another side branch called "parallel" with some files
@@ -792,6 +807,13 @@ test_expect_success 'bisect replay with old and new' '
 	git bisect reset
 '
 
+test_expect_success 'bisect replay with CRLF log' '
+	append_cr <log_to_replay.txt >log_to_replay_crlf.txt &&
+	git bisect replay log_to_replay_crlf.txt >bisect_result_crlf &&
+	grep "$HASH2 is the first new commit" bisect_result_crlf &&
+	git bisect reset
+'
+
 test_expect_success 'bisect cannot mix old/new and good/bad' '
 	git bisect start &&
 	git bisect bad $HASH4 &&
@@ -859,7 +881,9 @@ test_expect_success 'bisect cannot mix terms' '
 
 test_expect_success 'bisect terms rejects invalid terms' '
 	git bisect reset &&
+	test_must_fail git bisect start --term-good &&
 	test_must_fail git bisect start --term-good invalid..term &&
+	test_must_fail git bisect start --term-bad &&
 	test_must_fail git bisect terms --term-bad invalid..term &&
 	test_must_fail git bisect terms --term-good bad &&
 	test_must_fail git bisect terms --term-good old &&
diff --git a/third_party/git/t/t6034-merge-rename-nocruft.sh b/third_party/git/t/t6034-merge-rename-nocruft.sh
deleted file mode 100755
index 89871aa5b0..0000000000
--- a/third_party/git/t/t6034-merge-rename-nocruft.sh
+++ /dev/null
@@ -1,99 +0,0 @@
-#!/bin/sh
-
-test_description='Merge-recursive merging renames'
-. ./test-lib.sh
-
-test_expect_success setup \
-'
-cat >A <<\EOF &&
-a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
-b bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
-c cccccccccccccccccccccccccccccccccccccccccccccccc
-d dddddddddddddddddddddddddddddddddddddddddddddddd
-e eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
-f ffffffffffffffffffffffffffffffffffffffffffffffff
-g gggggggggggggggggggggggggggggggggggggggggggggggg
-h hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
-i iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
-j jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj
-k kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk
-l llllllllllllllllllllllllllllllllllllllllllllllll
-m mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
-n nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
-o oooooooooooooooooooooooooooooooooooooooooooooooo
-EOF
-
-cat >M <<\EOF &&
-A AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-B BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
-C CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
-D DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
-E EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
-F FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
-G GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG
-H HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH
-I IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII
-J JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ
-K KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK
-L LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL
-M MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
-N NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN
-O OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO
-EOF
-
-git add A M &&
-git commit -m "initial has A and M" &&
-git branch white &&
-git branch red &&
-git branch blue &&
-
-git checkout white &&
-sed -e "/^g /s/.*/g : white changes a line/" <A >B &&
-sed -e "/^G /s/.*/G : colored branch changes a line/" <M >N &&
-rm -f A M &&
-git update-index --add --remove A B M N &&
-git commit -m "white renames A->B, M->N" &&
-
-git checkout red &&
-echo created by red >R &&
-git update-index --add R &&
-git commit -m "red creates R" &&
-
-git checkout blue &&
-sed -e "/^o /s/.*/g : blue changes a line/" <A >B &&
-rm -f A &&
-mv B A &&
-git update-index A &&
-git commit -m "blue modify A" &&
-
-git checkout master'
-
-# This test broke in 65ac6e9c3f47807cb603af07a6a9e1a43bc119ae
-test_expect_success 'merge white into red (A->B,M->N)' \
-'
-	git checkout -b red-white red &&
-	git merge white &&
-	git write-tree &&
-	test_path_is_file B &&
-	test_path_is_file N &&
-	test_path_is_file R &&
-	test_path_is_missing A &&
-	test_path_is_missing M
-'
-
-# This test broke in 8371234ecaaf6e14fe3f2082a855eff1bbd79ae9
-test_expect_success 'merge blue into white (A->B, mod A, A untracked)' \
-'
-	git checkout -b white-blue white &&
-	echo dirty >A &&
-	git merge blue &&
-	git write-tree &&
-	test_path_is_file A &&
-	echo dirty >expect &&
-	test_cmp expect A &&
-	test_path_is_file B &&
-	test_path_is_file N &&
-	test_path_is_missing M
-'
-
-test_done
diff --git a/third_party/git/t/t6040-tracking-info.sh b/third_party/git/t/t6040-tracking-info.sh
index ad1922b999..50a934e1b2 100755
--- a/third_party/git/t/t6040-tracking-info.sh
+++ b/third_party/git/t/t6040-tracking-info.sh
@@ -264,25 +264,25 @@ test_expect_success 'fail to track annotated tags' '
 test_expect_success '--set-upstream-to does not change branch' '
 	git branch from-master master &&
 	git branch --set-upstream-to master from-master &&
-	git branch from-master2 master &&
-	test_must_fail git config branch.from-master2.merge > actual &&
-	git rev-list from-master2 &&
-	git update-ref refs/heads/from-master2 from-master2^ &&
-	git rev-parse from-master2 >expect2 &&
-	git branch --set-upstream-to master from-master2 &&
+	git branch from-topic_2 master &&
+	test_must_fail git config branch.from-topic_2.merge > actual &&
+	git rev-list from-topic_2 &&
+	git update-ref refs/heads/from-topic_2 from-topic_2^ &&
+	git rev-parse from-topic_2 >expect2 &&
+	git branch --set-upstream-to master from-topic_2 &&
 	git config branch.from-master.merge > actual &&
-	git rev-parse from-master2 >actual2 &&
+	git rev-parse from-topic_2 >actual2 &&
 	grep -q "^refs/heads/master$" actual &&
 	cmp expect2 actual2
 '
 
 test_expect_success '--set-upstream-to @{-1}' '
 	git checkout follower &&
-	git checkout from-master2 &&
-	git config branch.from-master2.merge > expect2 &&
+	git checkout from-topic_2 &&
+	git config branch.from-topic_2.merge > expect2 &&
 	git branch --set-upstream-to @{-1} from-master &&
 	git config branch.from-master.merge > actual &&
-	git config branch.from-master2.merge > actual2 &&
+	git config branch.from-topic_2.merge > actual2 &&
 	git branch --set-upstream-to follower from-master &&
 	git config branch.from-master.merge > expect &&
 	test_cmp expect2 actual2 &&
diff --git a/third_party/git/t/t6041-bisect-submodule.sh b/third_party/git/t/t6041-bisect-submodule.sh
index 62b8a2e7bb..df1eff0fb8 100755
--- a/third_party/git/t/t6041-bisect-submodule.sh
+++ b/third_party/git/t/t6041-bisect-submodule.sh
@@ -10,7 +10,12 @@ git_bisect () {
 	ls -1pR * >>expect &&
 	tar cf "$TRASH_DIRECTORY/tmp.tar" * &&
 	GOOD=$(git rev-parse --verify HEAD) &&
-	git checkout "$1" &&
+	may_only_be_test_must_fail "$2" &&
+	$2 git checkout "$1" &&
+	if test -n "$2"
+	then
+		return
+	fi &&
 	echo "foo" >bar &&
 	git add bar &&
 	git commit -m "bisect bad" &&
@@ -27,6 +32,6 @@ git_bisect () {
 	git bisect bad $BAD
 }
 
-test_submodule_switch "git_bisect"
+test_submodule_switch_func "git_bisect"
 
 test_done
diff --git a/third_party/git/t/t6050-replace.sh b/third_party/git/t/t6050-replace.sh
index e7e64e085d..c80dc10b8f 100755
--- a/third_party/git/t/t6050-replace.sh
+++ b/third_party/git/t/t6050-replace.sh
@@ -135,7 +135,7 @@ test_expect_success 'tag replaced commit' '
 test_expect_success '"git fsck" works' '
      git fsck master >fsck_master.out &&
      test_i18ngrep "dangling commit $R" fsck_master.out &&
-     test_i18ngrep "dangling tag $(cat .git/refs/tags/mytag)" fsck_master.out &&
+     test_i18ngrep "dangling tag $(git show-ref -s refs/tags/mytag)" fsck_master.out &&
      test -z "$(git fsck)"
 '
 
diff --git a/third_party/git/t/t6100-rev-list-in-order.sh b/third_party/git/t/t6100-rev-list-in-order.sh
index b2bb0a7f61..e934bc239c 100755
--- a/third_party/git/t/t6100-rev-list-in-order.sh
+++ b/third_party/git/t/t6100-rev-list-in-order.sh
@@ -22,7 +22,7 @@ test_expect_success 'setup a commit history with trees, blobs' '
 
 test_expect_success 'rev-list --in-commit-order' '
 	git rev-list --in-commit-order --objects HEAD >actual.raw &&
-	cut -c 1-40 >actual <actual.raw &&
+	cut -d" " -f1 >actual <actual.raw &&
 
 	git cat-file --batch-check="%(objectname)" >expect.raw <<-\EOF &&
 		HEAD^{commit}
@@ -49,7 +49,7 @@ test_expect_success 'rev-list --in-commit-order' '
 
 test_expect_success 'rev-list lists blobs and trees after commits' '
 	git rev-list --objects HEAD >actual.raw &&
-	cut -c 1-40 >actual <actual.raw &&
+	cut -d" " -f1 >actual <actual.raw &&
 
 	git cat-file --batch-check="%(objectname)" >expect.raw <<-\EOF &&
 		HEAD^{commit}
diff --git a/third_party/git/t/t6101-rev-parse-parents.sh b/third_party/git/t/t6101-rev-parse-parents.sh
index 7683e4a114..7531262a5e 100755
--- a/third_party/git/t/t6101-rev-parse-parents.sh
+++ b/third_party/git/t/t6101-rev-parse-parents.sh
@@ -34,7 +34,7 @@ test_expect_success 'setup' '
 '
 
 test_expect_success 'start is valid' '
-	git rev-parse start | grep "^[0-9a-f]\{40\}$"
+	git rev-parse start | grep "^$OID_REGEX$"
 '
 
 test_expect_success 'start^0' '
diff --git a/third_party/git/t/t6102-rev-list-unexpected-objects.sh b/third_party/git/t/t6102-rev-list-unexpected-objects.sh
index 28611c978e..52cde097dd 100755
--- a/third_party/git/t/t6102-rev-list-unexpected-objects.sh
+++ b/third_party/git/t/t6102-rev-list-unexpected-objects.sh
@@ -52,7 +52,7 @@ test_expect_success 'traverse unexpected non-commit parent (lone)' '
 '
 
 test_expect_success 'traverse unexpected non-commit parent (seen)' '
-	test_must_fail git rev-list --objects $commit $broken_commit \
+	test_must_fail git rev-list --objects $blob $broken_commit \
 		>output 2>&1 &&
 	test_i18ngrep "not a commit" output
 '
diff --git a/third_party/git/t/t6112-rev-list-filters-objects.sh b/third_party/git/t/t6112-rev-list-filters-objects.sh
index acd7f5ab80..de0e5a5d36 100755
--- a/third_party/git/t/t6112-rev-list-filters-objects.sh
+++ b/third_party/git/t/t6112-rev-list-filters-objects.sh
@@ -278,7 +278,19 @@ test_expect_success 'verify skipping tree iteration when not collecting omits' '
 	test_line_count = 2 actual &&
 
 	# Make sure no other trees were considered besides the root.
-	! grep "Skipping contents of tree [^.]" filter_trace
+	! grep "Skipping contents of tree [^.]" filter_trace &&
+
+	# Try this again with "combine:". If both sub-filters are skipping
+	# trees, the composite filter should also skip trees. This is not
+	# important unless the user does combine:tree:X+tree:Y or another filter
+	# besides "tree:" is implemented in the future which can skip trees.
+	GIT_TRACE=1 git -C r3 rev-list \
+		--objects --filter=combine:tree:1+tree:3 HEAD 2>filter_trace &&
+
+	# Only skip the dir1/ tree, which is shared between the two commits.
+	grep "Skipping contents of tree " filter_trace >actual &&
+	test_write_lines "Skipping contents of tree dir1/..." >expected &&
+	test_cmp expected actual
 '
 
 # Test tree:# filters.
@@ -330,6 +342,148 @@ test_expect_success 'verify tree:3 includes everything expected' '
 	test_line_count = 10 actual
 '
 
+test_expect_success 'combine:... for a simple combination' '
+	git -C r3 rev-list --objects --filter=combine:tree:2+blob:none HEAD \
+		>actual &&
+
+	expect_has HEAD "" &&
+	expect_has HEAD~1 "" &&
+	expect_has HEAD dir1 &&
+
+	# There are also 2 commit objects
+	test_line_count = 5 actual &&
+
+	cp actual expected &&
+
+	# Try again using repeated --filter - this is equivalent to a manual
+	# combine with "combine:...+..."
+	git -C r3 rev-list --objects --filter=combine:tree:2 \
+		--filter=blob:none HEAD >actual &&
+
+	test_cmp expected actual
+'
+
+test_expect_success 'combine:... with URL encoding' '
+	git -C r3 rev-list --objects \
+		--filter=combine:tree%3a2+blob:%6Eon%65 HEAD >actual &&
+
+	expect_has HEAD "" &&
+	expect_has HEAD~1 "" &&
+	expect_has HEAD dir1 &&
+
+	# There are also 2 commit objects
+	test_line_count = 5 actual
+'
+
+expect_invalid_filter_spec () {
+	spec="$1" &&
+	err="$2" &&
+
+	test_must_fail git -C r3 rev-list --objects --filter="$spec" HEAD \
+		>actual 2>actual_stderr &&
+	test_must_be_empty actual &&
+	test_i18ngrep "$err" actual_stderr
+}
+
+test_expect_success 'combine:... while URL-encoding things that should not be' '
+	expect_invalid_filter_spec combine%3Atree:2+blob:none \
+		"invalid filter-spec"
+'
+
+test_expect_success 'combine: with nothing after the :' '
+	expect_invalid_filter_spec combine: "expected something after combine:"
+'
+
+test_expect_success 'parse error in first sub-filter in combine:' '
+	expect_invalid_filter_spec combine:tree:asdf+blob:none \
+		"expected .tree:<depth>."
+'
+
+test_expect_success 'combine:... with non-encoded reserved chars' '
+	expect_invalid_filter_spec combine:tree:2+sparse:@xyz \
+		"must escape char in sub-filter-spec: .@." &&
+	expect_invalid_filter_spec combine:tree:2+sparse:\` \
+		"must escape char in sub-filter-spec: .\`." &&
+	expect_invalid_filter_spec combine:tree:2+sparse:~abc \
+		"must escape char in sub-filter-spec: .\~."
+'
+
+test_expect_success 'validate err msg for "combine:<valid-filter>+"' '
+	expect_invalid_filter_spec combine:tree:2+ "expected .tree:<depth>."
+'
+
+test_expect_success 'combine:... with edge-case hex digits: Ff Aa 0 9' '
+	git -C r3 rev-list --objects --filter="combine:tree:2+bl%6Fb:n%6fne" \
+		HEAD >actual &&
+	test_line_count = 5 actual &&
+	git -C r3 rev-list --objects --filter="combine:tree%3A2+blob%3anone" \
+		HEAD >actual &&
+	test_line_count = 5 actual &&
+	git -C r3 rev-list --objects --filter="combine:tree:%30" HEAD >actual &&
+	test_line_count = 2 actual &&
+	git -C r3 rev-list --objects --filter="combine:tree:%39+blob:none" \
+		HEAD >actual &&
+	test_line_count = 5 actual
+'
+
+test_expect_success 'add sparse pattern blobs whose paths have reserved chars' '
+	cp r3/pattern r3/pattern1+renamed% &&
+	cp r3/pattern "r3/p;at%ter+n" &&
+	cp r3/pattern r3/^~pattern &&
+	git -C r3 add pattern1+renamed% "p;at%ter+n" ^~pattern &&
+	git -C r3 commit -m "add sparse pattern files with reserved chars"
+'
+
+test_expect_success 'combine:... with more than two sub-filters' '
+	git -C r3 rev-list --objects \
+		--filter=combine:tree:3+blob:limit=40+sparse:oid=master:pattern \
+		HEAD >actual &&
+
+	expect_has HEAD "" &&
+	expect_has HEAD~1 "" &&
+	expect_has HEAD~2 "" &&
+	expect_has HEAD dir1 &&
+	expect_has HEAD dir1/sparse1 &&
+	expect_has HEAD dir1/sparse2 &&
+
+	# Should also have 3 commits
+	test_line_count = 9 actual &&
+
+	# Try again, this time making sure the last sub-filter is only
+	# URL-decoded once.
+	cp actual expect &&
+
+	git -C r3 rev-list --objects \
+		--filter=combine:tree:3+blob:limit=40+sparse:oid=master:pattern1%2brenamed%25 \
+		HEAD >actual &&
+	test_cmp expect actual &&
+
+	# Use the same composite filter again, but with a pattern file name that
+	# requires encoding multiple characters, and use implicit filter
+	# combining.
+	test_when_finished "rm -f trace1" &&
+	GIT_TRACE=$(pwd)/trace1 git -C r3 rev-list --objects \
+		--filter=tree:3 --filter=blob:limit=40 \
+		--filter=sparse:oid="master:p;at%ter+n" \
+		HEAD >actual &&
+
+	test_cmp expect actual &&
+	grep "Add to combine filter-spec: sparse:oid=master:p%3bat%25ter%2bn" \
+		trace1 &&
+
+	# Repeat the above test, but this time, the characters to encode are in
+	# the LHS of the combined filter.
+	test_when_finished "rm -f trace2" &&
+	GIT_TRACE=$(pwd)/trace2 git -C r3 rev-list --objects \
+		--filter=sparse:oid=master:^~pattern \
+		--filter=tree:3 --filter=blob:limit=40 \
+		HEAD >actual &&
+
+	test_cmp expect actual &&
+	grep "Add to combine filter-spec: sparse:oid=master:%5e%7epattern" \
+		trace2
+'
+
 # Test provisional omit collection logic with a repo that has objects appearing
 # at multiple depths - first deeper than the filter's threshold, then shallow.
 
@@ -373,6 +527,37 @@ test_expect_success 'verify skipping tree iteration when collecting omits' '
 	test_cmp expect actual
 '
 
+test_expect_success 'setup r5' '
+	git init r5 &&
+	mkdir -p r5/subdir &&
+
+	echo 1     >r5/short-root          &&
+	echo 12345 >r5/long-root           &&
+	echo a     >r5/subdir/short-subdir &&
+	echo abcde >r5/subdir/long-subdir  &&
+
+	git -C r5 add short-root long-root subdir &&
+	git -C r5 commit -m "commit msg"
+'
+
+test_expect_success 'verify collecting omits in combined: filter' '
+	# Note that this test guards against the naive implementation of simply
+	# giving both filters the same "omits" set and expecting it to
+	# automatically merge them.
+	git -C r5 rev-list --objects --quiet --filter-print-omitted \
+		--filter=combine:tree:2+blob:limit=3 HEAD >actual &&
+
+	# Expect 0 trees/commits, 3 blobs omitted (all blobs except short-root)
+	omitted_1=$(echo 12345 | git hash-object --stdin) &&
+	omitted_2=$(echo a     | git hash-object --stdin) &&
+	omitted_3=$(echo abcde | git hash-object --stdin) &&
+
+	grep ~$omitted_1 actual &&
+	grep ~$omitted_2 actual &&
+	grep ~$omitted_3 actual &&
+	test_line_count = 3 actual
+'
+
 # Test tree:<depth> where a tree is iterated to twice - once where a subentry is
 # too deep to be included, and again where the blob inside it is shallow enough
 # to be included. This makes sure we don't use LOFR_MARK_SEEN incorrectly (we
@@ -441,11 +626,4 @@ test_expect_success 'expand blob limit in protocol' '
 	grep "blob:limit=1024" trace
 '
 
-test_expect_success 'expand tree depth limit in protocol' '
-	GIT_TRACE_PACKET="$(pwd)/tree_trace" git -c protocol.version=2 clone \
-		--filter=tree:0k "file://$(pwd)/r2" tree &&
-	! grep "tree:0k" tree_trace &&
-	grep "tree:0" tree_trace
-'
-
 test_done
diff --git a/third_party/git/t/t6113-rev-list-bitmap-filters.sh b/third_party/git/t/t6113-rev-list-bitmap-filters.sh
new file mode 100755
index 0000000000..2b551e6fd0
--- /dev/null
+++ b/third_party/git/t/t6113-rev-list-bitmap-filters.sh
@@ -0,0 +1,77 @@
+#!/bin/sh
+
+test_description='rev-list combining bitmaps and filters'
+. ./test-lib.sh
+
+test_expect_success 'set up bitmapped repo' '
+	# one commit will have bitmaps, the other will not
+	test_commit one &&
+	test_commit much-larger-blob-one &&
+	git repack -adb &&
+	test_commit two &&
+	test_commit much-larger-blob-two
+'
+
+test_expect_success 'filters fallback to non-bitmap traversal' '
+	# use a path-based filter, since they are inherently incompatible with
+	# bitmaps (i.e., this test will never get confused by later code to
+	# combine the features)
+	filter=$(echo "!one" | git hash-object -w --stdin) &&
+	git rev-list --objects --filter=sparse:oid=$filter HEAD >expect &&
+	git rev-list --use-bitmap-index \
+		     --objects --filter=sparse:oid=$filter HEAD >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'blob:none filter' '
+	git rev-list --objects --filter=blob:none HEAD >expect &&
+	git rev-list --use-bitmap-index \
+		     --objects --filter=blob:none HEAD >actual &&
+	test_bitmap_traversal expect actual
+'
+
+test_expect_success 'blob:none filter with specified blob' '
+	git rev-list --objects --filter=blob:none HEAD HEAD:two.t >expect &&
+	git rev-list --use-bitmap-index \
+		     --objects --filter=blob:none HEAD HEAD:two.t >actual &&
+	test_bitmap_traversal expect actual
+'
+
+test_expect_success 'blob:limit filter' '
+	git rev-list --objects --filter=blob:limit=5 HEAD >expect &&
+	git rev-list --use-bitmap-index \
+		     --objects --filter=blob:limit=5 HEAD >actual &&
+	test_bitmap_traversal expect actual
+'
+
+test_expect_success 'blob:limit filter with specified blob' '
+	git rev-list --objects --filter=blob:limit=5 \
+		     HEAD HEAD:much-larger-blob-two.t >expect &&
+	git rev-list --use-bitmap-index \
+		     --objects --filter=blob:limit=5 \
+		     HEAD HEAD:much-larger-blob-two.t >actual &&
+	test_bitmap_traversal expect actual
+'
+
+test_expect_success 'tree:0 filter' '
+	git rev-list --objects --filter=tree:0 HEAD >expect &&
+	git rev-list --use-bitmap-index \
+		     --objects --filter=tree:0 HEAD >actual &&
+	test_bitmap_traversal expect actual
+'
+
+test_expect_success 'tree:0 filter with specified blob, tree' '
+	git rev-list --objects --filter=tree:0 HEAD HEAD:two.t >expect &&
+	git rev-list --use-bitmap-index \
+		     --objects --filter=tree:0 HEAD HEAD:two.t >actual &&
+	test_bitmap_traversal expect actual
+'
+
+test_expect_success 'tree:1 filter' '
+	git rev-list --objects --filter=tree:1 HEAD >expect &&
+	git rev-list --use-bitmap-index \
+		     --objects --filter=tree:1 HEAD >actual &&
+	test_cmp expect actual
+'
+
+test_done
diff --git a/third_party/git/t/t6120-describe.sh b/third_party/git/t/t6120-describe.sh
index 2b883d8174..f822d5d328 100755
--- a/third_party/git/t/t6120-describe.sh
+++ b/third_party/git/t/t6120-describe.sh
@@ -1,28 +1,27 @@
 #!/bin/sh
 
-test_description='test describe
+test_description='test describe'
+
+#  o---o-----o----o----o-------o----x
+#       \   D,R   e           /
+#        \---o-------------o-'
+#         \  B            /
+#          `-o----o----o-'
+#                 A    c
+#
+# First parent of a merge commit is on the same line, second parent below.
 
-                       B
-        .--------------o----o----o----x
-       /                   /    /
- o----o----o----o----o----.    /
-       \        A    c        /
-        .------------o---o---o
-                   D,R   e
-'
 . ./test-lib.sh
 
 check_describe () {
 	expect="$1"
 	shift
-	R=$(git describe "$@" 2>err.actual)
-	S=$?
-	cat err.actual >&3
-	test_expect_success "describe $*" '
-	test $S = 0 &&
+	describe_opts="$@"
+	test_expect_success "describe $describe_opts" '
+	R=$(git describe $describe_opts 2>err.actual) &&
 	case "$R" in
 	$expect)	echo happy ;;
-	*)	echo "Oops - $R is not $expect";
+	*)	echo "Oops - $R is not $expect" &&
 		false ;;
 	esac
 	'
@@ -130,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
 '
@@ -382,7 +399,7 @@ test_expect_success 'describe tag object' '
 	test_i18ngrep "fatal: test-blob-1 is neither a commit nor blob" actual
 '
 
-test_expect_failure ULIMIT_STACK_SIZE 'name-rev works in a deep repo' '
+test_expect_success ULIMIT_STACK_SIZE 'name-rev works in a deep repo' '
 	i=1 &&
 	while test $i -lt 8000
 	do
@@ -424,4 +441,111 @@ test_expect_success 'describe complains about missing object' '
 	test_must_fail git describe $ZERO_OID
 '
 
+test_expect_success 'name-rev a rev shortly after epoch' '
+	test_when_finished "git checkout master" &&
+
+	git checkout --orphan no-timestamp-underflow &&
+	# Any date closer to epoch than the CUTOFF_DATE_SLOP constant
+	# in builtin/name-rev.c.
+	GIT_COMMITTER_DATE="@1234 +0000" \
+	git commit -m "committer date shortly after epoch" &&
+	old_commit_oid=$(git rev-parse HEAD) &&
+
+	echo "$old_commit_oid no-timestamp-underflow" >expect &&
+	git name-rev $old_commit_oid >actual &&
+	test_cmp expect actual
+'
+
+# A--------------master
+#  \            /
+#   \----------M2
+#    \        /
+#     \---M1-C
+#      \ /
+#       B
+test_expect_success 'name-rev covers all conditions while looking at parents' '
+	git init repo &&
+	(
+		cd repo &&
+
+		echo A >file &&
+		git add file &&
+		git commit -m A &&
+		A=$(git rev-parse HEAD) &&
+
+		git checkout --detach &&
+		echo B >file &&
+		git commit -m B file &&
+		B=$(git rev-parse HEAD) &&
+
+		git checkout $A &&
+		git merge --no-ff $B &&  # M1
+
+		echo C >file &&
+		git commit -m C file &&
+
+		git checkout $A &&
+		git merge --no-ff HEAD@{1} && # M2
+
+		git checkout master &&
+		git merge --no-ff HEAD@{1} &&
+
+		echo "$B master^2^2~1^2" >expect &&
+		git name-rev $B >actual &&
+
+		test_cmp expect actual
+	)
+'
+
+#               B
+#               o
+#                \
+#  o-----o---o----x
+#        A
+#
+test_expect_success 'describe commits with disjoint bases' '
+	git init disjoint1 &&
+	(
+		cd disjoint1 &&
+
+		echo o >> file && git add file && git commit -m o &&
+		echo A >> file && git add file && git commit -m A &&
+		git tag A -a -m A &&
+		echo o >> file && git add file && git commit -m o &&
+
+		git checkout --orphan branch && rm file &&
+		echo B > file2 && git add file2 && git commit -m B &&
+		git tag B -a -m B &&
+		git merge --no-ff --allow-unrelated-histories master -m x &&
+
+		check_describe "A-3-*" HEAD
+	)
+'
+
+#           B
+#   o---o---o------------.
+#                         \
+#                  o---o---x
+#                  A
+#
+test_expect_success 'describe commits with disjoint bases 2' '
+	git init disjoint2 &&
+	(
+		cd disjoint2 &&
+
+		echo A >> file && git add file && GIT_COMMITTER_DATE="2020-01-01 18:00" git commit -m A &&
+		git tag A -a -m A &&
+		echo o >> file && git add file && GIT_COMMITTER_DATE="2020-01-01 18:01" git commit -m o &&
+
+		git checkout --orphan branch &&
+		echo o >> file2 && git add file2 && GIT_COMMITTER_DATE="2020-01-01 15:00" git commit -m o &&
+		echo o >> file2 && git add file2 && GIT_COMMITTER_DATE="2020-01-01 15:01" git commit -m o &&
+		echo B >> file2 && git add file2 && GIT_COMMITTER_DATE="2020-01-01 15:02" git commit -m B &&
+		git tag B -a -m B &&
+		git merge --no-ff --allow-unrelated-histories master -m x &&
+
+		check_describe "B-3-*" HEAD
+	)
+'
+
 test_done
diff --git a/third_party/git/t/t6130-pathspec-noglob.sh b/third_party/git/t/t6130-pathspec-noglob.sh
index 37760233a5..ba7902c9cd 100755
--- a/third_party/git/t/t6130-pathspec-noglob.sh
+++ b/third_party/git/t/t6130-pathspec-noglob.sh
@@ -10,6 +10,7 @@ test_expect_success 'create commits with glob characters' '
 	# the name "f*" in the worktree, because it is not allowed
 	# on Windows (the tests below do not depend on the presence
 	# of the file in the worktree)
+	git config core.protectNTFS false &&
 	git update-index --add --cacheinfo 100644 "$(git rev-parse HEAD:foo)" "f*" &&
 	test_tick &&
 	git commit -m star &&
diff --git a/third_party/git/t/t6132-pathspec-exclude.sh b/third_party/git/t/t6132-pathspec-exclude.sh
index 2462b19ddd..30328b87f0 100755
--- a/third_party/git/t/t6132-pathspec-exclude.sh
+++ b/third_party/git/t/t6132-pathspec-exclude.sh
@@ -211,4 +211,37 @@ test_expect_success 't_e_i() exclude case #8' '
 	)
 '
 
+test_expect_success 'grep --untracked PATTERN' '
+	# This test is not an actual test of exclude patterns, rather it
+	# is here solely to ensure that if any tests are inserted, deleted, or
+	# changed above, that we still have untracked files with the expected
+	# contents for the NEXT two tests.
+	cat <<-\EOF >expect-grep &&
+	actual
+	expect
+	sub/actual
+	sub/expect
+	EOF
+	git grep -l --untracked file -- >actual-grep &&
+	test_cmp expect-grep actual-grep
+'
+
+test_expect_success 'grep --untracked PATTERN :(exclude)DIR' '
+	cat <<-\EOF >expect-grep &&
+	actual
+	expect
+	EOF
+	git grep -l --untracked file -- ":(exclude)sub" >actual-grep &&
+	test_cmp expect-grep actual-grep
+'
+
+test_expect_success 'grep --untracked PATTERN :(exclude)*FILE' '
+	cat <<-\EOF >expect-grep &&
+	actual
+	sub/actual
+	EOF
+	git grep -l --untracked file -- ":(exclude)*expect" >actual-grep &&
+	test_cmp expect-grep actual-grep
+'
+
 test_done
diff --git a/third_party/git/t/t6136-pathspec-in-bare.sh b/third_party/git/t/t6136-pathspec-in-bare.sh
new file mode 100755
index 0000000000..b117251366
--- /dev/null
+++ b/third_party/git/t/t6136-pathspec-in-bare.sh
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+test_description='diagnosing out-of-scope pathspec'
+
+. ./test-lib.sh
+
+test_expect_success 'setup a bare and non-bare repository' '
+	test_commit file1 &&
+	git clone --bare . bare
+'
+
+test_expect_success 'log and ls-files in a bare repository' '
+	(
+		cd bare &&
+		test_must_fail git log -- .. >out 2>err &&
+		test_must_be_empty out &&
+		test_i18ngrep "outside repository" err &&
+
+		test_must_fail git ls-files -- .. >out 2>err &&
+		test_must_be_empty out &&
+		test_i18ngrep "outside repository" err
+	)
+'
+
+test_expect_success 'log and ls-files in .git directory' '
+	(
+		cd .git &&
+		test_must_fail git log -- .. >out 2>err &&
+		test_must_be_empty out &&
+		test_i18ngrep "outside repository" err &&
+
+		test_must_fail git ls-files -- .. >out 2>err &&
+		test_must_be_empty out &&
+		test_i18ngrep "outside repository" err
+	)
+'
+
+test_done
diff --git a/third_party/git/t/t6200-fmt-merge-msg.sh b/third_party/git/t/t6200-fmt-merge-msg.sh
index 8a72b4c43a..7d549748ef 100755
--- a/third_party/git/t/t6200-fmt-merge-msg.sh
+++ b/third_party/git/t/t6200-fmt-merge-msg.sh
@@ -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 -E "^# 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 &&
 
@@ -519,4 +542,24 @@ test_expect_success 'merge-msg with "merging" an annotated tag' '
 	test_cmp expected .git/MERGE_MSG
 '
 
+test_expect_success 'merge.suppressDest configuration' '
+	git checkout -B side master &&
+	git commit --allow-empty -m "One step ahead" &&
+	git checkout master &&
+	git fetch . side &&
+
+	git -c merge.suppressDest="" fmt-merge-msg <.git/FETCH_HEAD >full.1 &&
+	head -n1 full.1 >actual &&
+	grep -e "Merge branch .side. into master" actual &&
+
+	git -c merge.suppressDest="mast" fmt-merge-msg <.git/FETCH_HEAD >full.2 &&
+	head -n1 full.2 >actual &&
+	grep -e "Merge branch .side. into master$" actual &&
+
+	git -c merge.suppressDest="ma??er" fmt-merge-msg <.git/FETCH_HEAD >full.3 &&
+	head -n1 full.3 >actual &&
+	grep -e "Merge branch .side." actual &&
+	! grep -e " into master$" actual
+'
+
 test_done
diff --git a/third_party/git/t/t6300-for-each-ref.sh b/third_party/git/t/t6300-for-each-ref.sh
index ab69aa176d..b359023189 100755
--- a/third_party/git/t/t6300-for-each-ref.sh
+++ b/third_party/git/t/t6300-for-each-ref.sh
@@ -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 &&
@@ -48,8 +52,30 @@ test_atom() {
 		sanitize_pgp <actual >actual.clean &&
 		test_cmp expected actual.clean
 	"
+	# Automatically test "contents:size" atom after testing "contents"
+	if test "$2" = "contents"
+	then
+		case $(git cat-file -t "$ref") in
+		tag)
+			# We cannot use $3 as it expects sanitize_pgp to run
+			expect=$(git cat-file tag $ref | tail -n +6 | wc -c) ;;
+		tree | blob)
+			expect='' ;;
+		commit)
+			expect=$(printf '%s' "$3" | wc -c) ;;
+		esac
+		# Leave $expect unquoted to lose possible leading whitespaces
+		echo $expect >expected
+		test_expect_${4:-success} $PREREQ "basic atom: $1 contents:size" '
+			git for-each-ref --format="%(contents:size)" "$ref" >actual &&
+			test_cmp expected actual
+		'
+	fi
 }
 
+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,15 +108,21 @@ 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)
 test_atom head objectname:short=10 $(git rev-parse --short=10 refs/heads/master)
 test_atom head tree $(git rev-parse refs/heads/master^{tree})
+test_atom head tree:short $(git rev-parse --short refs/heads/master^{tree})
+test_atom head tree:short=1 $(git rev-parse --short=1 refs/heads/master^{tree})
+test_atom head tree:short=10 $(git rev-parse --short=10 refs/heads/master^{tree})
 test_atom head parent ''
+test_atom head parent:short ''
+test_atom head parent:short=1 ''
+test_atom head parent:short=10 ''
 test_atom head numparent 0
 test_atom head object ''
 test_atom head type ''
@@ -99,19 +131,26 @@ test_atom head '*objecttype' ''
 test_atom head author 'A U Thor <author@example.com> 1151968724 +0200'
 test_atom head authorname 'A U Thor'
 test_atom head authoremail '<author@example.com>'
+test_atom head authoremail:trim 'author@example.com'
+test_atom head authoremail:localpart 'author'
 test_atom head authordate 'Tue Jul 4 01:18:44 2006 +0200'
 test_atom head committer 'C O Mitter <committer@example.com> 1151968723 +0200'
 test_atom head committername 'C O Mitter'
 test_atom head committeremail '<committer@example.com>'
+test_atom head committeremail:trim 'committer@example.com'
+test_atom head committeremail:localpart 'committer'
 test_atom head committerdate 'Tue Jul 4 01:18:43 2006 +0200'
 test_atom head tag ''
 test_atom head tagger ''
 test_atom head taggername ''
 test_atom head taggeremail ''
+test_atom head taggeremail:trim ''
+test_atom head taggeremail:localpart ''
 test_atom head taggerdate ''
 test_atom head creator 'C O Mitter <committer@example.com> 1151968723 +0200'
 test_atom head creatordate 'Tue Jul 4 01:18:43 2006 +0200'
 test_atom head subject 'Initial'
+test_atom head subject:sanitize 'Initial'
 test_atom head contents:subject 'Initial'
 test_atom head body ''
 test_atom head contents:body ''
@@ -125,38 +164,51 @@ 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)
 test_atom head objectname:short=10 $(git rev-parse --short=10 refs/heads/master)
 test_atom tag tree ''
+test_atom tag tree:short ''
+test_atom tag tree:short=1 ''
+test_atom tag tree:short=10 ''
 test_atom tag parent ''
+test_atom tag parent:short ''
+test_atom tag parent:short=1 ''
+test_atom tag parent:short=10 ''
 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 ''
 test_atom tag authoremail ''
+test_atom tag authoremail:trim ''
+test_atom tag authoremail:localpart ''
 test_atom tag authordate ''
 test_atom tag committer ''
 test_atom tag committername ''
 test_atom tag committeremail ''
+test_atom tag committeremail:trim ''
+test_atom tag committeremail:localpart ''
 test_atom tag committerdate ''
 test_atom tag tag 'testtag'
 test_atom tag tagger 'C O Mitter <committer@example.com> 1151968725 +0200'
 test_atom tag taggername 'C O Mitter'
 test_atom tag taggeremail '<committer@example.com>'
+test_atom tag taggeremail:trim 'committer@example.com'
+test_atom tag taggeremail:localpart 'committer'
 test_atom tag taggerdate 'Tue Jul 4 01:18:45 2006 +0200'
 test_atom tag creator 'C O Mitter <committer@example.com> 1151968725 +0200'
 test_atom tag creatordate 'Tue Jul 4 01:18:45 2006 +0200'
 test_atom tag subject 'Tagging at 1151968727'
+test_atom tag subject:sanitize 'Tagging-at-1151968727'
 test_atom tag contents:subject 'Tagging at 1151968727'
 test_atom tag body ''
 test_atom tag contents:body ''
@@ -526,6 +578,29 @@ test_expect_success 'Check ambiguous head and tag refs II (loose)' '
 	test_cmp expected actual
 '
 
+test_expect_success 'create tag without tagger' '
+	git tag -a -m "Broken tag" taggerless &&
+	git tag -f taggerless $(git cat-file tag taggerless |
+		sed -e "/^tagger /d" |
+		git hash-object --stdin -w -t tag)
+'
+
+test_atom refs/tags/taggerless type 'commit'
+test_atom refs/tags/taggerless tag 'taggerless'
+test_atom refs/tags/taggerless tagger ''
+test_atom refs/tags/taggerless taggername ''
+test_atom refs/tags/taggerless taggeremail ''
+test_atom refs/tags/taggerless taggeremail:trim ''
+test_atom refs/tags/taggerless taggeremail:localpart ''
+test_atom refs/tags/taggerless taggerdate ''
+test_atom refs/tags/taggerless committer ''
+test_atom refs/tags/taggerless committername ''
+test_atom refs/tags/taggerless committeremail ''
+test_atom refs/tags/taggerless committeremail:trim ''
+test_atom refs/tags/taggerless committeremail:localpart ''
+test_atom refs/tags/taggerless committerdate ''
+test_atom refs/tags/taggerless subject 'Broken tag'
+
 test_expect_success 'an unusual tag with an incomplete line' '
 
 	git tag -m "bogo" bogo &&
@@ -546,6 +621,7 @@ test_expect_success 'create tag with subject and body content' '
 	git tag -F msg subject-body
 '
 test_atom refs/tags/subject-body subject 'the subject line'
+test_atom refs/tags/subject-body subject:sanitize 'the-subject-line'
 test_atom refs/tags/subject-body body 'first body line
 second body line
 '
@@ -566,6 +642,7 @@ test_expect_success 'create tag with multiline subject' '
 	git tag -F msg multiline
 '
 test_atom refs/tags/multiline subject 'first subject line second subject line'
+test_atom refs/tags/multiline subject:sanitize 'first-subject-line-second-subject-line'
 test_atom refs/tags/multiline contents:subject 'first subject line second subject line'
 test_atom refs/tags/multiline body 'first body line
 second body line
@@ -598,6 +675,7 @@ sig='-----BEGIN PGP SIGNATURE-----
 
 PREREQ=GPG
 test_atom refs/tags/signed-empty subject ''
+test_atom refs/tags/signed-empty subject:sanitize ''
 test_atom refs/tags/signed-empty contents:subject ''
 test_atom refs/tags/signed-empty body "$sig"
 test_atom refs/tags/signed-empty contents:body ''
@@ -605,6 +683,7 @@ test_atom refs/tags/signed-empty contents:signature "$sig"
 test_atom refs/tags/signed-empty contents "$sig"
 
 test_atom refs/tags/signed-short subject 'subject line'
+test_atom refs/tags/signed-short subject:sanitize 'subject-line'
 test_atom refs/tags/signed-short contents:subject 'subject line'
 test_atom refs/tags/signed-short body "$sig"
 test_atom refs/tags/signed-short contents:body ''
@@ -613,6 +692,7 @@ test_atom refs/tags/signed-short contents "subject line
 $sig"
 
 test_atom refs/tags/signed-long subject 'subject line'
+test_atom refs/tags/signed-long subject:sanitize 'subject-line'
 test_atom refs/tags/signed-long contents:subject 'subject line'
 test_atom refs/tags/signed-long body "body contents
 $sig"
@@ -624,17 +704,78 @@ test_atom refs/tags/signed-long contents "subject line
 body contents
 $sig"
 
-cat >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
+test_expect_success 'set up refs pointing to tree and blob' '
+	git update-ref refs/mytrees/first refs/heads/master^{tree} &&
+	git update-ref refs/myblobs/first refs/heads/master:one
+'
+
+test_atom refs/mytrees/first subject ""
+test_atom refs/mytrees/first contents:subject ""
+test_atom refs/mytrees/first body ""
+test_atom refs/mytrees/first contents:body ""
+test_atom refs/mytrees/first contents:signature ""
+test_atom refs/mytrees/first contents ""
+
+test_atom refs/myblobs/first subject ""
+test_atom refs/myblobs/first contents:subject ""
+test_atom refs/myblobs/first body ""
+test_atom refs/myblobs/first contents:body ""
+test_atom refs/myblobs/first contents:signature ""
+test_atom refs/myblobs/first contents ""
+
+test_expect_success 'set up multiple-sort tags' '
+	for when in 100000 200000
+	do
+		for email in user1 user2
+		do
+			for ref in ref1 ref2
+			do
+				GIT_COMMITTER_DATE="@$when +0000" \
+				GIT_COMMITTER_EMAIL="$email@example.com" \
+				git tag -m "tag $ref-$when-$email" \
+				multi-$ref-$when-$email || return 1
+			done
+		done
+	done
+'
 
 test_expect_success 'Verify sort with multiple keys' '
-	git for-each-ref --format="%(objectname) %(taggeremail) %(refname)" --sort=objectname --sort=taggeremail \
-		refs/tags/bogo refs/tags/master > actual &&
+	cat >expected <<-\EOF &&
+	100000 <user1@example.com> refs/tags/multi-ref2-100000-user1
+	100000 <user1@example.com> refs/tags/multi-ref1-100000-user1
+	100000 <user2@example.com> refs/tags/multi-ref2-100000-user2
+	100000 <user2@example.com> refs/tags/multi-ref1-100000-user2
+	200000 <user1@example.com> refs/tags/multi-ref2-200000-user1
+	200000 <user1@example.com> refs/tags/multi-ref1-200000-user1
+	200000 <user2@example.com> refs/tags/multi-ref2-200000-user2
+	200000 <user2@example.com> refs/tags/multi-ref1-200000-user2
+	EOF
+	git for-each-ref \
+		--format="%(taggerdate:unix) %(taggeremail) %(refname)" \
+		--sort=-refname \
+		--sort=taggeremail \
+		--sort=taggerdate \
+		"refs/tags/multi-*" >actual &&
 	test_cmp expected actual
 '
 
+test_expect_success 'equivalent sorts fall back on refname' '
+	cat >expected <<-\EOF &&
+	100000 <user1@example.com> refs/tags/multi-ref1-100000-user1
+	100000 <user2@example.com> refs/tags/multi-ref1-100000-user2
+	100000 <user1@example.com> refs/tags/multi-ref2-100000-user1
+	100000 <user2@example.com> refs/tags/multi-ref2-100000-user2
+	200000 <user1@example.com> refs/tags/multi-ref1-200000-user1
+	200000 <user2@example.com> refs/tags/multi-ref1-200000-user2
+	200000 <user1@example.com> refs/tags/multi-ref2-200000-user1
+	200000 <user2@example.com> refs/tags/multi-ref2-200000-user2
+	EOF
+	git for-each-ref \
+		--format="%(taggerdate:unix) %(taggeremail) %(refname)" \
+		--sort=taggerdate \
+		"refs/tags/multi-*" >actual &&
+	test_cmp expected actual
+'
 
 test_expect_success 'do not dereference NULL upon %(HEAD) on unborn branch' '
 	test_when_finished "git checkout master" &&
@@ -670,61 +811,40 @@ test_expect_success 'set up trailers for next test' '
 '
 
 test_expect_success '%(trailers:unfold) unfolds trailers' '
-	git for-each-ref --format="%(trailers:unfold)" refs/heads/master >actual &&
 	{
 		unfold <trailers
 		echo
 	} >expect &&
+	git for-each-ref --format="%(trailers:unfold)" refs/heads/master >actual &&
+	test_cmp expect actual &&
+	git for-each-ref --format="%(contents:trailers:unfold)" refs/heads/master >actual &&
 	test_cmp expect actual
 '
 
 test_expect_success '%(trailers:only) shows only "key: value" trailers' '
-	git for-each-ref --format="%(trailers:only)" refs/heads/master >actual &&
 	{
 		grep -v patch.description <trailers &&
 		echo
 	} >expect &&
+	git for-each-ref --format="%(trailers:only)" refs/heads/master >actual &&
+	test_cmp expect actual &&
+	git for-each-ref --format="%(contents:trailers:only)" refs/heads/master >actual &&
 	test_cmp expect actual
 '
 
 test_expect_success '%(trailers:only) and %(trailers:unfold) work together' '
-	git for-each-ref --format="%(trailers:only,unfold)" refs/heads/master >actual &&
-	git for-each-ref --format="%(trailers:unfold,only)" refs/heads/master >reverse &&
-	test_cmp actual reverse &&
 	{
 		grep -v patch.description <trailers | unfold &&
 		echo
 	} >expect &&
-	test_cmp expect actual
-'
-
-test_expect_success '%(contents:trailers:unfold) unfolds trailers' '
-	git for-each-ref --format="%(contents:trailers:unfold)" refs/heads/master >actual &&
-	{
-		unfold <trailers
-		echo
-	} >expect &&
-	test_cmp expect actual
-'
-
-test_expect_success '%(contents:trailers:only) shows only "key: value" trailers' '
-	git for-each-ref --format="%(contents:trailers:only)" refs/heads/master >actual &&
-	{
-		grep -v patch.description <trailers &&
-		echo
-	} >expect &&
-	test_cmp expect actual
-'
-
-test_expect_success '%(contents:trailers:only) and %(contents:trailers:unfold) work together' '
+	git for-each-ref --format="%(trailers:only,unfold)" refs/heads/master >actual &&
+	test_cmp expect actual &&
+	git for-each-ref --format="%(trailers:unfold,only)" refs/heads/master >actual &&
+	test_cmp actual actual &&
 	git for-each-ref --format="%(contents:trailers:only,unfold)" refs/heads/master >actual &&
-	git for-each-ref --format="%(contents:trailers:unfold,only)" refs/heads/master >reverse &&
-	test_cmp actual reverse &&
-	{
-		grep -v patch.description <trailers | unfold &&
-		echo
-	} >expect &&
-	test_cmp expect actual
+	test_cmp expect actual &&
+	git for-each-ref --format="%(contents:trailers:unfold,only)" refs/heads/master >actual &&
+	test_cmp actual actual
 '
 
 test_expect_success '%(trailers) rejects unknown trailers arguments' '
@@ -733,15 +853,16 @@ test_expect_success '%(trailers) rejects unknown trailers arguments' '
 	fatal: unknown %(trailers) argument: unsupported
 	EOF
 	test_must_fail git for-each-ref --format="%(trailers:unsupported)" 2>actual &&
+	test_i18ncmp expect actual &&
+	test_must_fail git for-each-ref --format="%(contents:trailers:unsupported)" 2>actual &&
 	test_i18ncmp expect actual
 '
 
-test_expect_success '%(contents:trailers) rejects unknown trailers arguments' '
-	# error message cannot be checked under i18n
+test_expect_success 'if arguments, %(contents:trailers) shows error if colon is missing' '
 	cat >expect <<-EOF &&
-	fatal: unknown %(trailers) argument: unsupported
+	fatal: unrecognized %(contents) argument: trailersonly
 	EOF
-	test_must_fail git for-each-ref --format="%(contents:trailers:unsupported)" 2>actual &&
+	test_must_fail git for-each-ref --format="%(contents:trailersonly)" 2>actual &&
 	test_i18ncmp expect actual
 '
 
@@ -869,4 +990,44 @@ test_expect_success 'for-each-ref --ignore-case ignores case' '
 	test_cmp expect actual
 '
 
+test_expect_success 'for-each-ref --ignore-case works on multiple sort keys' '
+	# name refs numerically to avoid case-insensitive filesystem conflicts
+	nr=0 &&
+	for email in a A b B
+	do
+		for subject in a A b B
+		do
+			GIT_COMMITTER_EMAIL="$email@example.com" \
+			git tag -m "tag $subject" icase-$(printf %02d $nr) &&
+			nr=$((nr+1))||
+			return 1
+		done
+	done &&
+	git for-each-ref --ignore-case \
+		--format="%(taggeremail) %(subject) %(refname)" \
+		--sort=refname \
+		--sort=subject \
+		--sort=taggeremail \
+		refs/tags/icase-* >actual &&
+	cat >expect <<-\EOF &&
+	<a@example.com> tag a refs/tags/icase-00
+	<a@example.com> tag A refs/tags/icase-01
+	<A@example.com> tag a refs/tags/icase-04
+	<A@example.com> tag A refs/tags/icase-05
+	<a@example.com> tag b refs/tags/icase-02
+	<a@example.com> tag B refs/tags/icase-03
+	<A@example.com> tag b refs/tags/icase-06
+	<A@example.com> tag B refs/tags/icase-07
+	<b@example.com> tag a refs/tags/icase-08
+	<b@example.com> tag A refs/tags/icase-09
+	<B@example.com> tag a refs/tags/icase-12
+	<B@example.com> tag A refs/tags/icase-13
+	<b@example.com> tag b refs/tags/icase-10
+	<b@example.com> tag B refs/tags/icase-11
+	<B@example.com> tag b refs/tags/icase-14
+	<B@example.com> tag B refs/tags/icase-15
+	EOF
+	test_cmp expect actual
+'
+
 test_done
diff --git a/third_party/git/t/t6301-for-each-ref-errors.sh b/third_party/git/t/t6301-for-each-ref-errors.sh
index 49cc65bb58..809854fc0c 100755
--- a/third_party/git/t/t6301-for-each-ref-errors.sh
+++ b/third_party/git/t/t6301-for-each-ref-errors.sh
@@ -5,9 +5,9 @@ test_description='for-each-ref errors for broken refs'
 . ./test-lib.sh
 
 ZEROS=$ZERO_OID
-MISSING=abababababababababababababababababababab
 
 test_expect_success setup '
+	MISSING=$(test_oid deadbeef) &&
 	git commit --allow-empty -m "Initial" &&
 	git tag testtag &&
 	git for-each-ref >full-list &&
diff --git a/third_party/git/t/t6302-for-each-ref-filter.sh b/third_party/git/t/t6302-for-each-ref-filter.sh
index 35408d53fd..781e470aea 100755
--- a/third_party/git/t/t6302-for-each-ref-filter.sh
+++ b/third_party/git/t/t6302-for-each-ref-filter.sh
@@ -437,8 +437,8 @@ test_expect_success 'check %(if:notequals=<string>)' '
 	test_cmp expect actual
 '
 
-test_expect_success '--merged is incompatible with --no-merged' '
-	test_must_fail git for-each-ref --merged HEAD --no-merged HEAD
+test_expect_success '--merged is compatible with --no-merged' '
+	git for-each-ref --merged HEAD --no-merged HEAD
 '
 
 test_expect_success 'validate worktree atom' '
diff --git a/third_party/git/t/t6020-merge-df.sh b/third_party/git/t/t6400-merge-df.sh
index 46b506b3b7..f1b84617af 100755
--- a/third_party/git/t/t6020-merge-df.sh
+++ b/third_party/git/t/t6400-merge-df.sh
@@ -83,9 +83,9 @@ test_expect_success 'modify/delete + directory/file conflict' '
 	test 4 -eq $(git ls-files -u | wc -l) &&
 	test 1 -eq $(git ls-files -o | wc -l) &&
 
-	test -f letters/file &&
-	test -f letters.txt &&
-	test -f letters~modify
+	test_path_is_file letters/file &&
+	test_path_is_file letters.txt &&
+	test_path_is_file letters~modify
 '
 
 test_expect_success 'modify/delete + directory/file conflict; other way' '
@@ -99,9 +99,52 @@ test_expect_success 'modify/delete + directory/file conflict; other way' '
 	test 4 -eq $(git ls-files -u | wc -l) &&
 	test 1 -eq $(git ls-files -o | wc -l) &&
 
-	test -f letters/file &&
-	test -f letters.txt &&
-	test -f letters~HEAD
+	test_path_is_file letters/file &&
+	test_path_is_file letters.txt &&
+	test_path_is_file letters~HEAD
+'
+
+test_expect_success 'Simple merge in repo with interesting pathnames' '
+	# Simple lexicographic ordering of files and directories would be:
+	#     foo
+	#     foo/bar
+	#     foo/bar-2
+	#     foo/bar/baz
+	#     foo/bar-2/baz
+	# The fact that foo/bar-2 appears between foo/bar and foo/bar/baz
+	# can trip up some codepaths, and is the point of this test.
+	test_create_repo name-ordering &&
+	(
+		cd name-ordering &&
+
+		mkdir -p foo/bar &&
+		mkdir -p foo/bar-2 &&
+		>foo/bar/baz &&
+		>foo/bar-2/baz &&
+		git add . &&
+		git commit -m initial &&
+
+		git branch topic &&
+		git branch other &&
+
+		git checkout other &&
+		echo other >foo/bar-2/baz &&
+		git add -u &&
+		git commit -m other &&
+
+		git checkout topic &&
+		echo topic >foo/bar/baz &&
+		git add -u &&
+		git commit -m topic &&
+
+		git merge other &&
+		git ls-files -s >out &&
+		test_line_count = 2 out &&
+		git rev-parse :0:foo/bar/baz :0:foo/bar-2/baz >actual &&
+		git rev-parse HEAD~1:foo/bar/baz other:foo/bar-2/baz >expect &&
+		test_cmp expect actual
+	)
+
 '
 
 test_done
diff --git a/third_party/git/t/t6401-merge-criss-cross.sh b/third_party/git/t/t6401-merge-criss-cross.sh
new file mode 100755
index 0000000000..9d5e992878
--- /dev/null
+++ b/third_party/git/t/t6401-merge-criss-cross.sh
@@ -0,0 +1,67 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Fredrik Kuivinen
+#
+
+# See https://lore.kernel.org/git/Pine.LNX.4.44.0504271254120.4678-100000@wax.eds.org/ for a
+# nice description of what this is about.
+
+
+test_description='Test criss-cross merge'
+. ./test-lib.sh
+
+test_expect_success 'prepare repository' '
+	test_write_lines 1 2 3 4 5 6 7 8 9 >file &&
+	git add file &&
+	git commit -m "Initial commit" file &&
+
+	git branch A &&
+	git branch B &&
+	git checkout A &&
+
+	test_write_lines 1 2 3 4 5 6 7 "8 changed in B8, branch A" 9 >file &&
+	git commit -m "B8" file &&
+	git checkout B &&
+
+	test_write_lines 1 2 "3 changed in C3, branch B" 4 5 6 7 8 9 >file &&
+	git commit -m "C3" file &&
+	git branch C3 &&
+
+	git merge -m "pre E3 merge" A &&
+
+	test_write_lines 1 2 "3 changed in E3, branch B. New file size" 4 5 6 7 "8 changed in B8, branch A" 9 >file &&
+	git commit -m "E3" file &&
+
+	git checkout A &&
+	git merge -m "pre D8 merge" C3 &&
+	test_write_lines 1 2 "3 changed in C3, branch B" 4 5 6 7 "8 changed in D8, branch A. New file size 2" 9 >file &&
+
+	git commit -m D8 file
+'
+
+test_expect_success 'Criss-cross merge' '
+	git merge -m "final merge" B
+'
+
+test_expect_success 'Criss-cross merge result' '
+	cat <<-\EOF >file-expect &&
+	1
+	2
+	3 changed in E3, branch B. New file size
+	4
+	5
+	6
+	7
+	8 changed in D8, branch A. New file size 2
+	9
+	EOF
+
+	test_cmp file-expect file
+'
+
+test_expect_success 'Criss-cross merge fails (-s resolve)' '
+	git reset --hard A^ &&
+	test_must_fail git merge -s resolve -m "final merge" B
+'
+
+test_done
diff --git a/third_party/git/t/t6022-merge-rename.sh b/third_party/git/t/t6402-merge-rename.sh
index 53cc9b2ffb..bbbba3dcbf 100755
--- a/third_party/git/t/t6022-merge-rename.sh
+++ b/third_party/git/t/t6402-merge-rename.sh
@@ -8,94 +8,94 @@ modify () {
 	mv "$2.x" "$2"
 }
 
-test_expect_success setup \
-'
-cat >A <<\EOF &&
-a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
-b bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
-c cccccccccccccccccccccccccccccccccccccccccccccccc
-d dddddddddddddddddddddddddddddddddddddddddddddddd
-e eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
-f ffffffffffffffffffffffffffffffffffffffffffffffff
-g gggggggggggggggggggggggggggggggggggggggggggggggg
-h hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
-i iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
-j jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj
-k kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk
-l llllllllllllllllllllllllllllllllllllllllllllllll
-m mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
-n nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
-o oooooooooooooooooooooooooooooooooooooooooooooooo
-EOF
-
-cat >M <<\EOF &&
-A AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-B BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
-C CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
-D DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
-E EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
-F FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
-G GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG
-H HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH
-I IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII
-J JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ
-K KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK
-L LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL
-M MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
-N NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN
-O OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO
-EOF
-
-git add A M &&
-git commit -m "initial has A and M" &&
-git branch white &&
-git branch red &&
-git branch blue &&
-git branch yellow &&
-git branch change &&
-git branch change+rename &&
-
-sed -e "/^g /s/.*/g : master changes a line/" <A >A+ &&
-mv A+ A &&
-git commit -a -m "master updates A" &&
-
-git checkout yellow &&
-rm -f M &&
-git commit -a -m "yellow removes M" &&
-
-git checkout white &&
-sed -e "/^g /s/.*/g : white changes a line/" <A >B &&
-sed -e "/^G /s/.*/G : colored branch changes a line/" <M >N &&
-rm -f A M &&
-git update-index --add --remove A B M N &&
-git commit -m "white renames A->B, M->N" &&
-
-git checkout red &&
-sed -e "/^g /s/.*/g : red changes a line/" <A >B &&
-sed -e "/^G /s/.*/G : colored branch changes a line/" <M >N &&
-rm -f A M &&
-git update-index --add --remove A B M N &&
-git commit -m "red renames A->B, M->N" &&
-
-git checkout blue &&
-sed -e "/^g /s/.*/g : blue changes a line/" <A >C &&
-sed -e "/^G /s/.*/G : colored branch changes a line/" <M >N &&
-rm -f A M &&
-git update-index --add --remove A C M N &&
-git commit -m "blue renames A->C, M->N" &&
-
-git checkout change &&
-sed -e "/^g /s/.*/g : changed line/" <A >A+ &&
-mv A+ A &&
-git commit -q -a -m "changed" &&
-
-git checkout change+rename &&
-sed -e "/^g /s/.*/g : changed line/" <A >B &&
-rm A &&
-git update-index --add B &&
-git commit -q -a -m "changed and renamed" &&
-
-git checkout master'
+test_expect_success 'setup' '
+	cat >A <<-\EOF &&
+	a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+	b bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+	c cccccccccccccccccccccccccccccccccccccccccccccccc
+	d dddddddddddddddddddddddddddddddddddddddddddddddd
+	e eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
+	f ffffffffffffffffffffffffffffffffffffffffffffffff
+	g gggggggggggggggggggggggggggggggggggggggggggggggg
+	h hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
+	i iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
+	j jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj
+	k kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk
+	l llllllllllllllllllllllllllllllllllllllllllllllll
+	m mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
+	n nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
+	o oooooooooooooooooooooooooooooooooooooooooooooooo
+	EOF
+
+	cat >M <<-\EOF &&
+	A AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	B BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
+	C CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
+	D DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
+	E EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
+	F FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+	G GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG
+	H HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH
+	I IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII
+	J JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ
+	K KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK
+	L LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL
+	M MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
+	N NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN
+	O OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO
+	EOF
+
+	git add A M &&
+	git commit -m "initial has A and M" &&
+	git branch white &&
+	git branch red &&
+	git branch blue &&
+	git branch yellow &&
+	git branch change &&
+	git branch change+rename &&
+
+	sed -e "/^g /s/.*/g : master changes a line/" <A >A+ &&
+	mv A+ A &&
+	git commit -a -m "master updates A" &&
+
+	git checkout yellow &&
+	rm -f M &&
+	git commit -a -m "yellow removes M" &&
+
+	git checkout white &&
+	sed -e "/^g /s/.*/g : white changes a line/" <A >B &&
+	sed -e "/^G /s/.*/G : colored branch changes a line/" <M >N &&
+	rm -f A M &&
+	git update-index --add --remove A B M N &&
+	git commit -m "white renames A->B, M->N" &&
+
+	git checkout red &&
+	sed -e "/^g /s/.*/g : red changes a line/" <A >B &&
+	sed -e "/^G /s/.*/G : colored branch changes a line/" <M >N &&
+	rm -f A M &&
+	git update-index --add --remove A B M N &&
+	git commit -m "red renames A->B, M->N" &&
+
+	git checkout blue &&
+	sed -e "/^g /s/.*/g : blue changes a line/" <A >C &&
+	sed -e "/^G /s/.*/G : colored branch changes a line/" <M >N &&
+	rm -f A M &&
+	git update-index --add --remove A C M N &&
+	git commit -m "blue renames A->C, M->N" &&
+
+	git checkout change &&
+	sed -e "/^g /s/.*/g : changed line/" <A >A+ &&
+	mv A+ A &&
+	git commit -q -a -m "changed" &&
+
+	git checkout change+rename &&
+	sed -e "/^g /s/.*/g : changed line/" <A >B &&
+	rm A &&
+	git update-index --add B &&
+	git commit -q -a -m "changed and renamed" &&
+
+	git checkout master
+'
 
 test_expect_success 'pull renaming branch into unrenaming one' \
 '
@@ -242,12 +242,24 @@ test_expect_success 'merge of identical changes in a renamed file' '
 	rm -f A M N &&
 	git reset --hard &&
 	git checkout change+rename &&
+
+	test-tool chmtime --get -3600 B >old-mtime &&
 	GIT_MERGE_VERBOSITY=3 git merge change >out &&
-	test_i18ngrep "^Skipped B" out &&
+
+	test-tool chmtime --get B >new-mtime &&
+	test_cmp old-mtime new-mtime &&
+
 	git reset --hard HEAD^ &&
 	git checkout change &&
+
+	# A will be renamed to B; we check mtimes and file presence
+	test_path_is_missing B &&
+	test-tool chmtime --get -3600 A >old-mtime &&
 	GIT_MERGE_VERBOSITY=3 git merge change+rename >out &&
-	test_i18ngrep ! "^Skipped B" out
+
+	test_path_is_missing A &&
+	test-tool chmtime --get B >new-mtime &&
+	test $(cat old-mtime) -lt $(cat new-mtime)
 '
 
 test_expect_success 'setup for rename + d/f conflicts' '
@@ -288,14 +300,15 @@ test_expect_success 'setup for rename + d/f conflicts' '
 	git commit -m "Conflicting change"
 '
 
-printf "1\n2\n3\n4\n5555\n6\n7\n8\n9\n10\n11\n" >expected
-
 test_expect_success 'Rename+D/F conflict; renamed file merges + dir not in way' '
 	git reset --hard &&
 	git checkout -q renamed-file-has-no-conflicts^0 &&
+
 	git merge --strategy=recursive dir-not-in-way &&
+
 	git diff --quiet &&
-	test -f dir &&
+	test_path_is_file dir &&
+	test_write_lines 1 2 3 4 5555 6 7 8 9 10 11 >expected &&
 	test_cmp expected dir
 '
 
@@ -315,8 +328,8 @@ test_expect_success 'Rename+D/F conflict; renamed file merges but dir in way' '
 	test_must_fail git diff --quiet &&
 	test_must_fail git diff --cached --quiet &&
 
-	test -f dir/file-in-the-way &&
-	test -f dir~HEAD &&
+	test_path_is_file dir/file-in-the-way &&
+	test_path_is_file dir~HEAD &&
 	test_cmp expected dir~HEAD
 '
 
@@ -337,29 +350,11 @@ test_expect_success 'Same as previous, but merged other way' '
 	test_must_fail git diff --quiet &&
 	test_must_fail git diff --cached --quiet &&
 
-	test -f dir/file-in-the-way &&
-	test -f dir~renamed-file-has-no-conflicts &&
+	test_path_is_file dir/file-in-the-way &&
+	test_path_is_file dir~renamed-file-has-no-conflicts &&
 	test_cmp expected dir~renamed-file-has-no-conflicts
 '
 
-cat >expected <<\EOF &&
-1
-2
-3
-4
-5
-6
-7
-8
-9
-10
-<<<<<<< HEAD:dir
-12
-=======
-11
->>>>>>> dir-not-in-way:sub/file
-EOF
-
 test_expect_success 'Rename+D/F conflict; renamed file cannot merge, dir not in way' '
 	git reset --hard &&
 	rm -rf dir~* &&
@@ -372,7 +367,24 @@ test_expect_success 'Rename+D/F conflict; renamed file cannot merge, dir not in
 	test_must_fail git diff --quiet &&
 	test_must_fail git diff --cached --quiet &&
 
-	test -f dir &&
+	test_path_is_file dir &&
+	cat >expected <<-\EOF &&
+	1
+	2
+	3
+	4
+	5
+	6
+	7
+	8
+	9
+	10
+	<<<<<<< HEAD:dir
+	12
+	=======
+	11
+	>>>>>>> dir-not-in-way:sub/file
+	EOF
 	test_cmp expected dir
 '
 
@@ -391,29 +403,11 @@ test_expect_success 'Rename+D/F conflict; renamed file cannot merge and dir in t
 	test_must_fail git diff --quiet &&
 	test_must_fail git diff --cached --quiet &&
 
-	test -f dir/file-in-the-way &&
-	test -f dir~HEAD &&
+	test_path_is_file dir/file-in-the-way &&
+	test_path_is_file dir~HEAD &&
 	test_cmp expected dir~HEAD
 '
 
-cat >expected <<\EOF &&
-1
-2
-3
-4
-5
-6
-7
-8
-9
-10
-<<<<<<< HEAD:sub/file
-11
-=======
-12
->>>>>>> renamed-file-has-conflicts:dir
-EOF
-
 test_expect_success 'Same as previous, but merged other way' '
 	git reset --hard &&
 	rm -rf dir~* &&
@@ -427,8 +421,25 @@ test_expect_success 'Same as previous, but merged other way' '
 	test_must_fail git diff --quiet &&
 	test_must_fail git diff --cached --quiet &&
 
-	test -f dir/file-in-the-way &&
-	test -f dir~renamed-file-has-conflicts &&
+	test_path_is_file dir/file-in-the-way &&
+	test_path_is_file dir~renamed-file-has-conflicts &&
+	cat >expected <<-\EOF &&
+	1
+	2
+	3
+	4
+	5
+	6
+	7
+	8
+	9
+	10
+	<<<<<<< HEAD:sub/file
+	11
+	=======
+	12
+	>>>>>>> renamed-file-has-conflicts:dir
+	EOF
 	test_cmp expected dir~renamed-file-has-conflicts
 '
 
@@ -464,9 +475,9 @@ test_expect_success 'both rename source and destination involved in D/F conflict
 
 	test_must_fail git diff --quiet &&
 
-	test -f destdir/foo &&
-	test -f one &&
-	test -f destdir~HEAD &&
+	test_path_is_file destdir/foo &&
+	test_path_is_file one &&
+	test_path_is_file destdir~HEAD &&
 	test "stuff" = "$(cat destdir~HEAD)"
 '
 
@@ -507,9 +518,9 @@ test_expect_success 'pair rename to parent of other (D/F conflicts) w/ untracked
 
 	test 4 -eq $(find . | grep -v .git | wc -l) &&
 
-	test -d one &&
-	test -f one~rename-two &&
-	test -f two &&
+	test_path_is_dir one &&
+	test_path_is_file one~rename-two &&
+	test_path_is_file two &&
 	test "other" = $(cat one~rename-two) &&
 	test "stuff" = $(cat two)
 '
@@ -527,8 +538,8 @@ test_expect_success 'pair rename to parent of other (D/F conflicts) w/ clean sta
 
 	test 3 -eq $(find . | grep -v .git | wc -l) &&
 
-	test -f one &&
-	test -f two &&
+	test_path_is_file one &&
+	test_path_is_file two &&
 	test "other" = $(cat one) &&
 	test "stuff" = $(cat two)
 '
@@ -568,11 +579,11 @@ test_expect_success 'check handling of differently renamed file with D/F conflic
 	test 1 -eq "$(git ls-files -u original | wc -l)" &&
 	test 2 -eq "$(git ls-files -o | wc -l)" &&
 
-	test -f one/file &&
-	test -f two/file &&
-	test -f one~HEAD &&
-	test -f two~second-rename &&
-	! test -f original
+	test_path_is_file one/file &&
+	test_path_is_file two/file &&
+	test_path_is_file one~HEAD &&
+	test_path_is_file two~second-rename &&
+	test_path_is_missing original
 '
 
 test_expect_success 'setup rename one file to two; directories moving out of the way' '
@@ -607,9 +618,9 @@ test_expect_success 'check handling of differently renamed file with D/F conflic
 	test 1 -eq "$(git ls-files -u original | wc -l)" &&
 	test 0 -eq "$(git ls-files -o | wc -l)" &&
 
-	test -f one &&
-	test -f two &&
-	! test -f original
+	test_path_is_file one &&
+	test_path_is_file two &&
+	test_path_is_missing original
 '
 
 test_expect_success 'setup avoid unnecessary update, normal rename' '
@@ -635,7 +646,7 @@ test_expect_success 'setup avoid unnecessary update, normal rename' '
 
 test_expect_success 'avoid unnecessary update, normal rename' '
 	git checkout -q avoid-unnecessary-update-1^0 &&
-	test-tool chmtime --get =1000000000 rename >expect &&
+	test-tool chmtime --get -3600 rename >expect &&
 	git merge merge-branch-1 &&
 	test-tool chmtime --get rename >actual &&
 	test_cmp expect actual # "rename" should have stayed intact
@@ -667,7 +678,7 @@ test_expect_success 'setup to test avoiding unnecessary update, with D/F conflic
 
 test_expect_success 'avoid unnecessary update, with D/F conflict' '
 	git checkout -q avoid-unnecessary-update-2^0 &&
-	test-tool chmtime --get =1000000000 df >expect &&
+	test-tool chmtime --get -3600 df >expect &&
 	git merge merge-branch-2 &&
 	test-tool chmtime --get df >actual &&
 	test_cmp expect actual # "df" should have stayed intact
@@ -698,7 +709,7 @@ test_expect_success 'setup avoid unnecessary update, dir->(file,nothing)' '
 
 test_expect_success 'avoid unnecessary update, dir->(file,nothing)' '
 	git checkout -q master^0 &&
-	test-tool chmtime --get =1000000000 df >expect &&
+	test-tool chmtime --get -3600 df >expect &&
 	git merge side &&
 	test-tool chmtime --get df >actual &&
 	test_cmp expect actual # "df" should have stayed intact
@@ -727,7 +738,7 @@ test_expect_success 'setup avoid unnecessary update, modify/delete' '
 
 test_expect_success 'avoid unnecessary update, modify/delete' '
 	git checkout -q master^0 &&
-	test-tool chmtime --get =1000000000 file >expect &&
+	test-tool chmtime --get -3600 file >expect &&
 	test_must_fail git merge side &&
 	test-tool chmtime --get file >actual &&
 	test_cmp expect actual # "file" should have stayed intact
@@ -755,7 +766,7 @@ test_expect_success 'setup avoid unnecessary update, rename/add-dest' '
 
 test_expect_success 'avoid unnecessary update, rename/add-dest' '
 	git checkout -q master^0 &&
-	test-tool chmtime --get =1000000000 newfile >expect &&
+	test-tool chmtime --get -3600 newfile >expect &&
 	git merge side &&
 	test-tool chmtime --get newfile >actual &&
 	test_cmp expect actual # "file" should have stayed intact
@@ -810,48 +821,48 @@ test_expect_success 'setup for use of extended merge markers' '
 	git commit -mC
 '
 
-cat >expected <<\EOF &&
-1
-2
-3
-4
-5
-6
-7
-8
-<<<<<<< HEAD:renamed_file
-9
-=======
-8.5
->>>>>>> master^0:original_file
-EOF
-
 test_expect_success 'merge master into rename has correct extended markers' '
 	git checkout rename^0 &&
 	test_must_fail git merge -s recursive master^0 &&
+
+	cat >expected <<-\EOF &&
+	1
+	2
+	3
+	4
+	5
+	6
+	7
+	8
+	<<<<<<< HEAD:renamed_file
+	9
+	=======
+	8.5
+	>>>>>>> master^0:original_file
+	EOF
 	test_cmp expected renamed_file
 '
 
-cat >expected <<\EOF &&
-1
-2
-3
-4
-5
-6
-7
-8
-<<<<<<< HEAD:original_file
-8.5
-=======
-9
->>>>>>> rename^0:renamed_file
-EOF
-
 test_expect_success 'merge rename into master has correct extended markers' '
 	git reset --hard &&
 	git checkout master^0 &&
 	test_must_fail git merge -s recursive rename^0 &&
+
+	cat >expected <<-\EOF &&
+	1
+	2
+	3
+	4
+	5
+	6
+	7
+	8
+	<<<<<<< HEAD:original_file
+	8.5
+	=======
+	9
+	>>>>>>> rename^0:renamed_file
+	EOF
 	test_cmp expected renamed_file
 '
 
diff --git a/third_party/git/t/t6403-merge-file.sh b/third_party/git/t/t6403-merge-file.sh
new file mode 100755
index 0000000000..2f421d967a
--- /dev/null
+++ b/third_party/git/t/t6403-merge-file.sh
@@ -0,0 +1,390 @@
+#!/bin/sh
+
+test_description='RCS merge replacement: merge-file'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	cat >orig.txt <<-\EOF &&
+	Dominus regit me,
+	et nihil mihi deerit.
+	In loco pascuae ibi me collocavit,
+	super aquam refectionis educavit me;
+	animam meam convertit,
+	deduxit me super semitas jusitiae,
+	propter nomen suum.
+	EOF
+
+	cat >new1.txt <<-\EOF &&
+	Dominus regit me,
+	et nihil mihi deerit.
+	In loco pascuae ibi me collocavit,
+	super aquam refectionis educavit me;
+	animam meam convertit,
+	deduxit me super semitas jusitiae,
+	propter nomen suum.
+	Nam et si ambulavero in medio umbrae mortis,
+	non timebo mala, quoniam tu mecum es:
+	virga tua et baculus tuus ipsa me consolata sunt.
+	EOF
+
+	cat >new2.txt <<-\EOF &&
+	Dominus regit me, et nihil mihi deerit.
+	In loco pascuae ibi me collocavit,
+	super aquam refectionis educavit me;
+	animam meam convertit,
+	deduxit me super semitas jusitiae,
+	propter nomen suum.
+	EOF
+
+	cat >new3.txt <<-\EOF &&
+	DOMINUS regit me,
+	et nihil mihi deerit.
+	In loco pascuae ibi me collocavit,
+	super aquam refectionis educavit me;
+	animam meam convertit,
+	deduxit me super semitas jusitiae,
+	propter nomen suum.
+	EOF
+
+	cat >new4.txt <<-\EOF &&
+	Dominus regit me, et nihil mihi deerit.
+	In loco pascuae ibi me collocavit,
+	super aquam refectionis educavit me;
+	animam meam convertit,
+	deduxit me super semitas jusitiae,
+	EOF
+
+	printf "propter nomen suum." >>new4.txt
+'
+
+test_expect_success 'merge with no changes' '
+	cp orig.txt test.txt &&
+	git merge-file test.txt orig.txt orig.txt &&
+	test_cmp test.txt orig.txt
+'
+
+test_expect_success "merge without conflict" '
+	cp new1.txt test.txt &&
+	git merge-file test.txt orig.txt new2.txt
+'
+
+test_expect_success 'works in subdirectory' '
+	mkdir dir &&
+	cp new1.txt dir/a.txt &&
+	cp orig.txt dir/o.txt &&
+	cp new2.txt dir/b.txt &&
+	( cd dir && git merge-file a.txt o.txt b.txt ) &&
+	test_path_is_missing a.txt
+'
+
+test_expect_success "merge without conflict (--quiet)" '
+	cp new1.txt test.txt &&
+	git merge-file --quiet test.txt orig.txt new2.txt
+'
+
+test_expect_failure "merge without conflict (missing LF at EOF)" '
+	cp new1.txt test2.txt &&
+	git merge-file test2.txt orig.txt new4.txt
+'
+
+test_expect_failure "merge result added missing LF" '
+	test_cmp test.txt test2.txt
+'
+
+test_expect_success "merge without conflict (missing LF at EOF, away from change in the other file)" '
+	cp new4.txt test3.txt &&
+	git merge-file --quiet test3.txt new2.txt new3.txt
+'
+
+test_expect_success "merge does not add LF away of change" '
+	cat >expect.txt <<-\EOF &&
+	DOMINUS regit me,
+	et nihil mihi deerit.
+	In loco pascuae ibi me collocavit,
+	super aquam refectionis educavit me;
+	animam meam convertit,
+	deduxit me super semitas jusitiae,
+	EOF
+	printf "propter nomen suum." >>expect.txt &&
+
+	test_cmp expect.txt test3.txt
+'
+
+test_expect_success "merge with conflicts" '
+	cp test.txt backup.txt &&
+	test_must_fail git merge-file test.txt orig.txt new3.txt
+'
+
+test_expect_success "expected conflict markers" '
+	cat >expect.txt <<-\EOF &&
+	<<<<<<< test.txt
+	Dominus regit me, et nihil mihi deerit.
+	=======
+	DOMINUS regit me,
+	et nihil mihi deerit.
+	>>>>>>> new3.txt
+	In loco pascuae ibi me collocavit,
+	super aquam refectionis educavit me;
+	animam meam convertit,
+	deduxit me super semitas jusitiae,
+	propter nomen suum.
+	Nam et si ambulavero in medio umbrae mortis,
+	non timebo mala, quoniam tu mecum es:
+	virga tua et baculus tuus ipsa me consolata sunt.
+	EOF
+
+	test_cmp expect.txt test.txt
+'
+
+test_expect_success "merge conflicting with --ours" '
+	cp backup.txt test.txt &&
+
+	cat >expect.txt <<-\EOF &&
+	Dominus regit me, et nihil mihi deerit.
+	In loco pascuae ibi me collocavit,
+	super aquam refectionis educavit me;
+	animam meam convertit,
+	deduxit me super semitas jusitiae,
+	propter nomen suum.
+	Nam et si ambulavero in medio umbrae mortis,
+	non timebo mala, quoniam tu mecum es:
+	virga tua et baculus tuus ipsa me consolata sunt.
+	EOF
+
+	git merge-file --ours test.txt orig.txt new3.txt &&
+	test_cmp expect.txt test.txt
+'
+
+test_expect_success "merge conflicting with --theirs" '
+	cp backup.txt test.txt &&
+
+	cat >expect.txt <<-\EOF &&
+	DOMINUS regit me,
+	et nihil mihi deerit.
+	In loco pascuae ibi me collocavit,
+	super aquam refectionis educavit me;
+	animam meam convertit,
+	deduxit me super semitas jusitiae,
+	propter nomen suum.
+	Nam et si ambulavero in medio umbrae mortis,
+	non timebo mala, quoniam tu mecum es:
+	virga tua et baculus tuus ipsa me consolata sunt.
+	EOF
+
+	git merge-file --theirs test.txt orig.txt new3.txt &&
+	test_cmp expect.txt test.txt
+'
+
+test_expect_success "merge conflicting with --union" '
+	cp backup.txt test.txt &&
+
+	cat >expect.txt <<-\EOF &&
+	Dominus regit me, et nihil mihi deerit.
+	DOMINUS regit me,
+	et nihil mihi deerit.
+	In loco pascuae ibi me collocavit,
+	super aquam refectionis educavit me;
+	animam meam convertit,
+	deduxit me super semitas jusitiae,
+	propter nomen suum.
+	Nam et si ambulavero in medio umbrae mortis,
+	non timebo mala, quoniam tu mecum es:
+	virga tua et baculus tuus ipsa me consolata sunt.
+	EOF
+
+	git merge-file --union test.txt orig.txt new3.txt &&
+	test_cmp expect.txt test.txt
+'
+
+test_expect_success "merge with conflicts, using -L" '
+	cp backup.txt test.txt &&
+
+	test_must_fail git merge-file -L 1 -L 2 test.txt orig.txt new3.txt
+'
+
+test_expect_success "expected conflict markers, with -L" '
+	cat >expect.txt <<-\EOF &&
+	<<<<<<< 1
+	Dominus regit me, et nihil mihi deerit.
+	=======
+	DOMINUS regit me,
+	et nihil mihi deerit.
+	>>>>>>> new3.txt
+	In loco pascuae ibi me collocavit,
+	super aquam refectionis educavit me;
+	animam meam convertit,
+	deduxit me super semitas jusitiae,
+	propter nomen suum.
+	Nam et si ambulavero in medio umbrae mortis,
+	non timebo mala, quoniam tu mecum es:
+	virga tua et baculus tuus ipsa me consolata sunt.
+	EOF
+
+	test_cmp expect.txt test.txt
+'
+
+test_expect_success "conflict in removed tail" '
+	sed "s/ tu / TU /" <new1.txt >new5.txt &&
+	test_must_fail git merge-file -p orig.txt new1.txt new5.txt >out
+'
+
+test_expect_success "expected conflict markers" '
+	cat >expect <<-\EOF &&
+	Dominus regit me,
+	et nihil mihi deerit.
+	In loco pascuae ibi me collocavit,
+	super aquam refectionis educavit me;
+	animam meam convertit,
+	deduxit me super semitas jusitiae,
+	propter nomen suum.
+	<<<<<<< orig.txt
+	=======
+	Nam et si ambulavero in medio umbrae mortis,
+	non timebo mala, quoniam TU mecum es:
+	virga tua et baculus tuus ipsa me consolata sunt.
+	>>>>>>> new5.txt
+	EOF
+
+	test_cmp expect out
+'
+
+test_expect_success 'binary files cannot be merged' '
+	test_must_fail git merge-file -p \
+		orig.txt "$TEST_DIRECTORY"/test-binary-1.png new1.txt 2> merge.err &&
+	grep "Cannot merge binary files" merge.err
+'
+
+test_expect_success 'MERGE_ZEALOUS simplifies non-conflicts' '
+	sed -e "s/deerit.\$/deerit;/" -e "s/me;\$/me./" <new5.txt >new6.txt &&
+	sed -e "s/deerit.\$/deerit,/" -e "s/me;\$/me,/" <new5.txt >new7.txt &&
+
+	test_must_fail git merge-file -p new6.txt new5.txt new7.txt > output &&
+	test 1 = $(grep ======= <output | wc -l)
+'
+
+test_expect_success 'ZEALOUS_ALNUM' '
+	sed -e "s/deerit./&%%%%/" -e "s/locavit,/locavit;/" <new6.txt | tr % "\012" >new8.txt &&
+	sed -e "s/deerit./&%%%%/" -e "s/locavit,/locavit --/" <new7.txt | tr % "\012" >new9.txt &&
+
+	test_must_fail git merge-file -p \
+		new8.txt new5.txt new9.txt >merge.out &&
+	test 1 = $(grep ======= <merge.out | wc -l)
+'
+
+test_expect_success '"diff3 -m" style output (1)' '
+	cat >expect <<-\EOF &&
+	Dominus regit me,
+	<<<<<<< new8.txt
+	et nihil mihi deerit;
+
+
+
+
+	In loco pascuae ibi me collocavit;
+	super aquam refectionis educavit me.
+	||||||| new5.txt
+	et nihil mihi deerit.
+	In loco pascuae ibi me collocavit,
+	super aquam refectionis educavit me;
+	=======
+	et nihil mihi deerit,
+
+
+
+
+	In loco pascuae ibi me collocavit --
+	super aquam refectionis educavit me,
+	>>>>>>> new9.txt
+	animam meam convertit,
+	deduxit me super semitas jusitiae,
+	propter nomen suum.
+	Nam et si ambulavero in medio umbrae mortis,
+	non timebo mala, quoniam TU mecum es:
+	virga tua et baculus tuus ipsa me consolata sunt.
+	EOF
+
+	test_must_fail git merge-file -p --diff3 \
+		new8.txt new5.txt new9.txt >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success '"diff3 -m" style output (2)' '
+	git config merge.conflictstyle diff3 &&
+	test_must_fail git merge-file -p \
+		new8.txt new5.txt new9.txt >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'marker size' '
+	cat >expect <<-\EOF &&
+	Dominus regit me,
+	<<<<<<<<<< new8.txt
+	et nihil mihi deerit;
+
+
+
+
+	In loco pascuae ibi me collocavit;
+	super aquam refectionis educavit me.
+	|||||||||| new5.txt
+	et nihil mihi deerit.
+	In loco pascuae ibi me collocavit,
+	super aquam refectionis educavit me;
+	==========
+	et nihil mihi deerit,
+
+
+
+
+	In loco pascuae ibi me collocavit --
+	super aquam refectionis educavit me,
+	>>>>>>>>>> new9.txt
+	animam meam convertit,
+	deduxit me super semitas jusitiae,
+	propter nomen suum.
+	Nam et si ambulavero in medio umbrae mortis,
+	non timebo mala, quoniam TU mecum es:
+	virga tua et baculus tuus ipsa me consolata sunt.
+	EOF
+
+	test_must_fail git merge-file -p --marker-size=10 \
+		new8.txt new5.txt new9.txt >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'conflict at EOF without LF resolved by --ours' '
+	printf "line1\nline2\nline3" >nolf-orig.txt &&
+	printf "line1\nline2\nline3x" >nolf-diff1.txt &&
+	printf "line1\nline2\nline3y" >nolf-diff2.txt &&
+
+	git merge-file -p --ours nolf-diff1.txt nolf-orig.txt nolf-diff2.txt >output.txt &&
+	printf "line1\nline2\nline3x" >expect.txt &&
+	test_cmp expect.txt output.txt
+'
+
+test_expect_success 'conflict at EOF without LF resolved by --theirs' '
+	git merge-file -p --theirs nolf-diff1.txt nolf-orig.txt nolf-diff2.txt >output.txt &&
+	printf "line1\nline2\nline3y" >expect.txt &&
+	test_cmp expect.txt output.txt
+'
+
+test_expect_success 'conflict at EOF without LF resolved by --union' '
+	git merge-file -p --union nolf-diff1.txt nolf-orig.txt nolf-diff2.txt >output.txt &&
+	printf "line1\nline2\nline3x\nline3y" >expect.txt &&
+	test_cmp expect.txt output.txt
+'
+
+test_expect_success 'conflict sections match existing line endings' '
+	printf "1\\r\\n2\\r\\n3" >crlf-orig.txt &&
+	printf "1\\r\\n2\\r\\n4" >crlf-diff1.txt &&
+	printf "1\\r\\n2\\r\\n5" >crlf-diff2.txt &&
+	test_must_fail git -c core.eol=crlf merge-file -p \
+		crlf-diff1.txt crlf-orig.txt crlf-diff2.txt >crlf.txt &&
+	test $(tr "\015" Q <crlf.txt | grep "^[<=>].*Q$" | wc -l) = 3 &&
+	test $(tr "\015" Q <crlf.txt | grep "[345]Q$" | wc -l) = 3 &&
+	test_must_fail git -c core.eol=crlf merge-file -p \
+		nolf-diff1.txt nolf-orig.txt nolf-diff2.txt >nolf.txt &&
+	test $(tr "\015" Q <nolf.txt | grep "^[<=>].*Q$" | wc -l) = 0
+'
+
+test_done
diff --git a/third_party/git/t/t6404-recursive-merge.sh b/third_party/git/t/t6404-recursive-merge.sh
new file mode 100755
index 0000000000..332cfc53fd
--- /dev/null
+++ b/third_party/git/t/t6404-recursive-merge.sh
@@ -0,0 +1,131 @@
+#!/bin/sh
+
+test_description='Test merge without common ancestors'
+. ./test-lib.sh
+
+# This scenario is based on a real-world repository of Shawn Pearce.
+
+# 1 - A - D - F
+#   \   X   /
+#     B   X
+#       X   \
+# 2 - C - E - G
+
+GIT_COMMITTER_DATE="2006-12-12 23:28:00 +0100"
+export GIT_COMMITTER_DATE
+
+test_expect_success 'setup tests' '
+	echo 1 >a1 &&
+	git add a1 &&
+	GIT_AUTHOR_DATE="2006-12-12 23:00:00" git commit -m 1 a1 &&
+
+	git checkout -b A master &&
+	echo A >a1 &&
+	GIT_AUTHOR_DATE="2006-12-12 23:00:01" git commit -m A a1 &&
+
+	git checkout -b B master &&
+	echo B >a1 &&
+	GIT_AUTHOR_DATE="2006-12-12 23:00:02" git commit -m B a1 &&
+
+	git checkout -b D A &&
+	git rev-parse B >.git/MERGE_HEAD &&
+	echo D >a1 &&
+	git update-index a1 &&
+	GIT_AUTHOR_DATE="2006-12-12 23:00:03" git commit -m D &&
+
+	git symbolic-ref HEAD refs/heads/other &&
+	echo 2 >a1 &&
+	GIT_AUTHOR_DATE="2006-12-12 23:00:04" git commit -m 2 a1 &&
+
+	git checkout -b C &&
+	echo C >a1 &&
+	GIT_AUTHOR_DATE="2006-12-12 23:00:05" git commit -m C a1 &&
+
+	git checkout -b E C &&
+	git rev-parse B >.git/MERGE_HEAD &&
+	echo E >a1 &&
+	git update-index a1 &&
+	GIT_AUTHOR_DATE="2006-12-12 23:00:06" git commit -m E &&
+
+	git checkout -b G E &&
+	git rev-parse A >.git/MERGE_HEAD &&
+	echo G >a1 &&
+	git update-index a1 &&
+	GIT_AUTHOR_DATE="2006-12-12 23:00:07" git commit -m G &&
+
+	git checkout -b F D &&
+	git rev-parse C >.git/MERGE_HEAD &&
+	echo F >a1 &&
+	git update-index a1 &&
+	GIT_AUTHOR_DATE="2006-12-12 23:00:08" git commit -m F &&
+
+	test_oid_cache <<-EOF
+	idxstage1 sha1:ec3fe2a791706733f2d8fa7ad45d9a9672031f5e
+	idxstage1 sha256:b3c8488929903aaebdeb22270cb6d36e5b8724b01ae0d4da24632f158c99676f
+	EOF
+'
+
+test_expect_success 'combined merge conflicts' '
+	test_must_fail env GIT_TEST_COMMIT_GRAPH=0 git merge -m final G
+'
+
+test_expect_success 'result contains a conflict' '
+	cat >expect <<-\EOF &&
+	<<<<<<< HEAD
+	F
+	=======
+	G
+	>>>>>>> G
+	EOF
+
+	test_cmp expect a1
+'
+
+test_expect_success 'virtual trees were processed' '
+	git ls-files --stage >out &&
+
+	cat >expect <<-EOF &&
+	100644 $(test_oid idxstage1) 1	a1
+	100644 $(git rev-parse F:a1) 2	a1
+	100644 $(git rev-parse G:a1) 3	a1
+	EOF
+
+	test_cmp expect out
+'
+
+test_expect_success 'refuse to merge binary files' '
+	git reset --hard &&
+	printf "\0" >binary-file &&
+	git add binary-file &&
+	git commit -m binary &&
+	git checkout G &&
+	printf "\0\0" >binary-file &&
+	git add binary-file &&
+	git commit -m binary2 &&
+	test_must_fail git merge F >merge.out 2>merge.err &&
+	grep "Cannot merge binary files: binary-file (HEAD vs. F)" merge.err
+'
+
+test_expect_success 'mark rename/delete as unmerged' '
+
+	git reset --hard &&
+	git checkout -b delete &&
+	git rm a1 &&
+	test_tick &&
+	git commit -m delete &&
+	git checkout -b rename HEAD^ &&
+	git mv a1 a2 &&
+	test_tick &&
+	git commit -m rename &&
+	test_must_fail git merge delete &&
+	test 1 = $(git ls-files --unmerged | wc -l) &&
+	git rev-parse --verify :2:a2 &&
+	test_must_fail git rev-parse --verify :3:a2 &&
+	git checkout -f delete &&
+	test_must_fail git merge rename &&
+	test 1 = $(git ls-files --unmerged | wc -l) &&
+	test_must_fail git rev-parse --verify :2:a2 &&
+	git rev-parse --verify :3:a2
+'
+
+test_done
diff --git a/third_party/git/t/t6405-merge-symlinks.sh b/third_party/git/t/t6405-merge-symlinks.sh
new file mode 100755
index 0000000000..6c0a90d044
--- /dev/null
+++ b/third_party/git/t/t6405-merge-symlinks.sh
@@ -0,0 +1,62 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Johannes Sixt
+#
+
+test_description='merging symlinks on filesystem w/o symlink support.
+
+This tests that git merge-recursive writes merge results as plain files
+if core.symlinks is false.'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	git config core.symlinks false &&
+	>file &&
+	git add file &&
+	git commit -m initial &&
+	git branch b-symlink &&
+	git branch b-file &&
+	l=$(printf file | git hash-object -t blob -w --stdin) &&
+	echo "120000 $l	symlink" | git update-index --index-info &&
+	git commit -m master &&
+	git checkout b-symlink &&
+	l=$(printf file-different | git hash-object -t blob -w --stdin) &&
+	echo "120000 $l	symlink" | git update-index --index-info &&
+	git commit -m b-symlink &&
+	git checkout b-file &&
+	echo plain-file >symlink &&
+	git add symlink &&
+	git commit -m b-file
+'
+
+test_expect_success 'merge master into b-symlink, which has a different symbolic link' '
+	git checkout b-symlink &&
+	test_must_fail git merge master
+'
+
+test_expect_success 'the merge result must be a file' '
+	test_path_is_file symlink
+'
+
+test_expect_success 'merge master into b-file, which has a file instead of a symbolic link' '
+	git reset --hard &&
+	git checkout b-file &&
+	test_must_fail git merge master
+'
+
+test_expect_success 'the merge result must be a file' '
+	test_path_is_file symlink
+'
+
+test_expect_success 'merge b-file, which has a file instead of a symbolic link, into master' '
+	git reset --hard &&
+	git checkout master &&
+	test_must_fail git merge b-file
+'
+
+test_expect_success 'the merge result must be a file' '
+	test_path_is_file symlink
+'
+
+test_done
diff --git a/third_party/git/t/t6026-merge-attr.sh b/third_party/git/t/t6406-merge-attr.sh
index 8f9b48a493..76a55f838c 100755
--- a/third_party/git/t/t6026-merge-attr.sh
+++ b/third_party/git/t/t6406-merge-attr.sh
@@ -32,7 +32,29 @@ test_expect_success setup '
 	test_tick &&
 	git commit -m Side &&
 
-	git tag anchor
+	git tag anchor &&
+
+	cat >./custom-merge <<-\EOF &&
+	#!/bin/sh
+
+	orig="$1" ours="$2" theirs="$3" exit="$4" path=$5
+	(
+		echo "orig is $orig"
+		echo "ours is $ours"
+		echo "theirs is $theirs"
+		echo "path is $path"
+		echo "=== orig ==="
+		cat "$orig"
+		echo "=== ours ==="
+		cat "$ours"
+		echo "=== theirs ==="
+		cat "$theirs"
+	) >"$ours+"
+	cat "$ours+" >"$ours"
+	rm -f "$ours+"
+	exit "$exit"
+	EOF
+	chmod +x ./custom-merge
 '
 
 test_expect_success merge '
@@ -82,28 +104,6 @@ test_expect_success 'retry the merge with longer context' '
 	grep "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" actual
 '
 
-cat >./custom-merge <<\EOF
-#!/bin/sh
-
-orig="$1" ours="$2" theirs="$3" exit="$4" path=$5
-(
-	echo "orig is $orig"
-	echo "ours is $ours"
-	echo "theirs is $theirs"
-	echo "path is $path"
-	echo "=== orig ==="
-	cat "$orig"
-	echo "=== ours ==="
-	cat "$ours"
-	echo "=== theirs ==="
-	cat "$theirs"
-) >"$ours+"
-cat "$ours+" >"$ours"
-rm -f "$ours+"
-exit "$exit"
-EOF
-chmod +x ./custom-merge
-
 test_expect_success 'custom merge backend' '
 
 	echo "* merge=union" >.gitattributes &&
@@ -122,7 +122,7 @@ test_expect_success 'custom merge backend' '
 	o=$(git unpack-file master^:text) &&
 	a=$(git unpack-file side^:text) &&
 	b=$(git unpack-file master:text) &&
-	sh -c "./custom-merge $o $a $b 0 'text'" &&
+	sh -c "./custom-merge $o $a $b 0 text" &&
 	sed -e 1,3d $a >check-2 &&
 	cmp check-1 check-2 &&
 	rm -f $o $a $b
@@ -149,7 +149,7 @@ test_expect_success 'custom merge backend' '
 	o=$(git unpack-file master^:text) &&
 	a=$(git unpack-file anchor:text) &&
 	b=$(git unpack-file master:text) &&
-	sh -c "./custom-merge $o $a $b 0 'text'" &&
+	sh -c "./custom-merge $o $a $b 0 text" &&
 	sed -e 1,3d $a >check-2 &&
 	cmp check-1 check-2 &&
 	sed -e 1,3d -e 4q $a >check-3 &&
diff --git a/third_party/git/t/t6027-merge-binary.sh b/third_party/git/t/t6407-merge-binary.sh
index 4e6c7cb77e..4e6c7cb77e 100755
--- a/third_party/git/t/t6027-merge-binary.sh
+++ b/third_party/git/t/t6407-merge-binary.sh
diff --git a/third_party/git/t/t6028-merge-up-to-date.sh b/third_party/git/t/t6408-merge-up-to-date.sh
index 7763c1ba98..7763c1ba98 100755
--- a/third_party/git/t/t6028-merge-up-to-date.sh
+++ b/third_party/git/t/t6408-merge-up-to-date.sh
diff --git a/third_party/git/t/t6029-merge-subtree.sh b/third_party/git/t/t6409-merge-subtree.sh
index 793f0c8bf3..b8e8b6f642 100755
--- a/third_party/git/t/t6029-merge-subtree.sh
+++ b/third_party/git/t/t6409-merge-subtree.sh
@@ -35,11 +35,11 @@ test_expect_success 'setup branch sub' '
 	test_commit foo
 '
 
-test_expect_success 'setup branch main' '
-	git checkout -b main master &&
+test_expect_success 'setup topic branch' '
+	git checkout -b topic master &&
 	git merge -s ours --no-commit --allow-unrelated-histories sub &&
 	git read-tree --prefix=dir/ -u sub &&
-	git commit -m "initial merge of sub into main" &&
+	git commit -m "initial merge of sub into topic" &&
 	test_path_is_file dir/foo.t &&
 	test_path_is_file hello
 '
@@ -49,9 +49,9 @@ test_expect_success 'update branch sub' '
 	test_commit bar
 '
 
-test_expect_success 'update branch main' '
-	git checkout main &&
-	git merge -s subtree sub -m "second merge of sub into main" &&
+test_expect_success 'update topic branch' '
+	git checkout topic &&
+	git merge -s subtree sub -m "second merge of sub into topic" &&
 	test_path_is_file dir/bar.t &&
 	test_path_is_file dir/foo.t &&
 	test_path_is_file hello
@@ -94,10 +94,10 @@ test_expect_success 'merge update' '
 	echo git-gui2 > git-gui.sh &&
 	o3=$(git hash-object git-gui.sh) &&
 	git add git-gui.sh &&
-	git checkout -b master2 &&
+	git checkout -b topic_2 &&
 	git commit -m "update git-gui" &&
 	cd ../git &&
-	git pull -s subtree gui master2 &&
+	git pull -s subtree gui topic_2 &&
 	git ls-files -s >actual &&
 	(
 		echo "100644 $o3 0	git-gui/git-gui.sh" &&
@@ -109,7 +109,7 @@ test_expect_success 'merge update' '
 test_expect_success 'initial ambiguous subtree' '
 	cd ../git &&
 	git reset --hard master &&
-	git checkout -b master2 &&
+	git checkout -b topic_2 &&
 	git merge -s ours --no-commit gui/master &&
 	git read-tree --prefix=git-gui2/ -u gui/master &&
 	git commit -m "Merge git-gui2 as our subdirectory" &&
@@ -125,8 +125,8 @@ test_expect_success 'initial ambiguous subtree' '
 
 test_expect_success 'merge using explicit' '
 	cd ../git &&
-	git reset --hard master2 &&
-	git pull -Xsubtree=git-gui gui master2 &&
+	git reset --hard topic_2 &&
+	git pull -Xsubtree=git-gui gui topic_2 &&
 	git ls-files -s >actual &&
 	(
 		echo "100644 $o3 0	git-gui/git-gui.sh" &&
@@ -138,8 +138,8 @@ test_expect_success 'merge using explicit' '
 
 test_expect_success 'merge2 using explicit' '
 	cd ../git &&
-	git reset --hard master2 &&
-	git pull -Xsubtree=git-gui2 gui master2 &&
+	git reset --hard topic_2 &&
+	git pull -Xsubtree=git-gui2 gui topic_2 &&
 	git ls-files -s >actual &&
 	(
 		echo "100644 $o1 0	git-gui/git-gui.sh" &&
diff --git a/third_party/git/t/t6031-merge-filemode.sh b/third_party/git/t/t6411-merge-filemode.sh
index 87741efad3..87741efad3 100755
--- a/third_party/git/t/t6031-merge-filemode.sh
+++ b/third_party/git/t/t6411-merge-filemode.sh
diff --git a/third_party/git/t/t6032-merge-large-rename.sh b/third_party/git/t/t6412-merge-large-rename.sh
index 80777386dc..80777386dc 100755
--- a/third_party/git/t/t6032-merge-large-rename.sh
+++ b/third_party/git/t/t6412-merge-large-rename.sh
diff --git a/third_party/git/t/t6033-merge-crlf.sh b/third_party/git/t/t6413-merge-crlf.sh
index e8d65eefb5..e8d65eefb5 100755
--- a/third_party/git/t/t6033-merge-crlf.sh
+++ b/third_party/git/t/t6413-merge-crlf.sh
diff --git a/third_party/git/t/t6414-merge-rename-nocruft.sh b/third_party/git/t/t6414-merge-rename-nocruft.sh
new file mode 100755
index 0000000000..a25e730460
--- /dev/null
+++ b/third_party/git/t/t6414-merge-rename-nocruft.sh
@@ -0,0 +1,97 @@
+#!/bin/sh
+
+test_description='Merge-recursive merging renames'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	cat >A <<-\EOF &&
+	a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+	b bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+	c cccccccccccccccccccccccccccccccccccccccccccccccc
+	d dddddddddddddddddddddddddddddddddddddddddddddddd
+	e eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
+	f ffffffffffffffffffffffffffffffffffffffffffffffff
+	g gggggggggggggggggggggggggggggggggggggggggggggggg
+	h hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
+	i iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
+	j jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj
+	k kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk
+	l llllllllllllllllllllllllllllllllllllllllllllllll
+	m mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
+	n nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
+	o oooooooooooooooooooooooooooooooooooooooooooooooo
+	EOF
+
+	cat >M <<-\EOF &&
+	A AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	B BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
+	C CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
+	D DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
+	E EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
+	F FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+	G GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG
+	H HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH
+	I IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII
+	J JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ
+	K KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK
+	L LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL
+	M MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
+	N NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN
+	O OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO
+	EOF
+
+	git add A M &&
+	git commit -m "initial has A and M" &&
+	git branch white &&
+	git branch red &&
+	git branch blue &&
+
+	git checkout white &&
+	sed -e "/^g /s/.*/g : white changes a line/" <A >B &&
+	sed -e "/^G /s/.*/G : colored branch changes a line/" <M >N &&
+	rm -f A M &&
+	git update-index --add --remove A B M N &&
+	git commit -m "white renames A->B, M->N" &&
+
+	git checkout red &&
+	echo created by red >R &&
+	git update-index --add R &&
+	git commit -m "red creates R" &&
+
+	git checkout blue &&
+	sed -e "/^o /s/.*/g : blue changes a line/" <A >B &&
+	rm -f A &&
+	mv B A &&
+	git update-index A &&
+	git commit -m "blue modify A" &&
+
+	git checkout master
+'
+
+# This test broke in 65ac6e9c3f47807cb603af07a6a9e1a43bc119ae
+test_expect_success 'merge white into red (A->B,M->N)' '
+	git checkout -b red-white red &&
+	git merge white &&
+	git write-tree &&
+	test_path_is_file B &&
+	test_path_is_file N &&
+	test_path_is_file R &&
+	test_path_is_missing A &&
+	test_path_is_missing M
+'
+
+# This test broke in 8371234ecaaf6e14fe3f2082a855eff1bbd79ae9
+test_expect_success 'merge blue into white (A->B, mod A, A untracked)' '
+	git checkout -b white-blue white &&
+	echo dirty >A &&
+	git merge blue &&
+	git write-tree &&
+	test_path_is_file A &&
+	echo dirty >expect &&
+	test_cmp expect A &&
+	test_path_is_file B &&
+	test_path_is_file N &&
+	test_path_is_missing M
+'
+
+test_done
diff --git a/third_party/git/t/t6035-merge-dir-to-symlink.sh b/third_party/git/t/t6415-merge-dir-to-symlink.sh
index 9324ea4416..2eddcc7664 100755
--- a/third_party/git/t/t6035-merge-dir-to-symlink.sh
+++ b/third_party/git/t/t6415-merge-dir-to-symlink.sh
@@ -31,19 +31,19 @@ test_expect_success 'a/b-2/c/d is kept when clobbering symlink b' '
 	git rm --cached a/b &&
 	git commit -m "untracked symlink remains" &&
 	git checkout -f start^0 &&
-	test -f a/b-2/c/d
+	test_path_is_file a/b-2/c/d
 '
 
 test_expect_success 'checkout should not have deleted a/b-2/c/d' '
 	git checkout HEAD^0 &&
 	git reset --hard master &&
 	 git checkout start^0 &&
-	 test -f a/b-2/c/d
+	 test_path_is_file a/b-2/c/d
 '
 
 test_expect_success 'setup for merge test' '
 	git reset --hard &&
-	test -f a/b-2/c/d &&
+	test_path_is_file a/b-2/c/d &&
 	echo x > a/x &&
 	git add a/x &&
 	git commit -m x &&
@@ -54,7 +54,7 @@ test_expect_success 'Handle D/F conflict, do not lose a/b-2/c/d in merge (resolv
 	git reset --hard &&
 	git checkout baseline^0 &&
 	git merge -s resolve master &&
-	test -f a/b-2/c/d
+	test_path_is_file a/b-2/c/d
 '
 
 test_expect_success SYMLINKS 'a/b was resolved as symlink' '
@@ -65,7 +65,7 @@ test_expect_success 'Handle D/F conflict, do not lose a/b-2/c/d in merge (recurs
 	git reset --hard &&
 	git checkout baseline^0 &&
 	git merge -s recursive master &&
-	test -f a/b-2/c/d
+	test_path_is_file a/b-2/c/d
 '
 
 test_expect_success SYMLINKS 'a/b was resolved as symlink' '
@@ -76,7 +76,7 @@ test_expect_success 'Handle F/D conflict, do not lose a/b-2/c/d in merge (resolv
 	git reset --hard &&
 	git checkout master^0 &&
 	git merge -s resolve baseline^0 &&
-	test -f a/b-2/c/d
+	test_path_is_file a/b-2/c/d
 '
 
 test_expect_success SYMLINKS 'a/b was resolved as symlink' '
@@ -87,7 +87,7 @@ test_expect_success 'Handle F/D conflict, do not lose a/b-2/c/d in merge (recurs
 	git reset --hard &&
 	git checkout master^0 &&
 	git merge -s recursive baseline^0 &&
-	test -f a/b-2/c/d
+	test_path_is_file a/b-2/c/d
 '
 
 test_expect_success SYMLINKS 'a/b was resolved as symlink' '
@@ -99,8 +99,8 @@ test_expect_failure 'do not lose untracked in merge (resolve)' '
 	git checkout baseline^0 &&
 	>a/b/c/e &&
 	test_must_fail git merge -s resolve master &&
-	test -f a/b/c/e &&
-	test -f a/b-2/c/d
+	test_path_is_file a/b/c/e &&
+	test_path_is_file a/b-2/c/d
 '
 
 test_expect_success 'do not lose untracked in merge (recursive)' '
@@ -108,8 +108,8 @@ test_expect_success 'do not lose untracked in merge (recursive)' '
 	git checkout baseline^0 &&
 	>a/b/c/e &&
 	test_must_fail git merge -s recursive master &&
-	test -f a/b/c/e &&
-	test -f a/b-2/c/d
+	test_path_is_file a/b/c/e &&
+	test_path_is_file a/b-2/c/d
 '
 
 test_expect_success 'do not lose modifications in merge (resolve)' '
@@ -140,7 +140,7 @@ test_expect_success 'merge should not have D/F conflicts (resolve)' '
 	git reset --hard &&
 	git checkout baseline^0 &&
 	git merge -s resolve test2 &&
-	test -f a/b/c/d
+	test_path_is_file a/b/c/d
 '
 
 test_expect_success SYMLINKS 'a/b-2 was resolved as symlink' '
@@ -151,7 +151,7 @@ test_expect_success 'merge should not have D/F conflicts (recursive)' '
 	git reset --hard &&
 	git checkout baseline^0 &&
 	git merge -s recursive test2 &&
-	test -f a/b/c/d
+	test_path_is_file a/b/c/d
 '
 
 test_expect_success SYMLINKS 'a/b-2 was resolved as symlink' '
@@ -162,7 +162,7 @@ test_expect_success 'merge should not have F/D conflicts (recursive)' '
 	git reset --hard &&
 	git checkout -b foo test2 &&
 	git merge -s recursive baseline^0 &&
-	test -f a/b/c/d
+	test_path_is_file a/b/c/d
 '
 
 test_expect_success SYMLINKS 'a/b-2 was resolved as symlink' '
diff --git a/third_party/git/t/t6036-recursive-corner-cases.sh b/third_party/git/t/t6416-recursive-corner-cases.sh
index d23b948f27..fd98989b14 100755
--- a/third_party/git/t/t6036-recursive-corner-cases.sh
+++ b/third_party/git/t/t6416-recursive-corner-cases.sh
@@ -60,9 +60,9 @@ test_expect_success 'merge simple rename+criss-cross with no modifications' '
 		test_must_fail git merge -s recursive R2^0 &&
 
 		git ls-files -s >out &&
-		test_line_count = 2 out &&
+		test_line_count = 5 out &&
 		git ls-files -u >out &&
-		test_line_count = 2 out &&
+		test_line_count = 3 out &&
 		git ls-files -o >out &&
 		test_line_count = 1 out &&
 
@@ -133,9 +133,9 @@ test_expect_success 'merge criss-cross + rename merges with basic modification'
 		test_must_fail git merge -s recursive R2^0 &&
 
 		git ls-files -s >out &&
-		test_line_count = 2 out &&
+		test_line_count = 5 out &&
 		git ls-files -u >out &&
-		test_line_count = 2 out &&
+		test_line_count = 3 out &&
 		git ls-files -o >out &&
 		test_line_count = 1 out &&
 
@@ -218,8 +218,18 @@ test_expect_success 'git detects differently handled merges conflict' '
 		git ls-files -o >out &&
 		test_line_count = 1 out &&
 
-		git rev-parse >expect       \
-			C:new_a  D:new_a  E:new_a &&
+		git cat-file -p C:new_a >ours &&
+		git cat-file -p C:a >theirs &&
+		>empty &&
+		test_must_fail git merge-file \
+			-L "Temporary merge branch 1" \
+			-L "" \
+			-L "Temporary merge branch 2" \
+			ours empty theirs &&
+		sed -e "s/^\([<=>]\)/\1\1\1/" ours >ours-tweaked &&
+		git hash-object ours-tweaked >expect &&
+		git rev-parse >>expect      \
+				  D:new_a  E:new_a &&
 		git rev-parse   >actual     \
 			:1:new_a :2:new_a :3:new_a &&
 		test_cmp expect actual &&
@@ -257,7 +267,8 @@ test_expect_success 'git detects differently handled merges conflict, swapped' '
 		ctime=$(git log --no-walk --date=raw --format=%cd C | awk "{print \$1}") &&
 		newctime=$(($btime+1)) &&
 		git fast-export --no-data --all | sed -e s/$ctime/$newctime/ | git fast-import --force --quiet &&
-		# End of differences; rest is copy-paste of last test
+		# End of most differences; rest is copy-paste of last test,
+		# other than swapping C:a and C:new_a due to order switch
 
 		git checkout D^0 &&
 		test_must_fail git merge -s recursive E^0 &&
@@ -269,8 +280,18 @@ test_expect_success 'git detects differently handled merges conflict, swapped' '
 		git ls-files -o >out &&
 		test_line_count = 1 out &&
 
-		git rev-parse >expect       \
-			C:new_a  D:new_a  E:new_a &&
+		git cat-file -p C:a >ours &&
+		git cat-file -p C:new_a >theirs &&
+		>empty &&
+		test_must_fail git merge-file \
+			-L "Temporary merge branch 1" \
+			-L "" \
+			-L "Temporary merge branch 2" \
+			ours empty theirs &&
+		sed -e "s/^\([<=>]\)/\1\1\1/" ours >ours-tweaked &&
+		git hash-object ours-tweaked >expect &&
+		git rev-parse >>expect      \
+				  D:new_a  E:new_a &&
 		git rev-parse   >actual     \
 			:1:new_a :2:new_a :3:new_a &&
 		test_cmp expect actual &&
@@ -431,7 +452,7 @@ test_expect_success 'git detects conflict merging criss-cross+modify/delete, rev
 #
 # So choice 5 at least provides some kind of conflict for the original case,
 # and can merge cleanly as expected with D1 and E3.  It also made things just
-# slightly funny for merging D1 and e$, where E4 is defined as:
+# slightly funny for merging D1 and E4, where E4 is defined as:
 #   Commit E4: Merge B & C, modifying 'a' and renaming to 'a2', and deleting 'a/'
 # in this case, we'll get a rename/rename(1to2) conflict because a~$UNIQUE
 # gets renamed to 'a' in D1 and to 'a2' in E4.  But that's better than having
@@ -1123,7 +1144,7 @@ test_expect_failure 'check symlink add/add' '
 		test_must_fail git merge -s recursive E^0 &&
 
 		git ls-files -s >out &&
-		test_line_count = 2 out &&
+		test_line_count = 3 out &&
 		git ls-files -u >out &&
 		test_line_count = 2 out &&
 		git ls-files -o >out &&
@@ -1532,7 +1553,7 @@ test_expect_success 'setup nested conflicts' '
 		mv -f b_R1 b &&
 		mv -f a_R1 a &&
 		git add b a &&
-		test_tick && git commit -m "verson R1 of files" &&
+		test_tick && git commit -m "version R1 of files" &&
 		git tag R1 &&
 
 		# Create first merge on left side
@@ -1562,6 +1583,7 @@ test_expect_success 'check nested conflicts' '
 		cd nested_conflicts &&
 
 		git clean -f &&
+		MASTER=$(git rev-parse --short master) &&
 		git checkout L2^0 &&
 
 		# Merge must fail; there is a conflict
@@ -1582,7 +1604,7 @@ test_expect_success 'check nested conflicts' '
 		git cat-file -p R1:a >theirs &&
 		test_must_fail git merge-file --diff3 \
 			-L "Temporary merge branch 1" \
-			-L "merged common ancestors"  \
+			-L "$MASTER"  \
 			-L "Temporary merge branch 2" \
 			ours  \
 			base  \
@@ -1594,7 +1616,7 @@ test_expect_success 'check nested conflicts' '
 		git cat-file -p R1:b >theirs &&
 		test_must_fail git merge-file --diff3 \
 			-L "Temporary merge branch 1" \
-			-L "merged common ancestors"  \
+			-L "$MASTER"  \
 			-L "Temporary merge branch 2" \
 			ours  \
 			base  \
@@ -1695,7 +1717,7 @@ test_expect_success 'setup virtual merge base with nested conflicts' '
 		git checkout R &&
 		echo right >>content &&
 		git add content &&
-		test_tick && git commit -m "verson R1 of content" &&
+		test_tick && git commit -m "version R1 of content" &&
 		git tag R1 &&
 
 		# Create L2
@@ -1732,6 +1754,7 @@ test_expect_success 'check virtual merge base with nested conflicts' '
 	(
 		cd virtual_merge_base_has_nested_conflicts &&
 
+		MASTER=$(git rev-parse --short master) &&
 		git checkout L3^0 &&
 
 		# Merge must fail; there is a conflict
@@ -1760,7 +1783,7 @@ test_expect_success 'check virtual merge base with nested conflicts' '
 		cp left merged-once &&
 		test_must_fail git merge-file --diff3 \
 			-L "Temporary merge branch 1" \
-			-L "merged common ancestors"  \
+			-L "$MASTER"  \
 			-L "Temporary merge branch 2" \
 			merged-once \
 			base        \
diff --git a/third_party/git/t/t6037-merge-ours-theirs.sh b/third_party/git/t/t6417-merge-ours-theirs.sh
index 0aebc6c028..0aebc6c028 100755
--- a/third_party/git/t/t6037-merge-ours-theirs.sh
+++ b/third_party/git/t/t6417-merge-ours-theirs.sh
diff --git a/third_party/git/t/t6038-merge-text-auto.sh b/third_party/git/t/t6418-merge-text-auto.sh
index 5e8d5fa50c..30983d18b1 100755
--- a/third_party/git/t/t6038-merge-text-auto.sh
+++ b/third_party/git/t/t6418-merge-text-auto.sh
@@ -158,7 +158,7 @@ test_expect_success 'Detect LF/CRLF conflict from addition of text=auto' '
 	compare_files expected file.fuzzy
 '
 
-test_expect_failure 'checkout -m after setting text=auto' '
+test_expect_success 'checkout -m after setting text=auto' '
 	cat <<-\EOF >expected &&
 	first line
 	same line
@@ -168,12 +168,12 @@ test_expect_failure 'checkout -m after setting text=auto' '
 	git rm -fr . &&
 	rm -f .gitattributes &&
 	git reset --hard initial &&
-	git checkout a -- . &&
+	git restore --source=a -- . &&
 	git checkout -m b &&
-	compare_files expected file
+	git diff --no-index --ignore-cr-at-eol expected file
 '
 
-test_expect_failure 'checkout -m addition of text=auto' '
+test_expect_success 'checkout -m addition of text=auto' '
 	cat <<-\EOF >expected &&
 	first line
 	same line
@@ -183,23 +183,9 @@ test_expect_failure 'checkout -m addition of text=auto' '
 	git rm -fr . &&
 	rm -f .gitattributes file &&
 	git reset --hard initial &&
-	git checkout b -- . &&
+	git restore --source=b -- . &&
 	git checkout -m a &&
-	compare_files expected file
-'
-
-test_expect_failure 'cherry-pick patch from after text=auto was added' '
-	append_cr <<-\EOF >expected &&
-	first line
-	same line
-	EOF
-
-	git config merge.renormalize true &&
-	git rm -fr . &&
-	git reset --hard b &&
-	test_must_fail git cherry-pick a >err 2>&1 &&
-	grep "[Nn]othing added" err &&
-	compare_files expected file
+	git diff --no-index --ignore-cr-at-eol expected file
 '
 
 test_expect_success 'Test delete/normalize conflict' '
@@ -211,7 +197,8 @@ test_expect_success 'Test delete/normalize conflict' '
 	git commit -m "remove file" &&
 	git checkout master &&
 	git reset --hard a^ &&
-	git merge side
+	git merge side &&
+	test_path_is_missing file
 '
 
 test_done
diff --git a/third_party/git/t/t6039-merge-ignorecase.sh b/third_party/git/t/t6419-merge-ignorecase.sh
index 531850d834..531850d834 100755
--- a/third_party/git/t/t6039-merge-ignorecase.sh
+++ b/third_party/git/t/t6419-merge-ignorecase.sh
diff --git a/third_party/git/t/t6042-merge-rename-corner-cases.sh b/third_party/git/t/t6422-merge-rename-corner-cases.sh
index c5b57f40c3..3375eaf4e7 100755
--- a/third_party/git/t/t6042-merge-rename-corner-cases.sh
+++ b/third_party/git/t/t6422-merge-rename-corner-cases.sh
@@ -5,7 +5,7 @@ test_description="recursive merge corner cases w/ renames but not criss-crosses"
 
 . ./test-lib.sh
 
-test_expect_success 'setup rename/delete + untracked file' '
+test_setup_rename_delete_untracked () {
 	test_create_repo rename-delete-untracked &&
 	(
 		cd rename-delete-untracked &&
@@ -29,9 +29,10 @@ test_expect_success 'setup rename/delete + untracked file' '
 		git commit -m track-people-instead-of-objects &&
 		echo "Myyy PRECIOUSSS" >ring
 	)
-'
+}
 
 test_expect_success "Does git preserve Gollum's precious artifact?" '
+	test_setup_rename_delete_untracked &&
 	(
 		cd rename-delete-untracked &&
 
@@ -49,7 +50,7 @@ test_expect_success "Does git preserve Gollum's precious artifact?" '
 #
 # We should be able to merge B & C cleanly
 
-test_expect_success 'setup rename/modify/add-source conflict' '
+test_setup_rename_modify_add_source () {
 	test_create_repo rename-modify-add-source &&
 	(
 		cd rename-modify-add-source &&
@@ -70,9 +71,10 @@ test_expect_success 'setup rename/modify/add-source conflict' '
 		git add a &&
 		git commit -m C
 	)
-'
+}
 
 test_expect_failure 'rename/modify/add-source conflict resolvable' '
+	test_setup_rename_modify_add_source &&
 	(
 		cd rename-modify-add-source &&
 
@@ -88,7 +90,7 @@ test_expect_failure 'rename/modify/add-source conflict resolvable' '
 	)
 '
 
-test_expect_success 'setup resolvable conflict missed if rename missed' '
+test_setup_break_detection_1 () {
 	test_create_repo break-detection-1 &&
 	(
 		cd break-detection-1 &&
@@ -110,9 +112,10 @@ test_expect_success 'setup resolvable conflict missed if rename missed' '
 		git add a &&
 		git commit -m C
 	)
-'
+}
 
 test_expect_failure 'conflict caused if rename not detected' '
+	test_setup_break_detection_1 &&
 	(
 		cd break-detection-1 &&
 
@@ -135,7 +138,7 @@ test_expect_failure 'conflict caused if rename not detected' '
 	)
 '
 
-test_expect_success 'setup conflict resolved wrong if rename missed' '
+test_setup_break_detection_2 () {
 	test_create_repo break-detection-2 &&
 	(
 		cd break-detection-2 &&
@@ -160,9 +163,10 @@ test_expect_success 'setup conflict resolved wrong if rename missed' '
 		git add a &&
 		git commit -m E
 	)
-'
+}
 
 test_expect_failure 'missed conflict if rename not detected' '
+	test_setup_break_detection_2 &&
 	(
 		cd break-detection-2 &&
 
@@ -182,7 +186,7 @@ test_expect_failure 'missed conflict if rename not detected' '
 #   Commit B: rename a->b
 #   Commit C: rename a->b, add unrelated a
 
-test_expect_success 'setup undetected rename/add-source causes data loss' '
+test_setup_break_detection_3 () {
 	test_create_repo break-detection-3 &&
 	(
 		cd break-detection-3 &&
@@ -202,9 +206,10 @@ test_expect_success 'setup undetected rename/add-source causes data loss' '
 		git add a &&
 		git commit -m C
 	)
-'
+}
 
 test_expect_failure 'detect rename/add-source and preserve all data' '
+	test_setup_break_detection_3 &&
 	(
 		cd break-detection-3 &&
 
@@ -231,6 +236,7 @@ test_expect_failure 'detect rename/add-source and preserve all data' '
 '
 
 test_expect_failure 'detect rename/add-source and preserve all data, merge other way' '
+	test_setup_break_detection_3 &&
 	(
 		cd break-detection-3 &&
 
@@ -256,10 +262,10 @@ test_expect_failure 'detect rename/add-source and preserve all data, merge other
 	)
 '
 
-test_expect_success 'setup content merge + rename/directory conflict' '
-	test_create_repo rename-directory-1 &&
+test_setup_rename_directory () {
+	test_create_repo rename-directory-$1 &&
 	(
-		cd rename-directory-1 &&
+		cd rename-directory-$1 &&
 
 		printf "1\n2\n3\n4\n5\n6\n" >file &&
 		git add file &&
@@ -290,11 +296,12 @@ test_expect_success 'setup content merge + rename/directory conflict' '
 		test_tick &&
 		git commit -m left
 	)
-'
+}
 
 test_expect_success 'rename/directory conflict + clean content merge' '
+	test_setup_rename_directory 1a &&
 	(
-		cd rename-directory-1 &&
+		cd rename-directory-1a &&
 
 		git checkout left-clean^0 &&
 
@@ -320,8 +327,9 @@ test_expect_success 'rename/directory conflict + clean content merge' '
 '
 
 test_expect_success 'rename/directory conflict + content merge conflict' '
+	test_setup_rename_directory 1b &&
 	(
-		cd rename-directory-1 &&
+		cd rename-directory-1b &&
 
 		git reset --hard &&
 		git clean -fdqx &&
@@ -358,7 +366,7 @@ test_expect_success 'rename/directory conflict + content merge conflict' '
 	)
 '
 
-test_expect_success 'setup content merge + rename/directory conflict w/ disappearing dir' '
+test_setup_rename_directory_2 () {
 	test_create_repo rename-directory-2 &&
 	(
 		cd rename-directory-2 &&
@@ -385,9 +393,10 @@ test_expect_success 'setup content merge + rename/directory conflict w/ disappea
 		test_tick &&
 		git commit -m left
 	)
-'
+}
 
 test_expect_success 'disappearing dir in rename/directory conflict handled' '
+	test_setup_rename_directory_2 &&
 	(
 		cd rename-directory-2 &&
 
@@ -416,10 +425,10 @@ test_expect_success 'disappearing dir in rename/directory conflict handled' '
 #   Commit A: rename a->b, modifying b too
 #   Commit B: modify a, add different b
 
-test_expect_success 'setup rename-with-content-merge vs. add' '
-	test_create_repo rename-with-content-merge-and-add &&
+test_setup_rename_with_content_merge_and_add () {
+	test_create_repo rename-with-content-merge-and-add-$1 &&
 	(
-		cd rename-with-content-merge-and-add &&
+		cd rename-with-content-merge-and-add-$1 &&
 
 		test_seq 1 5 >a &&
 		git add a &&
@@ -438,16 +447,17 @@ test_expect_success 'setup rename-with-content-merge vs. add' '
 		git add a b &&
 		git commit -m B
 	)
-'
+}
 
 test_expect_success 'handle rename-with-content-merge vs. add' '
+	test_setup_rename_with_content_merge_and_add AB &&
 	(
-		cd rename-with-content-merge-and-add &&
+		cd rename-with-content-merge-and-add-AB &&
 
 		git checkout A^0 &&
 
 		test_must_fail git merge -s recursive B^0 >out &&
-		test_i18ngrep "CONFLICT (rename/add)" out &&
+		test_i18ngrep "CONFLICT (.*/add)" out &&
 
 		git ls-files -s >out &&
 		test_line_count = 2 out &&
@@ -483,8 +493,9 @@ test_expect_success 'handle rename-with-content-merge vs. add' '
 '
 
 test_expect_success 'handle rename-with-content-merge vs. add, merge other way' '
+	test_setup_rename_with_content_merge_and_add BA &&
 	(
-		cd rename-with-content-merge-and-add &&
+		cd rename-with-content-merge-and-add-BA &&
 
 		git reset --hard &&
 		git clean -fdx &&
@@ -492,7 +503,7 @@ test_expect_success 'handle rename-with-content-merge vs. add, merge other way'
 		git checkout B^0 &&
 
 		test_must_fail git merge -s recursive A^0 >out &&
-		test_i18ngrep "CONFLICT (rename/add)" out &&
+		test_i18ngrep "CONFLICT (.*/add)" out &&
 
 		git ls-files -s >out &&
 		test_line_count = 2 out &&
@@ -539,7 +550,7 @@ test_expect_success 'handle rename-with-content-merge vs. add, merge other way'
 #   * The working copy should have two files, both of form c~<unique>; does it?
 #   * Nothing else should be present.  Is anything?
 
-test_expect_success 'setup rename/rename (2to1) + modify/modify' '
+test_setup_rename_rename_2to1 () {
 	test_create_repo rename-rename-2to1 &&
 	(
 		cd rename-rename-2to1 &&
@@ -562,16 +573,17 @@ test_expect_success 'setup rename/rename (2to1) + modify/modify' '
 		git add a &&
 		git commit -m C
 	)
-'
+}
 
 test_expect_success 'handle rename/rename (2to1) conflict correctly' '
+	test_setup_rename_rename_2to1 &&
 	(
 		cd rename-rename-2to1 &&
 
 		git checkout B^0 &&
 
 		test_must_fail git merge -s recursive C^0 >out &&
-		test_i18ngrep "CONFLICT (rename/rename)" out &&
+		test_i18ngrep "CONFLICT (\(.*\)/\1)" out &&
 
 		git ls-files -s >out &&
 		test_line_count = 2 out &&
@@ -610,7 +622,7 @@ test_expect_success 'handle rename/rename (2to1) conflict correctly' '
 #   Commit A: new file: a
 #   Commit B: rename a->b
 #   Commit C: rename a->c
-test_expect_success 'setup simple rename/rename (1to2) conflict' '
+test_setup_rename_rename_1to2 () {
 	test_create_repo rename-rename-1to2 &&
 	(
 		cd rename-rename-1to2 &&
@@ -631,9 +643,10 @@ test_expect_success 'setup simple rename/rename (1to2) conflict' '
 		test_tick &&
 		git commit -m C
 	)
-'
+}
 
 test_expect_success 'merge has correct working tree contents' '
+	test_setup_rename_rename_1to2 &&
 	(
 		cd rename-rename-1to2 &&
 
@@ -667,7 +680,7 @@ test_expect_success 'merge has correct working tree contents' '
 #
 # Merging of B & C should NOT be clean; there's a rename/rename conflict
 
-test_expect_success 'setup rename/rename(1to2)/add-source conflict' '
+test_setup_rename_rename_1to2_add_source_1 () {
 	test_create_repo rename-rename-1to2-add-source-1 &&
 	(
 		cd rename-rename-1to2-add-source-1 &&
@@ -687,9 +700,10 @@ test_expect_success 'setup rename/rename(1to2)/add-source conflict' '
 		git add a &&
 		git commit -m C
 	)
-'
+}
 
 test_expect_failure 'detect conflict with rename/rename(1to2)/add-source merge' '
+	test_setup_rename_rename_1to2_add_source_1 &&
 	(
 		cd rename-rename-1to2-add-source-1 &&
 
@@ -714,7 +728,7 @@ test_expect_failure 'detect conflict with rename/rename(1to2)/add-source merge'
 	)
 '
 
-test_expect_success 'setup rename/rename(1to2)/add-source resolvable conflict' '
+test_setup_rename_rename_1to2_add_source_2 () {
 	test_create_repo rename-rename-1to2-add-source-2 &&
 	(
 		cd rename-rename-1to2-add-source-2 &&
@@ -737,9 +751,10 @@ test_expect_success 'setup rename/rename(1to2)/add-source resolvable conflict' '
 		test_tick &&
 		git commit -m two
 	)
-'
+}
 
 test_expect_failure 'rename/rename/add-source still tracks new a file' '
+	test_setup_rename_rename_1to2_add_source_2 &&
 	(
 		cd rename-rename-1to2-add-source-2 &&
 
@@ -759,7 +774,7 @@ test_expect_failure 'rename/rename/add-source still tracks new a file' '
 	)
 '
 
-test_expect_success 'setup rename/rename(1to2)/add-dest conflict' '
+test_setup_rename_rename_1to2_add_dest () {
 	test_create_repo rename-rename-1to2-add-dest &&
 	(
 		cd rename-rename-1to2-add-dest &&
@@ -784,9 +799,10 @@ test_expect_success 'setup rename/rename(1to2)/add-dest conflict' '
 		test_tick &&
 		git commit -m two
 	)
-'
+}
 
 test_expect_success 'rename/rename/add-dest merge still knows about conflicting file versions' '
+	test_setup_rename_rename_1to2_add_dest &&
 	(
 		cd rename-rename-1to2-add-dest &&
 
@@ -838,7 +854,7 @@ test_expect_success 'rename/rename/add-dest merge still knows about conflicting
 #   Commit B: rename foo->bar
 #   Expected: CONFLICT (rename/add/delete), two-way merged bar
 
-test_expect_success 'rad-setup: rename/add/delete conflict' '
+test_setup_rad () {
 	test_create_repo rad &&
 	(
 		cd rad &&
@@ -860,21 +876,27 @@ test_expect_success 'rad-setup: rename/add/delete conflict' '
 		git mv foo bar &&
 		git commit -m "rename foo to bar"
 	)
-'
+}
 
 test_expect_failure 'rad-check: rename/add/delete conflict' '
+	test_setup_rad &&
 	(
 		cd rad &&
 
 		git checkout B^0 &&
 		test_must_fail git merge -s recursive A^0 >out 2>err &&
 
-		# Not sure whether the output should contain just one
-		# "CONFLICT (rename/add/delete)" line, or if it should break
-		# it into a pair of "CONFLICT (rename/delete)" and
-		# "CONFLICT (rename/add)"; allow for either.
-		test_i18ngrep "CONFLICT (rename.*add)" out &&
-		test_i18ngrep "CONFLICT (rename.*delete)" out &&
+		# Instead of requiring the output to contain one combined line
+		#   CONFLICT (rename/add/delete)
+		# or perhaps two lines:
+		#   CONFLICT (rename/add): new file collides with rename target
+		#   CONFLICT (rename/delete): rename source removed on other side
+		# and instead of requiring "rename/add" instead of "add/add",
+		# be flexible in the type of console output message(s) reported
+		# for this particular case; we will be more stringent about the
+		# contents of the index and working directory.
+		test_i18ngrep "CONFLICT (.*/add)" out &&
+		test_i18ngrep "CONFLICT (rename.*/delete)" out &&
 		test_must_be_empty err &&
 
 		git ls-files -s >file_count &&
@@ -882,14 +904,14 @@ test_expect_failure 'rad-check: rename/add/delete conflict' '
 		git ls-files -u >file_count &&
 		test_line_count = 2 file_count &&
 		git ls-files -o >file_count &&
-		test_line_count = 2 file_count &&
+		test_line_count = 3 file_count &&
 
 		git rev-parse >actual \
 			:2:bar :3:bar &&
 		git rev-parse >expect \
 			B:bar  A:bar  &&
 
-		test_cmp file_is_missing foo &&
+		test_path_is_missing foo &&
 		# bar should have two-way merged contents of the different
 		# versions of bar; check that content from both sides is
 		# present.
@@ -904,7 +926,7 @@ test_expect_failure 'rad-check: rename/add/delete conflict' '
 #   Commit B: rename bar->baz, rm foo
 #   Expected: CONFLICT (rename/rename/delete/delete), two-way merged baz
 
-test_expect_success 'rrdd-setup: rename/rename(2to1)/delete/delete conflict' '
+test_setup_rrdd () {
 	test_create_repo rrdd &&
 	(
 		cd rrdd &&
@@ -927,20 +949,27 @@ test_expect_success 'rrdd-setup: rename/rename(2to1)/delete/delete conflict' '
 		git rm foo &&
 		git commit -m "Rename bar, remove foo"
 	)
-'
+}
 
 test_expect_failure 'rrdd-check: rename/rename(2to1)/delete/delete conflict' '
+	test_setup_rrdd &&
 	(
 		cd rrdd &&
 
 		git checkout A^0 &&
 		test_must_fail git merge -s recursive B^0 >out 2>err &&
 
-		# Not sure whether the output should contain just one
-		# "CONFLICT (rename/rename/delete/delete)" line, or if it
-		# should break it into three: "CONFLICT (rename/rename)" and
-		# two "CONFLICT (rename/delete)" lines; allow for either.
-		test_i18ngrep "CONFLICT (rename/rename)" out &&
+		# Instead of requiring the output to contain one combined line
+		#   CONFLICT (rename/rename/delete/delete)
+		# or perhaps two lines:
+		#   CONFLICT (rename/rename): ...
+		#   CONFLICT (rename/delete): info about pair 1
+		#   CONFLICT (rename/delete): info about pair 2
+		# and instead of requiring "rename/rename" instead of "add/add",
+		# be flexible in the type of console output message(s) reported
+		# for this particular case; we will be more stringent about the
+		# contents of the index and working directory.
+		test_i18ngrep "CONFLICT (\(.*\)/\1)" out &&
 		test_i18ngrep "CONFLICT (rename.*delete)" out &&
 		test_must_be_empty err &&
 
@@ -949,15 +978,15 @@ test_expect_failure 'rrdd-check: rename/rename(2to1)/delete/delete conflict' '
 		git ls-files -u >file_count &&
 		test_line_count = 2 file_count &&
 		git ls-files -o >file_count &&
-		test_line_count = 2 file_count &&
+		test_line_count = 3 file_count &&
 
 		git rev-parse >actual \
 			:2:baz :3:baz &&
 		git rev-parse >expect \
 			O:foo  O:bar  &&
 
-		test_cmp file_is_missing foo &&
-		test_cmp file_is_missing bar &&
+		test_path_is_missing foo &&
+		test_path_is_missing bar &&
 		# baz should have two-way merged contents of the original
 		# contents of foo and bar; check that content from both sides
 		# is present.
@@ -973,7 +1002,7 @@ test_expect_failure 'rrdd-check: rename/rename(2to1)/delete/delete conflict' '
 #   Expected: six CONFLICT(rename/rename) messages, each path in two of the
 #             multi-way merged contents found in two, four, six
 
-test_expect_success 'mod6-setup: chains of rename/rename(1to2) and rename/rename(2to1)' '
+test_setup_mod6 () {
 	test_create_repo mod6 &&
 	(
 		cd mod6 &&
@@ -1009,9 +1038,10 @@ test_expect_success 'mod6-setup: chains of rename/rename(1to2) and rename/rename
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
 test_expect_failure 'mod6-check: chains of rename/rename(1to2) and rename/rename(2to1)' '
+	test_setup_mod6 &&
 	(
 		cd mod6 &&
 
@@ -1023,25 +1053,25 @@ test_expect_failure 'mod6-check: chains of rename/rename(1to2) and rename/rename
 		test_must_be_empty err &&
 
 		git ls-files -s >file_count &&
-		test_line_count = 6 file_count &&
+		test_line_count = 9 file_count &&
 		git ls-files -u >file_count &&
-		test_line_count = 6 file_count &&
+		test_line_count = 9 file_count &&
 		git ls-files -o >file_count &&
 		test_line_count = 3 file_count &&
 
 		test_seq 10 20 >merged-one &&
 		test_seq 51 60 >merged-five &&
 		# Determine what the merge of three would give us.
-		test_seq 30 40 >three-side-A &&
+		test_seq 31 39 >three-base &&
+		test_seq 31 40 >three-side-A &&
 		test_seq 31 39 >three-side-B &&
-		echo forty >three-side-B &&
-		>empty &&
+		echo forty >>three-side-B &&
 		test_must_fail git merge-file \
-			-L "HEAD" \
+			-L "HEAD:four" \
 			-L "" \
-			-L "B^0" \
-			three-side-A empty three-side-B &&
-		sed -e "s/^\([<=>]\)/\1\1\1/" three-side-A >merged-three &&
+			-L "B^0:two" \
+			three-side-A three-base three-side-B &&
+		sed -e "s/^\([<=>]\)/\1\1/" three-side-A >merged-three &&
 
 		# Verify the index is as expected
 		git rev-parse >actual         \
@@ -1056,6 +1086,7 @@ test_expect_failure 'mod6-check: chains of rename/rename(1to2) and rename/rename
 
 		git cat-file -p :2:two >expect &&
 		git cat-file -p :3:two >other &&
+		>empty &&
 		test_must_fail git merge-file    \
 			-L "HEAD"  -L ""  -L "B^0" \
 			expect     empty  other &&
@@ -1108,7 +1139,8 @@ test_conflicts_with_adds_and_renames() {
 	#      files.  Is it present?
 	#   4) There should not be any three~* files in the working
 	#      tree
-	test_expect_success "setup simple $sideL/$sideR conflict" '
+	test_setup_collision_conflict () {
+	#test_expect_success "setup simple $sideL/$sideR conflict" '
 		test_create_repo simple_${sideL}_${sideR} &&
 		(
 			cd simple_${sideL}_${sideR} &&
@@ -1185,9 +1217,11 @@ test_conflicts_with_adds_and_renames() {
 			fi &&
 			test_tick && git commit -m R
 		)
-	'
+	#'
+	}
 
 	test_expect_success "check simple $sideL/$sideR conflict" '
+		test_setup_collision_conflict &&
 		(
 			cd simple_${sideL}_${sideR} &&
 
@@ -1254,7 +1288,7 @@ test_conflicts_with_adds_and_renames add    add
 #
 #   So, we have four different conflicting files that all end up at path
 #   'three'.
-test_expect_success 'setup nested conflicts from rename/rename(2to1)' '
+test_setup_nested_conflicts_from_rename_rename () {
 	test_create_repo nested_conflicts_from_rename_rename &&
 	(
 		cd nested_conflicts_from_rename_rename &&
@@ -1305,9 +1339,10 @@ test_expect_success 'setup nested conflicts from rename/rename(2to1)' '
 		git add one three &&
 		test_tick && git commit -m german
 	)
-'
+}
 
 test_expect_success 'check nested conflicts from rename/rename(2to1)' '
+	test_setup_nested_conflicts_from_rename_rename &&
 	(
 		cd nested_conflicts_from_rename_rename &&
 
@@ -1356,4 +1391,59 @@ test_expect_success 'check nested conflicts from rename/rename(2to1)' '
 	)
 '
 
+# Testcase rename/rename(1to2) of a binary file
+#   Commit O: orig
+#   Commit A: orig-A
+#   Commit B: orig-B
+#   Expected: CONFLICT(rename/rename) message, three unstaged entries in the
+#             index, and contents of orig-[AB] at path orig-[AB]
+test_setup_rename_rename_1_to_2_binary () {
+	test_create_repo rename_rename_1_to_2_binary &&
+	(
+		cd rename_rename_1_to_2_binary &&
+
+		echo '* binary' >.gitattributes &&
+		git add .gitattributes &&
+
+		test_seq 1 10 >orig &&
+		git add orig &&
+		git commit -m orig &&
+
+		git branch A &&
+		git branch B &&
+
+		git checkout A &&
+		git mv orig orig-A &&
+		test_seq 1 11 >orig-A &&
+		git add orig-A &&
+		git commit -m orig-A &&
+
+		git checkout B &&
+		git mv orig orig-B &&
+		test_seq 0 10 >orig-B &&
+		git add orig-B &&
+		git commit -m orig-B
+
+	)
+}
+
+test_expect_success 'rename/rename(1to2) with a binary file' '
+	test_setup_rename_rename_1_to_2_binary &&
+	(
+		cd rename_rename_1_to_2_binary &&
+
+		git checkout A^0 &&
+
+		test_must_fail git merge -s recursive B^0 &&
+
+		# Make sure the index has the right number of entries
+		git ls-files -s >actual &&
+		test_line_count = 4 actual &&
+
+		git rev-parse A:orig-A B:orig-B >expect &&
+		git hash-object orig-A orig-B >actual &&
+		test_cmp expect actual
+	)
+'
+
 test_done
diff --git a/third_party/git/t/t6043-merge-rename-directories.sh b/third_party/git/t/t6423-merge-rename-directories.sh
index c966147d5d..f7ecbb886d 100755
--- a/third_party/git/t/t6043-merge-rename-directories.sh
+++ b/third_party/git/t/t6423-merge-rename-directories.sh
@@ -38,7 +38,7 @@ test_description="recursive merge with directory renames"
 #   Commit B: z/{b,c,d,e/f}
 #   Expected: y/{b,c,d,e/f}
 
-test_expect_success '1a-setup: Simple directory rename detection' '
+test_setup_1a () {
 	test_create_repo 1a &&
 	(
 		cd 1a &&
@@ -67,9 +67,10 @@ test_expect_success '1a-setup: Simple directory rename detection' '
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '1a-check: Simple directory rename detection' '
+test_expect_success '1a: Simple directory rename detection' '
+	test_setup_1a &&
 	(
 		cd 1a &&
 
@@ -103,7 +104,7 @@ test_expect_success '1a-check: Simple directory rename detection' '
 #   Commit B: y/{b,c,d}
 #   Expected: y/{b,c,d,e}
 
-test_expect_success '1b-setup: Merge a directory with another' '
+test_setup_1b () {
 	test_create_repo 1b &&
 	(
 		cd 1b &&
@@ -134,9 +135,10 @@ test_expect_success '1b-setup: Merge a directory with another' '
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '1b-check: Merge a directory with another' '
+test_expect_success '1b: Merge a directory with another' '
+	test_setup_1b &&
 	(
 		cd 1b &&
 
@@ -165,7 +167,7 @@ test_expect_success '1b-check: Merge a directory with another' '
 #   Commit B: z/{b,c,d}
 #   Expected: y/{b,c,d}  (because x/d -> z/d -> y/d)
 
-test_expect_success '1c-setup: Transitive renaming' '
+test_setup_1c () {
 	test_create_repo 1c &&
 	(
 		cd 1c &&
@@ -193,9 +195,10 @@ test_expect_success '1c-setup: Transitive renaming' '
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '1c-check: Transitive renaming' '
+test_expect_success '1c: Transitive renaming' '
+	test_setup_1c &&
 	(
 		cd 1c &&
 
@@ -227,7 +230,7 @@ test_expect_success '1c-check: Transitive renaming' '
 #   Note: y/m & z/n should definitely move into x.  By the same token, both
 #         y/wham_1 & z/wham_2 should too...giving us a conflict.
 
-test_expect_success '1d-setup: Directory renames cause a rename/rename(2to1) conflict' '
+test_setup_1d () {
 	test_create_repo 1d &&
 	(
 		cd 1d &&
@@ -262,16 +265,17 @@ test_expect_success '1d-setup: Directory renames cause a rename/rename(2to1) con
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '1d-check: Directory renames cause a rename/rename(2to1) conflict' '
+test_expect_success '1d: Directory renames cause a rename/rename(2to1) conflict' '
+	test_setup_1d &&
 	(
 		cd 1d &&
 
 		git checkout A^0 &&
 
 		test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 >out &&
-		test_i18ngrep "CONFLICT (rename/rename)" out &&
+		test_i18ngrep "CONFLICT (\(.*\)/\1)" out &&
 
 		git ls-files -s >out &&
 		test_line_count = 8 out &&
@@ -313,7 +317,7 @@ test_expect_success '1d-check: Directory renames cause a rename/rename(2to1) con
 #   Commit B: z/{oldb,oldc,d}
 #   Expected: y/{newb,newc,d}
 
-test_expect_success '1e-setup: Renamed directory, with all files being renamed too' '
+test_setup_1e () {
 	test_create_repo 1e &&
 	(
 		cd 1e &&
@@ -342,9 +346,10 @@ test_expect_success '1e-setup: Renamed directory, with all files being renamed t
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '1e-check: Renamed directory, with all files being renamed too' '
+test_expect_success '1e: Renamed directory, with all files being renamed too' '
+	test_setup_1e &&
 	(
 		cd 1e &&
 
@@ -371,7 +376,7 @@ test_expect_success '1e-check: Renamed directory, with all files being renamed t
 #   Commit B: y/{b,c}, x/{d,e,f}
 #   Expected: y/{b,c}, x/{d,e,f,g}
 
-test_expect_success '1f-setup: Split a directory into two other directories' '
+test_setup_1f () {
 	test_create_repo 1f &&
 	(
 		cd 1f &&
@@ -408,9 +413,10 @@ test_expect_success '1f-setup: Split a directory into two other directories' '
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '1f-check: Split a directory into two other directories' '
+test_expect_success '1f: Split a directory into two other directories' '
+	test_setup_1f &&
 	(
 		cd 1f &&
 
@@ -459,7 +465,7 @@ test_expect_success '1f-check: Split a directory into two other directories' '
 #   Commit A: y/b, w/c
 #   Commit B: z/{b,c,d}
 #   Expected: y/b, w/c, z/d, with warning about z/ -> (y/ vs. w/) conflict
-test_expect_success '2a-setup: Directory split into two on one side, with equal numbers of paths' '
+test_setup_2a () {
 	test_create_repo 2a &&
 	(
 		cd 2a &&
@@ -489,9 +495,10 @@ test_expect_success '2a-setup: Directory split into two on one side, with equal
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '2a-check: Directory split into two on one side, with equal numbers of paths' '
+test_expect_success '2a: Directory split into two on one side, with equal numbers of paths' '
+	test_setup_2a &&
 	(
 		cd 2a &&
 
@@ -520,7 +527,7 @@ test_expect_success '2a-check: Directory split into two on one side, with equal
 #   Commit A: y/b, w/c
 #   Commit B: z/{b,c}, x/d
 #   Expected: y/b, w/c, x/d; No warning about z/ -> (y/ vs. w/) conflict
-test_expect_success '2b-setup: Directory split into two on one side, with equal numbers of paths' '
+test_setup_2b () {
 	test_create_repo 2b &&
 	(
 		cd 2b &&
@@ -551,9 +558,10 @@ test_expect_success '2b-setup: Directory split into two on one side, with equal
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '2b-check: Directory split into two on one side, with equal numbers of paths' '
+test_expect_success '2b: Directory split into two on one side, with equal numbers of paths' '
+	test_setup_2b &&
 	(
 		cd 2b &&
 
@@ -601,7 +609,7 @@ test_expect_success '2b-check: Directory split into two on one side, with equal
 #   Commit A: z/{b,c,d} (no change)
 #   Commit B: y/{b,c}, x/d
 #   Expected: y/{b,c}, x/d
-test_expect_success '3a-setup: Avoid implicit rename if involved as source on other side' '
+test_setup_3a () {
 	test_create_repo 3a &&
 	(
 		cd 3a &&
@@ -632,9 +640,10 @@ test_expect_success '3a-setup: Avoid implicit rename if involved as source on ot
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '3a-check: Avoid implicit rename if involved as source on other side' '
+test_expect_success '3a: Avoid implicit rename if involved as source on other side' '
+	test_setup_3a &&
 	(
 		cd 3a &&
 
@@ -664,7 +673,7 @@ test_expect_success '3a-check: Avoid implicit rename if involved as source on ot
 #         get it involved in directory rename detection.  If it were, we might
 #         end up with CONFLICT:(z/d -> y/d vs. x/d vs. w/d), i.e. a
 #         rename/rename/rename(1to3) conflict, which is just weird.
-test_expect_success '3b-setup: Avoid implicit rename if involved as source on current side' '
+test_setup_3b () {
 	test_create_repo 3b &&
 	(
 		cd 3b &&
@@ -697,9 +706,10 @@ test_expect_success '3b-setup: Avoid implicit rename if involved as source on cu
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '3b-check: Avoid implicit rename if involved as source on current side' '
+test_expect_success '3b: Avoid implicit rename if involved as source on current side' '
+	test_setup_3b &&
 	(
 		cd 3b &&
 
@@ -744,7 +754,7 @@ test_expect_success '3b-check: Avoid implicit rename if involved as source on cu
 #
 # What if we were to attempt to do directory rename detection when someone
 # "mostly" moved a directory but still left some files around, or,
-# equivalently, fully renamed a directory in one commmit and then recreated
+# equivalently, fully renamed a directory in one commit and then recreated
 # that directory in a later commit adding some new files and then tried to
 # merge?
 #
@@ -786,7 +796,7 @@ test_expect_success '3b-check: Avoid implicit rename if involved as source on cu
 #   Expected: y/{b,c,d}, z/{e,f}
 #   NOTE: Even though most files from z moved to y, we don't want f to follow.
 
-test_expect_success '4a-setup: Directory split, with original directory still present' '
+test_setup_4a () {
 	test_create_repo 4a &&
 	(
 		cd 4a &&
@@ -818,9 +828,10 @@ test_expect_success '4a-setup: Directory split, with original directory still pr
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '4a-check: Directory split, with original directory still present' '
+test_expect_success '4a: Directory split, with original directory still present' '
+	test_setup_4a &&
 	(
 		cd 4a &&
 
@@ -874,7 +885,7 @@ test_expect_success '4a-check: Directory split, with original directory still pr
 #         of history, giving us no way to represent this conflict in the
 #         index.
 
-test_expect_success '5a-setup: Merge directories, other side adds files to original and target' '
+test_setup_5a () {
 	test_create_repo 5a &&
 	(
 		cd 5a &&
@@ -907,9 +918,10 @@ test_expect_success '5a-setup: Merge directories, other side adds files to origi
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '5a-check: Merge directories, other side adds files to original and target' '
+test_expect_success '5a: Merge directories, other side adds files to original and target' '
+	test_setup_5a &&
 	(
 		cd 5a &&
 
@@ -941,14 +953,14 @@ test_expect_success '5a-check: Merge directories, other side adds files to origi
 #   Commit B: z/{b,c,d_1,e}, y/d_3
 #   Expected: y/{b,c,e}, CONFLICT(add/add: y/d_2 vs. y/d_3)
 #   NOTE: If z/d_1 in commit B were to be involved in dir rename detection, as
-#         we normaly would since z/ is being renamed to y/, then this would be
+#         we normally would since z/ is being renamed to y/, then this would be
 #         a rename/delete (z/d_1 -> y/d_1 vs. deleted) AND an add/add/add
 #         conflict of y/d_1 vs. y/d_2 vs. y/d_3.  Add/add/add is not
 #         representable in the index, so the existence of y/d_3 needs to
 #         cause us to bail on directory rename detection for that path, falling
 #         back to git behavior without the directory rename detection.
 
-test_expect_success '5b-setup: Rename/delete in order to get add/add/add conflict' '
+test_setup_5b () {
 	test_create_repo 5b &&
 	(
 		cd 5b &&
@@ -981,9 +993,10 @@ test_expect_success '5b-setup: Rename/delete in order to get add/add/add conflic
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '5b-check: Rename/delete in order to get add/add/add conflict' '
+test_expect_success '5b: Rename/delete in order to get add/add/add conflict' '
+	test_setup_5b &&
 	(
 		cd 5b &&
 
@@ -1024,7 +1037,7 @@ test_expect_success '5b-check: Rename/delete in order to get add/add/add conflic
 #             y/d are y/d_2 and y/d_4.  We still do the move from z/e to y/e,
 #             though, because it doesn't have anything in the way.
 
-test_expect_success '5c-setup: Transitive rename would cause rename/rename/rename/add/add/add' '
+test_setup_5c () {
 	test_create_repo 5c &&
 	(
 		cd 5c &&
@@ -1061,9 +1074,10 @@ test_expect_success '5c-setup: Transitive rename would cause rename/rename/renam
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '5c-check: Transitive rename would cause rename/rename/rename/add/add/add' '
+test_expect_success '5c: Transitive rename would cause rename/rename/rename/add/add/add' '
+	test_setup_5c &&
 	(
 		cd 5c &&
 
@@ -1113,7 +1127,7 @@ test_expect_success '5c-check: Transitive rename would cause rename/rename/renam
 #         detection for z/d_2, but that doesn't prevent us from applying the
 #         directory rename detection for z/f -> y/f.
 
-test_expect_success '5d-setup: Directory/file/file conflict due to directory rename' '
+test_setup_5d () {
 	test_create_repo 5d &&
 	(
 		cd 5d &&
@@ -1145,9 +1159,10 @@ test_expect_success '5d-setup: Directory/file/file conflict due to directory ren
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '5d-check: Directory/file/file conflict due to directory rename' '
+test_expect_success '5d: Directory/file/file conflict due to directory rename' '
+	test_setup_5d &&
 	(
 		cd 5d &&
 
@@ -1205,7 +1220,7 @@ test_expect_success '5d-check: Directory/file/file conflict due to directory ren
 #         them under y/ doesn't accidentally catch z/d and make it look like
 #         it is also involved in a rename/delete conflict.
 
-test_expect_success '6a-setup: Tricky rename/delete' '
+test_setup_6a () {
 	test_create_repo 6a &&
 	(
 		cd 6a &&
@@ -1235,9 +1250,10 @@ test_expect_success '6a-setup: Tricky rename/delete' '
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '6a-check: Tricky rename/delete' '
+test_expect_success '6a: Tricky rename/delete' '
+	test_setup_6a &&
 	(
 		cd 6a &&
 
@@ -1271,7 +1287,7 @@ test_expect_success '6a-check: Tricky rename/delete' '
 #         but B did that rename and still decided to put the file into z/,
 #         so we probably shouldn't apply directory rename detection for it.
 
-test_expect_success '6b-setup: Same rename done on both sides' '
+test_setup_6b () {
 	test_create_repo 6b &&
 	(
 		cd 6b &&
@@ -1300,9 +1316,10 @@ test_expect_success '6b-setup: Same rename done on both sides' '
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '6b-check: Same rename done on both sides' '
+test_expect_success '6b: Same rename done on both sides' '
+	test_setup_6b &&
 	(
 		cd 6b &&
 
@@ -1334,7 +1351,7 @@ test_expect_success '6b-check: Same rename done on both sides' '
 #   NOTE: Seems obvious, but just checking that the implementation doesn't
 #         "accidentally detect a rename" and give us y/{b,c,d}.
 
-test_expect_success '6c-setup: Rename only done on same side' '
+test_setup_6c () {
 	test_create_repo 6c &&
 	(
 		cd 6c &&
@@ -1362,9 +1379,10 @@ test_expect_success '6c-setup: Rename only done on same side' '
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '6c-check: Rename only done on same side' '
+test_expect_success '6c: Rename only done on same side' '
+	test_setup_6c &&
 	(
 		cd 6c &&
 
@@ -1396,7 +1414,7 @@ test_expect_success '6c-check: Rename only done on same side' '
 #   NOTE: Again, this seems obvious but just checking that the implementation
 #         doesn't "accidentally detect a rename" and give us y/{b,c,d}.
 
-test_expect_success '6d-setup: We do not always want transitive renaming' '
+test_setup_6d () {
 	test_create_repo 6d &&
 	(
 		cd 6d &&
@@ -1424,9 +1442,10 @@ test_expect_success '6d-setup: We do not always want transitive renaming' '
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '6d-check: We do not always want transitive renaming' '
+test_expect_success '6d: We do not always want transitive renaming' '
+	test_setup_6d &&
 	(
 		cd 6d &&
 
@@ -1458,7 +1477,7 @@ test_expect_success '6d-check: We do not always want transitive renaming' '
 #         doesn't "accidentally detect a rename" and give us y/{b,c} +
 #         add/add conflict on y/d_1 vs y/d_2.
 
-test_expect_success '6e-setup: Add/add from one side' '
+test_setup_6e () {
 	test_create_repo 6e &&
 	(
 		cd 6e &&
@@ -1487,9 +1506,10 @@ test_expect_success '6e-setup: Add/add from one side' '
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '6e-check: Add/add from one side' '
+test_expect_success '6e: Add/add from one side' '
+	test_setup_6e &&
 	(
 		cd 6e &&
 
@@ -1552,7 +1572,7 @@ test_expect_success '6e-check: Add/add from one side' '
 #   Expected: y/d, CONFLICT(rename/rename for both z/b and z/c)
 #   NOTE: There's a rename of z/ here, y/ has more renames, so z/d -> y/d.
 
-test_expect_success '7a-setup: rename-dir vs. rename-dir (NOT split evenly) PLUS add-other-file' '
+test_setup_7a () {
 	test_create_repo 7a &&
 	(
 		cd 7a &&
@@ -1583,9 +1603,10 @@ test_expect_success '7a-setup: rename-dir vs. rename-dir (NOT split evenly) PLUS
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '7a-check: rename-dir vs. rename-dir (NOT split evenly) PLUS add-other-file' '
+test_expect_success '7a: rename-dir vs. rename-dir (NOT split evenly) PLUS add-other-file' '
+	test_setup_7a &&
 	(
 		cd 7a &&
 
@@ -1623,7 +1644,7 @@ test_expect_success '7a-check: rename-dir vs. rename-dir (NOT split evenly) PLUS
 #   Commit B: z/{b,c,d_1},        w/d_2
 #   Expected: y/{b,c}, CONFLICT(rename/rename(2to1): x/d_1, w/d_2 -> y_d)
 
-test_expect_success '7b-setup: rename/rename(2to1), but only due to transitive rename' '
+test_setup_7b () {
 	test_create_repo 7b &&
 	(
 		cd 7b &&
@@ -1655,16 +1676,17 @@ test_expect_success '7b-setup: rename/rename(2to1), but only due to transitive r
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '7b-check: rename/rename(2to1), but only due to transitive rename' '
+test_expect_success '7b: rename/rename(2to1), but only due to transitive rename' '
+	test_setup_7b &&
 	(
 		cd 7b &&
 
 		git checkout A^0 &&
 
 		test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 >out &&
-		test_i18ngrep "CONFLICT (rename/rename)" out &&
+		test_i18ngrep "CONFLICT (\(.*\)/\1)" out &&
 
 		git ls-files -s >out &&
 		test_line_count = 4 out &&
@@ -1702,7 +1724,7 @@ test_expect_success '7b-check: rename/rename(2to1), but only due to transitive r
 #         neither CONFLICT(x/d -> w/d vs. z/d)
 #         nor CONFLiCT x/d -> w/d vs. y/d vs. z/d)
 
-test_expect_success '7c-setup: rename/rename(1to...2or3); transitive rename may add complexity' '
+test_setup_7c () {
 	test_create_repo 7c &&
 	(
 		cd 7c &&
@@ -1732,9 +1754,10 @@ test_expect_success '7c-setup: rename/rename(1to...2or3); transitive rename may
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '7c-check: rename/rename(1to...2or3); transitive rename may add complexity' '
+test_expect_success '7c: rename/rename(1to...2or3); transitive rename may add complexity' '
+	test_setup_7c &&
 	(
 		cd 7c &&
 
@@ -1766,7 +1789,7 @@ test_expect_success '7c-check: rename/rename(1to...2or3); transitive rename may
 #   Expected: y/{b,c}, CONFLICT(delete x/d vs rename to y/d)
 #   NOTE: z->y so NOT CONFLICT(delete x/d vs rename to z/d)
 
-test_expect_success '7d-setup: transitive rename involved in rename/delete; how is it reported?' '
+test_setup_7d () {
 	test_create_repo 7d &&
 	(
 		cd 7d &&
@@ -1796,9 +1819,10 @@ test_expect_success '7d-setup: transitive rename involved in rename/delete; how
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '7d-check: transitive rename involved in rename/delete; how is it reported?' '
+test_expect_success '7d: transitive rename involved in rename/delete; how is it reported?' '
+	test_setup_7d &&
 	(
 		cd 7d &&
 
@@ -1851,7 +1875,7 @@ test_expect_success '7d-check: transitive rename involved in rename/delete; how
 #         see testcases 9c and 9d for further discussion of this issue and
 #         how it's resolved.
 
-test_expect_success '7e-setup: transitive rename in rename/delete AND dirs in the way' '
+test_setup_7e () {
 	test_create_repo 7e &&
 	(
 		cd 7e &&
@@ -1886,9 +1910,10 @@ test_expect_success '7e-setup: transitive rename in rename/delete AND dirs in th
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '7e-check: transitive rename in rename/delete AND dirs in the way' '
+test_expect_success '7e: transitive rename in rename/delete AND dirs in the way' '
+	test_setup_7e &&
 	(
 		cd 7e &&
 
@@ -1945,7 +1970,7 @@ test_expect_success '7e-check: transitive rename in rename/delete AND dirs in th
 # simple rule from section 5 prevents me from handling this as optimally as
 # we potentially could.
 
-test_expect_success '8a-setup: Dual-directory rename, one into the others way' '
+test_setup_8a () {
 	test_create_repo 8a &&
 	(
 		cd 8a &&
@@ -1977,9 +2002,10 @@ test_expect_success '8a-setup: Dual-directory rename, one into the others way' '
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '8a-check: Dual-directory rename, one into the others way' '
+test_expect_success '8a: Dual-directory rename, one into the others way' '
+	test_setup_8a &&
 	(
 		cd 8a &&
 
@@ -2023,7 +2049,7 @@ test_expect_success '8a-check: Dual-directory rename, one into the others way' '
 # making us fall back to pre-directory-rename-detection behavior for both
 # e_1 and e_2.
 
-test_expect_success '8b-setup: Dual-directory rename, one into the others way, with conflicting filenames' '
+test_setup_8b () {
 	test_create_repo 8b &&
 	(
 		cd 8b &&
@@ -2055,9 +2081,10 @@ test_expect_success '8b-setup: Dual-directory rename, one into the others way, w
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '8b-check: Dual-directory rename, one into the others way, with conflicting filenames' '
+test_expect_success '8b: Dual-directory rename, one into the others way, with conflicting filenames' '
+	test_setup_8b &&
 	(
 		cd 8b &&
 
@@ -2089,14 +2116,14 @@ test_expect_success '8b-check: Dual-directory rename, one into the others way, w
 #
 #   Note: It could easily be argued that the correct resolution here is
 #         y/{b,c,e}, CONFLICT(rename/delete: z/d -> y/d vs deleted)
-#         and that the modifed version of d should be present in y/ after
+#         and that the modified version of d should be present in y/ after
 #         the merge, just marked as conflicted.  Indeed, I previously did
 #         argue that.  But applying directory renames to the side of
 #         history where a file is merely modified results in spurious
 #         rename/rename(1to2) conflicts -- see testcase 9h.  See also
 #         notes in 8d.
 
-test_expect_success '8c-setup: modify/delete or rename+modify/delete?' '
+test_setup_8c () {
 	test_create_repo 8c &&
 	(
 		cd 8c &&
@@ -2127,9 +2154,10 @@ test_expect_success '8c-setup: modify/delete or rename+modify/delete?' '
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '8c-check: modify/delete or rename+modify/delete' '
+test_expect_success '8c: modify/delete or rename+modify/delete' '
+	test_setup_8c &&
 	(
 		cd 8c &&
 
@@ -2175,7 +2203,7 @@ test_expect_success '8c-check: modify/delete or rename+modify/delete' '
 #   during merging are supposed to be about opposite sides doing things
 #   differently.
 
-test_expect_success '8d-setup: rename/delete...or not?' '
+test_setup_8d () {
 	test_create_repo 8d &&
 	(
 		cd 8d &&
@@ -2204,9 +2232,10 @@ test_expect_success '8d-setup: rename/delete...or not?' '
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '8d-check: rename/delete...or not?' '
+test_expect_success '8d: rename/delete...or not?' '
+	test_setup_8d &&
 	(
 		cd 8d &&
 
@@ -2231,26 +2260,25 @@ test_expect_success '8d-check: rename/delete...or not?' '
 #   Commit B: w/{b,c}, z/d
 #
 # Possible Resolutions:
-#   w/o dir-rename detection: z/d, CONFLICT(z/b -> y/b vs. w/b),
-#                                  CONFLICT(z/c -> y/c vs. w/c)
-#   Currently expected:       y/d, CONFLICT(z/b -> y/b vs. w/b),
-#                                  CONFLICT(z/c -> y/c vs. w/c)
-#   Optimal:                  ??
+#   if z not considered renamed: z/d, CONFLICT(z/b -> y/b vs. w/b),
+#                                     CONFLICT(z/c -> y/c vs. w/c)
+#   if z->y rename considered:   y/d, CONFLICT(z/b -> y/b vs. w/b),
+#                                     CONFLICT(z/c -> y/c vs. w/c)
+#   Optimal:                     ??
 #
 # Notes: In commit A, directory z got renamed to y.  In commit B, directory z
 #        did NOT get renamed; the directory is still present; instead it is
 #        considered to have just renamed a subset of paths in directory z
-#        elsewhere.  Therefore, the directory rename done in commit A to z/
-#        applies to z/d and maps it to y/d.
+#        elsewhere.  However, this is much like testcase 6b (where commit B
+#        moves all the original paths out of z/ but opted to keep d
+#        within z/).  This makes it hard to judge where d should end up.
 #
 #        It's possible that users would get confused about this, but what
-#        should we do instead?  Silently leaving at z/d seems just as bad or
-#        maybe even worse.  Perhaps we could print a big warning about z/d
-#        and how we're moving to y/d in this case, but when I started thinking
-#        about the ramifications of doing that, I didn't know how to rule out
-#        that opening other weird edge and corner cases so I just punted.
+#        should we do instead?  It's not at all clear to me whether z/d or
+#        y/d or something else is a better resolution here, and other cases
+#        start getting really tricky, so I just picked one.
 
-test_expect_success '8e-setup: Both sides rename, one side adds to original directory' '
+test_setup_8e () {
 	test_create_repo 8e &&
 	(
 		cd 8e &&
@@ -2279,9 +2307,10 @@ test_expect_success '8e-setup: Both sides rename, one side adds to original dire
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '8e-check: Both sides rename, one side adds to original directory' '
+test_expect_success '8e: Both sides rename, one side adds to original directory' '
+	test_setup_8e &&
 	(
 		cd 8e &&
 
@@ -2333,7 +2362,7 @@ test_expect_success '8e-check: Both sides rename, one side adds to original dire
 #         of which one had the most paths going to it.  A naive implementation
 #         of that could take the new file in commit B at z/i to x/w/i or x/i.
 
-test_expect_success '9a-setup: Inner renamed directory within outer renamed directory' '
+test_setup_9a () {
 	test_create_repo 9a &&
 	(
 		cd 9a &&
@@ -2366,9 +2395,10 @@ test_expect_success '9a-setup: Inner renamed directory within outer renamed dire
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '9a-check: Inner renamed directory within outer renamed directory' '
+test_expect_success '9a: Inner renamed directory within outer renamed directory' '
+	test_setup_9a &&
 	(
 		cd 9a &&
 
@@ -2404,7 +2434,7 @@ test_expect_success '9a-check: Inner renamed directory within outer renamed dire
 #   Commit B: z/{b,c,d_3}
 #   Expected: y/{b,c,d_merged}
 
-test_expect_success '9b-setup: Transitive rename with content merge' '
+test_setup_9b () {
 	test_create_repo 9b &&
 	(
 		cd 9b &&
@@ -2436,9 +2466,10 @@ test_expect_success '9b-setup: Transitive rename with content merge' '
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '9b-check: Transitive rename with content merge' '
+test_expect_success '9b: Transitive rename with content merge' '
+	test_setup_9b &&
 	(
 		cd 9b &&
 
@@ -2491,7 +2522,7 @@ test_expect_success '9b-check: Transitive rename with content merge' '
 #         away, then ignore that particular rename from the other side of
 #         history for any implicit directory renames.
 
-test_expect_success '9c-setup: Doubly transitive rename?' '
+test_setup_9c () {
 	test_create_repo 9c &&
 	(
 		cd 9c &&
@@ -2526,9 +2557,10 @@ test_expect_success '9c-setup: Doubly transitive rename?' '
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '9c-check: Doubly transitive rename?' '
+test_expect_success '9c: Doubly transitive rename?' '
+	test_setup_9c &&
 	(
 		cd 9c &&
 
@@ -2579,7 +2611,7 @@ test_expect_success '9c-check: Doubly transitive rename?' '
 #   simple rules that are consistent with what we need for all the other
 #   testcases and simplifies things for the user.
 
-test_expect_success '9d-setup: N-way transitive rename?' '
+test_setup_9d () {
 	test_create_repo 9d &&
 	(
 		cd 9d &&
@@ -2614,9 +2646,10 @@ test_expect_success '9d-setup: N-way transitive rename?' '
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '9d-check: N-way transitive rename?' '
+test_expect_success '9d: N-way transitive rename?' '
+	test_setup_9d &&
 	(
 		cd 9d &&
 
@@ -2653,7 +2686,7 @@ test_expect_success '9d-check: N-way transitive rename?' '
 #   Expected: combined/{a,b,c,d,e,f,g,h,i,j,k,l}, CONFLICT(Nto1) warnings,
 #             dir1/yo, dir2/yo, dir3/yo, dirN/yo
 
-test_expect_success '9e-setup: N-to-1 whammo' '
+test_setup_9e () {
 	test_create_repo 9e &&
 	(
 		cd 9e &&
@@ -2696,9 +2729,10 @@ test_expect_success '9e-setup: N-to-1 whammo' '
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success C_LOCALE_OUTPUT '9e-check: N-to-1 whammo' '
+test_expect_success C_LOCALE_OUTPUT '9e: N-to-1 whammo' '
+	test_setup_9e &&
 	(
 		cd 9e &&
 
@@ -2745,7 +2779,7 @@ test_expect_success C_LOCALE_OUTPUT '9e-check: N-to-1 whammo' '
 #   Commit B: goal/{a,b}/$more_files, goal/c
 #   Expected: priority/{a,b}/$more_files, priority/c
 
-test_expect_success '9f-setup: Renamed directory that only contained immediate subdirs' '
+test_setup_9f () {
 	test_create_repo 9f &&
 	(
 		cd 9f &&
@@ -2774,9 +2808,10 @@ test_expect_success '9f-setup: Renamed directory that only contained immediate s
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '9f-check: Renamed directory that only contained immediate subdirs' '
+test_expect_success '9f: Renamed directory that only contained immediate subdirs' '
+	test_setup_9f &&
 	(
 		cd 9f &&
 
@@ -2808,8 +2843,16 @@ test_expect_success '9f-check: Renamed directory that only contained immediate s
 #   Commit A: priority/{alpha,bravo}/$more_files
 #   Commit B: goal/{a,b}/$more_files, goal/c
 #   Expected: priority/{alpha,bravo}/$more_files, priority/c
-
-test_expect_success '9g-setup: Renamed directory that only contained immediate subdirs, immediate subdirs renamed' '
+# We currently fail this test because the directory renames we detect are
+#   goal/a/ -> priority/alpha/
+#   goal/b/ -> priority/bravo/
+# We do not detect
+#   goal/   -> priority/
+# because of no files found within goal/, and the fact that "a" != "alpha"
+# and "b" != "bravo".  But I'm not sure it's really a failure given that
+# viewpoint...
+
+test_setup_9g () {
 	test_create_repo 9g &&
 	(
 		cd 9g &&
@@ -2841,9 +2884,10 @@ test_expect_success '9g-setup: Renamed directory that only contained immediate s
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_failure '9g-check: Renamed directory that only contained immediate subdirs, immediate subdirs renamed' '
+test_expect_failure '9g: Renamed directory that only contained immediate subdirs, immediate subdirs renamed' '
+	test_setup_9g &&
 	(
 		cd 9g &&
 
@@ -2877,7 +2921,7 @@ test_expect_failure '9g-check: Renamed directory that only contained immediate s
 #   Expected: y/{b,c}, x/d_2
 #   NOTE: If we applied the z/ -> y/ rename to z/d, then we'd end up with
 #         a rename/rename(1to2) conflict (z/d -> y/d vs. x/d)
-test_expect_success '9h-setup: Avoid dir rename on merely modified path' '
+test_setup_9h () {
 	test_create_repo 9h &&
 	(
 		cd 9h &&
@@ -2910,9 +2954,10 @@ test_expect_success '9h-setup: Avoid dir rename on merely modified path' '
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '9h-check: Avoid dir rename on merely modified path' '
+test_expect_success '9h: Avoid dir rename on merely modified path' '
+	test_setup_9h &&
 	(
 		cd 9h &&
 
@@ -2957,7 +3002,7 @@ test_expect_success '9h-check: Avoid dir rename on merely modified path' '
 #   Expected: Aborted Merge +
 #       ERROR_MSG(untracked working tree files would be overwritten by merge)
 
-test_expect_success '10a-setup: Overwrite untracked with normal rename/delete' '
+test_setup_10a () {
 	test_create_repo 10a &&
 	(
 		cd 10a &&
@@ -2983,9 +3028,10 @@ test_expect_success '10a-setup: Overwrite untracked with normal rename/delete' '
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '10a-check: Overwrite untracked with normal rename/delete' '
+test_expect_success '10a: Overwrite untracked with normal rename/delete' '
+	test_setup_10a &&
 	(
 		cd 10a &&
 
@@ -3021,7 +3067,7 @@ test_expect_success '10a-check: Overwrite untracked with normal rename/delete' '
 #             z/c_1 -> z/d_1 rename recorded at stage 3 for y/d +
 #       ERROR_MSG(refusing to lose untracked file at 'y/d')
 
-test_expect_success '10b-setup: Overwrite untracked with dir rename + delete' '
+test_setup_10b () {
 	test_create_repo 10b &&
 	(
 		cd 10b &&
@@ -3050,9 +3096,10 @@ test_expect_success '10b-setup: Overwrite untracked with dir rename + delete' '
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '10b-check: Overwrite untracked with dir rename + delete' '
+test_expect_success '10b: Overwrite untracked with dir rename + delete' '
+	test_setup_10b &&
 	(
 		cd 10b &&
 
@@ -3098,10 +3145,10 @@ test_expect_success '10b-check: Overwrite untracked with dir rename + delete' '
 #             y/c~B^0 +
 #             ERROR_MSG(Refusing to lose untracked file at y/c)
 
-test_expect_success '10c-setup: Overwrite untracked with dir rename/rename(1to2)' '
-	test_create_repo 10c &&
+test_setup_10c () {
+	test_create_repo 10c_$1 &&
 	(
-		cd 10c &&
+		cd 10c_$1 &&
 
 		mkdir z x &&
 		echo a >z/a &&
@@ -3128,11 +3175,12 @@ test_expect_success '10c-setup: Overwrite untracked with dir rename/rename(1to2)
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '10c-check: Overwrite untracked with dir rename/rename(1to2)' '
+test_expect_success '10c1: Overwrite untracked with dir rename/rename(1to2)' '
+	test_setup_10c 1 &&
 	(
-		cd 10c &&
+		cd 10c_1 &&
 
 		git checkout A^0 &&
 		echo important >y/c &&
@@ -3163,9 +3211,10 @@ test_expect_success '10c-check: Overwrite untracked with dir rename/rename(1to2)
 	)
 '
 
-test_expect_success '10c-check: Overwrite untracked with dir rename/rename(1to2), other direction' '
+test_expect_success '10c2: Overwrite untracked with dir rename/rename(1to2), other direction' '
+	test_setup_10c 2 &&
 	(
-		cd 10c &&
+		cd 10c_2 &&
 
 		git reset --hard &&
 		git clean -fdqx &&
@@ -3208,7 +3257,7 @@ test_expect_success '10c-check: Overwrite untracked with dir rename/rename(1to2)
 #             CONFLICT(rename/rename) z/c_1 vs x/f_2 -> y/wham
 #             ERROR_MSG(Refusing to lose untracked file at y/wham)
 
-test_expect_success '10d-setup: Delete untracked with dir rename/rename(2to1)' '
+test_setup_10d () {
 	test_create_repo 10d &&
 	(
 		cd 10d &&
@@ -3240,9 +3289,10 @@ test_expect_success '10d-setup: Delete untracked with dir rename/rename(2to1)' '
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '10d-check: Delete untracked with dir rename/rename(2to1)' '
+test_expect_success '10d: Delete untracked with dir rename/rename(2to1)' '
+	test_setup_10d &&
 	(
 		cd 10d &&
 
@@ -3290,7 +3340,7 @@ test_expect_success '10d-check: Delete untracked with dir rename/rename(2to1)' '
 #   Commit B: z/{a,b,c}
 #   Expected: y/{a,b,c} + untracked z/c
 
-test_expect_success '10e-setup: Does git complain about untracked file that is not really in the way?' '
+test_setup_10e () {
 	test_create_repo 10e &&
 	(
 		cd 10e &&
@@ -3317,9 +3367,10 @@ test_expect_success '10e-setup: Does git complain about untracked file that is n
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_failure '10e-check: Does git complain about untracked file that is not really in the way?' '
+test_expect_failure '10e: Does git complain about untracked file that is not really in the way?' '
+	test_setup_10e &&
 	(
 		cd 10e &&
 
@@ -3371,7 +3422,7 @@ test_expect_failure '10e-check: Does git complain about untracked file that is n
 #             z/c~HEAD with contents of B:z/b_v2,
 #             z/c with uncommitted mods on top of A:z/c_v1
 
-test_expect_success '11a-setup: Avoid losing dirty contents with simple rename' '
+test_setup_11a () {
 	test_create_repo 11a &&
 	(
 		cd 11a &&
@@ -3398,9 +3449,10 @@ test_expect_success '11a-setup: Avoid losing dirty contents with simple rename'
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '11a-check: Avoid losing dirty contents with simple rename' '
+test_expect_success '11a: Avoid losing dirty contents with simple rename' '
+	test_setup_11a &&
 	(
 		cd 11a &&
 
@@ -3441,7 +3493,7 @@ test_expect_success '11a-check: Avoid losing dirty contents with simple rename'
 #             ERROR_MSG(Refusing to lose dirty file at z/c)
 
 
-test_expect_success '11b-setup: Avoid losing dirty file involved in directory rename' '
+test_setup_11b () {
 	test_create_repo 11b &&
 	(
 		cd 11b &&
@@ -3470,9 +3522,10 @@ test_expect_success '11b-setup: Avoid losing dirty file involved in directory re
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '11b-check: Avoid losing dirty file involved in directory rename' '
+test_expect_success '11b: Avoid losing dirty file involved in directory rename' '
+	test_setup_11b &&
 	(
 		cd 11b &&
 
@@ -3515,7 +3568,7 @@ test_expect_success '11b-check: Avoid losing dirty file involved in directory re
 #   Expected: Abort_msg("following files would be overwritten by merge") +
 #             y/c left untouched (still has uncommitted mods)
 
-test_expect_success '11c-setup: Avoid losing not-uptodate with rename + D/F conflict' '
+test_setup_11c () {
 	test_create_repo 11c &&
 	(
 		cd 11c &&
@@ -3545,9 +3598,10 @@ test_expect_success '11c-setup: Avoid losing not-uptodate with rename + D/F conf
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '11c-check: Avoid losing not-uptodate with rename + D/F conflict' '
+test_expect_success '11c: Avoid losing not-uptodate with rename + D/F conflict' '
+	test_setup_11c &&
 	(
 		cd 11c &&
 
@@ -3581,7 +3635,7 @@ test_expect_success '11c-check: Avoid losing not-uptodate with rename + D/F conf
 #             Warning_Msg("Refusing to lose dirty file at z/c) +
 #             y/{a,c~HEAD,c/d}, x/b, now-untracked z/c_v1 with uncommitted mods
 
-test_expect_success '11d-setup: Avoid losing not-uptodate with rename + D/F conflict' '
+test_setup_11d () {
 	test_create_repo 11d &&
 	(
 		cd 11d &&
@@ -3612,9 +3666,10 @@ test_expect_success '11d-setup: Avoid losing not-uptodate with rename + D/F conf
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '11d-check: Avoid losing not-uptodate with rename + D/F conflict' '
+test_expect_success '11d: Avoid losing not-uptodate with rename + D/F conflict' '
+	test_setup_11d &&
 	(
 		cd 11d &&
 
@@ -3659,7 +3714,7 @@ test_expect_success '11d-check: Avoid losing not-uptodate with rename + D/F conf
 #             y/c~HEAD has A:y/c_2 contents
 #             y/c has dirty file from before merge
 
-test_expect_success '11e-setup: Avoid deleting not-uptodate with dir rename/rename(1to2)/add' '
+test_setup_11e () {
 	test_create_repo 11e &&
 	(
 		cd 11e &&
@@ -3691,9 +3746,10 @@ test_expect_success '11e-setup: Avoid deleting not-uptodate with dir rename/rena
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '11e-check: Avoid deleting not-uptodate with dir rename/rename(1to2)/add' '
+test_expect_success '11e: Avoid deleting not-uptodate with dir rename/rename(1to2)/add' '
+	test_setup_11e &&
 	(
 		cd 11e &&
 
@@ -3744,7 +3800,7 @@ test_expect_success '11e-check: Avoid deleting not-uptodate with dir rename/rena
 #             CONFLICT(rename/rename) x/c vs x/d -> y/wham
 #             ERROR_MSG(Refusing to lose dirty file at y/wham)
 
-test_expect_success '11f-setup: Avoid deleting not-uptodate with dir rename/rename(2to1)' '
+test_setup_11f () {
 	test_create_repo 11f &&
 	(
 		cd 11f &&
@@ -3773,9 +3829,10 @@ test_expect_success '11f-setup: Avoid deleting not-uptodate with dir rename/rena
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '11f-check: Avoid deleting not-uptodate with dir rename/rename(2to1)' '
+test_expect_success '11f: Avoid deleting not-uptodate with dir rename/rename(2to1)' '
+	test_setup_11f &&
 	(
 		cd 11f &&
 
@@ -3832,7 +3889,7 @@ test_expect_success '11f-check: Avoid deleting not-uptodate with dir rename/rena
 #   Commit B: node1/{leaf1,leaf2,leaf5}, node2/{leaf3,leaf4,leaf6}
 #   Expected: node1/{leaf1,leaf2,leaf5,node2/{leaf3,leaf4,leaf6}}
 
-test_expect_success '12a-setup: Moving one directory hierarchy into another' '
+test_setup_12a () {
 	test_create_repo 12a &&
 	(
 		cd 12a &&
@@ -3862,9 +3919,10 @@ test_expect_success '12a-setup: Moving one directory hierarchy into another' '
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '12a-check: Moving one directory hierarchy into another' '
+test_expect_success '12a: Moving one directory hierarchy into another' '
+	test_setup_12a &&
 	(
 		cd 12a &&
 
@@ -3910,7 +3968,7 @@ test_expect_success '12a-check: Moving one directory hierarchy into another' '
 #         To which, I can do no more than shrug my shoulders and say that
 #         even simple rules give weird results when given weird inputs.
 
-test_expect_success '12b-setup: Moving two directory hierarchies into each other' '
+test_setup_12b () {
 	test_create_repo 12b &&
 	(
 		cd 12b &&
@@ -3938,9 +3996,10 @@ test_expect_success '12b-setup: Moving two directory hierarchies into each other
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '12b-check: Moving two directory hierarchies into each other' '
+test_expect_success '12b: Moving two directory hierarchies into each other' '
+	test_setup_12b &&
 	(
 		cd 12b &&
 
@@ -3976,7 +4035,7 @@ test_expect_success '12b-check: Moving two directory hierarchies into each other
 #   NOTE: This is *exactly* like 12c, except that every path is modified on
 #         each side of the merge.
 
-test_expect_success '12c-setup: Moving one directory hierarchy into another w/ content merge' '
+test_setup_12c () {
 	test_create_repo 12c &&
 	(
 		cd 12c &&
@@ -4008,9 +4067,10 @@ test_expect_success '12c-setup: Moving one directory hierarchy into another w/ c
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '12c-check: Moving one directory hierarchy into another w/ content merge' '
+test_expect_success '12c: Moving one directory hierarchy into another w/ content merge' '
+	test_setup_12c &&
 	(
 		cd 12c &&
 
@@ -4051,6 +4111,122 @@ test_expect_success '12c-check: Moving one directory hierarchy into another w/ c
 	)
 '
 
+# Testcase 12d, Rename/merge of subdirectory into the root
+#   Commit O: a/b/subdir/foo
+#   Commit A: subdir/foo
+#   Commit B: a/b/subdir/foo, a/b/bar
+#   Expected: subdir/foo, bar
+
+test_setup_12d () {
+	test_create_repo 12d &&
+	(
+		cd 12d &&
+
+		mkdir -p a/b/subdir &&
+		test_commit a/b/subdir/foo &&
+
+		git branch O &&
+		git branch A &&
+		git branch B &&
+
+		git checkout A &&
+		mkdir subdir &&
+		git mv a/b/subdir/foo.t subdir/foo.t &&
+		test_tick &&
+		git commit -m "A" &&
+
+		git checkout B &&
+		test_commit a/b/bar
+	)
+}
+
+test_expect_success '12d: Rename/merge subdir into the root, variant 1' '
+	test_setup_12d &&
+	(
+		cd 12d &&
+
+		git checkout A^0 &&
+
+		git -c merge.directoryRenames=true merge -s recursive B^0 &&
+
+		git ls-files -s >out &&
+		test_line_count = 2 out &&
+
+		git rev-parse >actual \
+			HEAD:subdir/foo.t   HEAD:bar.t &&
+		git rev-parse >expect \
+			O:a/b/subdir/foo.t  B:a/b/bar.t &&
+		test_cmp expect actual &&
+
+		git hash-object bar.t >actual &&
+		git rev-parse B:a/b/bar.t >expect &&
+		test_cmp expect actual &&
+
+		test_must_fail git rev-parse HEAD:a/b/subdir/foo.t &&
+		test_must_fail git rev-parse HEAD:a/b/bar.t &&
+		test_path_is_missing a/ &&
+		test_path_is_file bar.t
+	)
+'
+
+# Testcase 12e, Rename/merge of subdirectory into the root
+#   Commit O: a/b/foo
+#   Commit A: foo
+#   Commit B: a/b/foo, a/b/bar
+#   Expected: foo, bar
+
+test_setup_12e () {
+	test_create_repo 12e &&
+	(
+		cd 12e &&
+
+		mkdir -p a/b &&
+		test_commit a/b/foo &&
+
+		git branch O &&
+		git branch A &&
+		git branch B &&
+
+		git checkout A &&
+		mkdir subdir &&
+		git mv a/b/foo.t foo.t &&
+		test_tick &&
+		git commit -m "A" &&
+
+		git checkout B &&
+		test_commit a/b/bar
+	)
+}
+
+test_expect_success '12e: Rename/merge subdir into the root, variant 2' '
+	test_setup_12e &&
+	(
+		cd 12e &&
+
+		git checkout A^0 &&
+
+		git -c merge.directoryRenames=true merge -s recursive B^0 &&
+
+		git ls-files -s >out &&
+		test_line_count = 2 out &&
+
+		git rev-parse >actual \
+			HEAD:foo.t   HEAD:bar.t &&
+		git rev-parse >expect \
+			O:a/b/foo.t  B:a/b/bar.t &&
+		test_cmp expect actual &&
+
+		git hash-object bar.t >actual &&
+		git rev-parse B:a/b/bar.t >expect &&
+		test_cmp expect actual &&
+
+		test_must_fail git rev-parse HEAD:a/b/foo.t &&
+		test_must_fail git rev-parse HEAD:a/b/bar.t &&
+		test_path_is_missing a/ &&
+		test_path_is_file bar.t
+	)
+'
+
 ###########################################################################
 # SECTION 13: Checking informational and conflict messages
 #
@@ -4068,10 +4244,10 @@ test_expect_success '12c-check: Moving one directory hierarchy into another w/ c
 #   Commit B: z/{b,c,d,e/f}
 #   Expected: y/{b,c,d,e/f}, with notices/conflicts for both y/d and y/e/f
 
-test_expect_success '13a-setup: messages for newly added files' '
-	test_create_repo 13a &&
+test_setup_13a () {
+	test_create_repo 13a_$1 &&
 	(
-		cd 13a &&
+		cd 13a_$1 &&
 
 		mkdir z &&
 		echo b >z/b &&
@@ -4097,11 +4273,12 @@ test_expect_success '13a-setup: messages for newly added files' '
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '13a-check(conflict): messages for newly added files' '
+test_expect_success '13a(conflict): messages for newly added files' '
+	test_setup_13a conflict &&
 	(
-		cd 13a &&
+		cd 13a_conflict &&
 
 		git checkout A^0 &&
 
@@ -4121,9 +4298,10 @@ test_expect_success '13a-check(conflict): messages for newly added files' '
 	)
 '
 
-test_expect_success '13a-check(info): messages for newly added files' '
+test_expect_success '13a(info): messages for newly added files' '
+	test_setup_13a info &&
 	(
-		cd 13a &&
+		cd 13a_info &&
 
 		git reset --hard &&
 		git checkout A^0 &&
@@ -4153,10 +4331,10 @@ test_expect_success '13a-check(info): messages for newly added files' '
 #   Expected: y/{b,c,d_merged}, with two conflict messages for y/d,
 #             one about content, and one about file location
 
-test_expect_success '13b-setup: messages for transitive rename with conflicted content' '
-	test_create_repo 13b &&
+test_setup_13b () {
+	test_create_repo 13b_$1 &&
 	(
-		cd 13b &&
+		cd 13b_$1 &&
 
 		mkdir x &&
 		mkdir z &&
@@ -4185,11 +4363,12 @@ test_expect_success '13b-setup: messages for transitive rename with conflicted c
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '13b-check(conflict): messages for transitive rename with conflicted content' '
+test_expect_success '13b(conflict): messages for transitive rename with conflicted content' '
+	test_setup_13b conflict &&
 	(
-		cd 13b &&
+		cd 13b_conflict &&
 
 		git checkout A^0 &&
 
@@ -4207,9 +4386,10 @@ test_expect_success '13b-check(conflict): messages for transitive rename with co
 	)
 '
 
-test_expect_success '13b-check(info): messages for transitive rename with conflicted content' '
+test_expect_success '13b(info): messages for transitive rename with conflicted content' '
+	test_setup_13b info &&
 	(
-		cd 13b &&
+		cd 13b_info &&
 
 		git reset --hard &&
 		git checkout A^0 &&
@@ -4232,16 +4412,16 @@ test_expect_success '13b-check(info): messages for transitive rename with confli
 #   Commit O: z/{b,c},   x/{d,e}
 #   Commit A: y/{b,c,d}, x/e
 #   Commit B: z/{b,c,d}, x/e
-#   Expected: y/{b,c,d}, with info or conflict messages for d (
+#   Expected: y/{b,c,d}, x/e, with info or conflict messages for d
 #             A: renamed x/d -> z/d; B: renamed z/ -> y/ AND renamed x/d to y/d
 #             One could argue A had partial knowledge of what was done with
 #             d and B had full knowledge, but that's a slippery slope as
 #             shown in testcase 13d.
 
-test_expect_success '13c-setup: messages for rename/rename(1to1) via transitive rename' '
-	test_create_repo 13c &&
+test_setup_13c () {
+	test_create_repo 13c_$1 &&
 	(
-		cd 13c &&
+		cd 13c_$1 &&
 
 		mkdir x &&
 		mkdir z &&
@@ -4269,11 +4449,12 @@ test_expect_success '13c-setup: messages for rename/rename(1to1) via transitive
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '13c-check(conflict): messages for rename/rename(1to1) via transitive rename' '
+test_expect_success '13c(conflict): messages for rename/rename(1to1) via transitive rename' '
+	test_setup_13c conflict &&
 	(
-		cd 13c &&
+		cd 13c_conflict &&
 
 		git checkout A^0 &&
 
@@ -4290,9 +4471,10 @@ test_expect_success '13c-check(conflict): messages for rename/rename(1to1) via t
 	)
 '
 
-test_expect_success '13c-check(info): messages for rename/rename(1to1) via transitive rename' '
+test_expect_success '13c(info): messages for rename/rename(1to1) via transitive rename' '
+	test_setup_13c info &&
 	(
-		cd 13c &&
+		cd 13c_info &&
 
 		git reset --hard &&
 		git checkout A^0 &&
@@ -4324,10 +4506,10 @@ test_expect_success '13c-check(info): messages for rename/rename(1to1) via trans
 #               * B renames a/y to c/y, and A renames c/->d/ => a/y -> d/y
 #               No conflict in where a/y ends up, so put it in d/y.
 
-test_expect_success '13d-setup: messages for rename/rename(1to1) via dual transitive rename' '
-	test_create_repo 13d &&
+test_setup_13d () {
+	test_create_repo 13d_$1 &&
 	(
-		cd 13d &&
+		cd 13d_$1 &&
 
 		mkdir a &&
 		mkdir b &&
@@ -4356,11 +4538,12 @@ test_expect_success '13d-setup: messages for rename/rename(1to1) via dual transi
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '13d-check(conflict): messages for rename/rename(1to1) via dual transitive rename' '
+test_expect_success '13d(conflict): messages for rename/rename(1to1) via dual transitive rename' '
+	test_setup_13d conflict &&
 	(
-		cd 13d &&
+		cd 13d_conflict &&
 
 		git checkout A^0 &&
 
@@ -4380,9 +4563,10 @@ test_expect_success '13d-check(conflict): messages for rename/rename(1to1) via d
 	)
 '
 
-test_expect_success '13d-check(info): messages for rename/rename(1to1) via dual transitive rename' '
+test_expect_success '13d(info): messages for rename/rename(1to1) via dual transitive rename' '
+	test_setup_13d info &&
 	(
-		cd 13d &&
+		cd 13d_info &&
 
 		git reset --hard &&
 		git checkout A^0 &&
@@ -4448,7 +4632,7 @@ test_expect_success '13d-check(info): messages for rename/rename(1to1) via dual
 #          in the outer merge for this special kind of setup, but it at
 #          least avoids hitting a BUG().
 #
-test_expect_success '13e-setup: directory rename detection in recursive case' '
+test_setup_13e () {
 	test_create_repo 13e &&
 	(
 		cd 13e &&
@@ -4493,9 +4677,10 @@ test_expect_success '13e-setup: directory rename detection in recursive case' '
 		test_tick &&
 		git commit -m "D"
 	)
-'
+}
 
-test_expect_success '13e-check: directory rename detection in recursive case' '
+test_expect_success '13e: directory rename detection in recursive case' '
+	test_setup_13e &&
 	(
 		cd 13e &&
 
diff --git a/third_party/git/t/t6044-merge-unrelated-index-changes.sh b/third_party/git/t/t6424-merge-unrelated-index-changes.sh
index 5e3779ebc9..5e3779ebc9 100755
--- a/third_party/git/t/t6044-merge-unrelated-index-changes.sh
+++ b/third_party/git/t/t6424-merge-unrelated-index-changes.sh
diff --git a/third_party/git/t/t6045-merge-rename-delete.sh b/third_party/git/t/t6425-merge-rename-delete.sh
index 5d33577d2f..f79d021590 100755
--- a/third_party/git/t/t6045-merge-rename-delete.sh
+++ b/third_party/git/t/t6425-merge-rename-delete.sh
@@ -17,7 +17,8 @@ test_expect_success 'rename/delete' '
 	git commit -m "delete" &&
 
 	test_must_fail git merge --strategy=recursive rename >output &&
-	test_i18ngrep "CONFLICT (rename/delete): A deleted in HEAD and renamed to B in rename. Version rename of B left in tree." output
+	test_i18ngrep "CONFLICT (rename/delete): A.* renamed .*to B.* in rename" output &&
+	test_i18ngrep "CONFLICT (rename/delete): A.*deleted in HEAD." output
 '
 
 test_done
diff --git a/third_party/git/t/t6046-merge-skip-unneeded-updates.sh b/third_party/git/t/t6426-merge-skip-unneeded-updates.sh
index 3a47623ed3..699813671c 100755
--- a/third_party/git/t/t6046-merge-skip-unneeded-updates.sh
+++ b/third_party/git/t/t6426-merge-skip-unneeded-updates.sh
@@ -36,10 +36,10 @@ test_description="merge cases"
 #   Commit B: b_3
 #   Expected: b_2
 
-test_expect_success '1a-setup: Modify(A)/Modify(B), change on B subset of A' '
-	test_create_repo 1a &&
+test_setup_1a () {
+	test_create_repo 1a_$1 &&
 	(
-		cd 1a &&
+		cd 1a_$1 &&
 
 		test_write_lines 1 2 3 4 5 6 7 8 9 10 >b &&
 		git add b &&
@@ -62,26 +62,24 @@ test_expect_success '1a-setup: Modify(A)/Modify(B), change on B subset of A' '
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '1a-check-L: Modify(A)/Modify(B), change on B subset of A' '
-	test_when_finished "git -C 1a reset --hard" &&
-	test_when_finished "git -C 1a clean -fd" &&
+test_expect_success '1a-L: Modify(A)/Modify(B), change on B subset of A' '
+	test_setup_1a L &&
 	(
-		cd 1a &&
+		cd 1a_L &&
 
 		git checkout A^0 &&
 
-		test-tool chmtime =31337 b &&
-		test-tool chmtime -v +0 b >expected-mtime &&
+		test-tool chmtime --get -3600 b >old-mtime &&
 
 		GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err &&
 
-		test_i18ngrep "Skipped b" out &&
 		test_must_be_empty err &&
 
-		test-tool chmtime -v +0 b >actual-mtime &&
-		test_cmp expected-mtime actual-mtime &&
+		# Make sure b was NOT updated
+		test-tool chmtime --get b >new-mtime &&
+		test_cmp old-mtime new-mtime &&
 
 		git ls-files -s >index_files &&
 		test_line_count = 1 index_files &&
@@ -96,17 +94,20 @@ test_expect_success '1a-check-L: Modify(A)/Modify(B), change on B subset of A' '
 	)
 '
 
-test_expect_success '1a-check-R: Modify(A)/Modify(B), change on B subset of A' '
-	test_when_finished "git -C 1a reset --hard" &&
-	test_when_finished "git -C 1a clean -fd" &&
+test_expect_success '1a-R: Modify(A)/Modify(B), change on B subset of A' '
+	test_setup_1a R &&
 	(
-		cd 1a &&
+		cd 1a_R &&
 
 		git checkout B^0 &&
 
+		test-tool chmtime --get -3600 b >old-mtime &&
 		GIT_MERGE_VERBOSITY=3 git merge -s recursive A^0 >out 2>err &&
 
-		test_i18ngrep "Auto-merging b" out &&
+		# Make sure b WAS updated
+		test-tool chmtime --get b >new-mtime &&
+		test $(cat old-mtime) -lt $(cat new-mtime) &&
+
 		test_must_be_empty err &&
 
 		git ls-files -s >index_files &&
@@ -133,10 +134,10 @@ test_expect_success '1a-check-R: Modify(A)/Modify(B), change on B subset of A' '
 #   Commit B: c_1
 #   Expected: c_2
 
-test_expect_success '2a-setup: Modify(A)/rename(B)' '
-	test_create_repo 2a &&
+test_setup_2a () {
+	test_create_repo 2a_$1 &&
 	(
-		cd 2a &&
+		cd 2a_$1 &&
 
 		test_seq 1 10 >b &&
 		git add b &&
@@ -158,20 +159,19 @@ test_expect_success '2a-setup: Modify(A)/rename(B)' '
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '2a-check-L: Modify/rename, merge into modify side' '
-	test_when_finished "git -C 2a reset --hard" &&
-	test_when_finished "git -C 2a clean -fd" &&
+test_expect_success '2a-L: Modify/rename, merge into modify side' '
+	test_setup_2a L &&
 	(
-		cd 2a &&
+		cd 2a_L &&
 
 		git checkout A^0 &&
 
+		test_path_is_missing c &&
 		GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err &&
 
-		test_i18ngrep ! "Skipped c" out &&
-		test_must_be_empty err &&
+		test_path_is_file c &&
 
 		git ls-files -s >index_files &&
 		test_line_count = 1 index_files &&
@@ -189,17 +189,20 @@ test_expect_success '2a-check-L: Modify/rename, merge into modify side' '
 	)
 '
 
-test_expect_success '2a-check-R: Modify/rename, merge into rename side' '
-	test_when_finished "git -C 2a reset --hard" &&
-	test_when_finished "git -C 2a clean -fd" &&
+test_expect_success '2a-R: Modify/rename, merge into rename side' '
+	test_setup_2a R &&
 	(
-		cd 2a &&
+		cd 2a_R &&
 
 		git checkout B^0 &&
 
+		test-tool chmtime --get -3600 c >old-mtime &&
 		GIT_MERGE_VERBOSITY=3 git merge -s recursive A^0 >out 2>err &&
 
-		test_i18ngrep ! "Skipped c" out &&
+		# Make sure c WAS updated
+		test-tool chmtime --get c >new-mtime &&
+		test $(cat old-mtime) -lt $(cat new-mtime) &&
+
 		test_must_be_empty err &&
 
 		git ls-files -s >index_files &&
@@ -224,10 +227,10 @@ test_expect_success '2a-check-R: Modify/rename, merge into rename side' '
 #   Commit B: b_3
 #   Expected: c_2
 
-test_expect_success '2b-setup: Rename+Mod(A)/Mod(B), B mods subset of A' '
-	test_create_repo 2b &&
+test_setup_2b () {
+	test_create_repo 2b_$1 &&
 	(
-		cd 2b &&
+		cd 2b_$1 &&
 
 		test_write_lines 1 2 3 4 5 6 7 8 9 10 >b &&
 		git add b &&
@@ -251,26 +254,23 @@ test_expect_success '2b-setup: Rename+Mod(A)/Mod(B), B mods subset of A' '
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '2b-check-L: Rename+Mod(A)/Mod(B), B mods subset of A' '
-	test_when_finished "git -C 2b reset --hard" &&
-	test_when_finished "git -C 2b clean -fd" &&
+test_expect_success '2b-L: Rename+Mod(A)/Mod(B), B mods subset of A' '
+	test_setup_2b L &&
 	(
-		cd 2b &&
+		cd 2b_L &&
 
 		git checkout A^0 &&
 
-		test-tool chmtime =31337 c &&
-		test-tool chmtime -v +0 c >expected-mtime &&
-
+		test-tool chmtime --get -3600 c >old-mtime &&
 		GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err &&
 
-		test_i18ngrep "Skipped c" out &&
 		test_must_be_empty err &&
 
-		test-tool chmtime -v +0 c >actual-mtime &&
-		test_cmp expected-mtime actual-mtime &&
+		# Make sure c WAS updated
+		test-tool chmtime --get c >new-mtime &&
+		test_cmp old-mtime new-mtime &&
 
 		git ls-files -s >index_files &&
 		test_line_count = 1 index_files &&
@@ -288,17 +288,19 @@ test_expect_success '2b-check-L: Rename+Mod(A)/Mod(B), B mods subset of A' '
 	)
 '
 
-test_expect_success '2b-check-R: Rename+Mod(A)/Mod(B), B mods subset of A' '
-	test_when_finished "git -C 2b reset --hard" &&
-	test_when_finished "git -C 2b clean -fd" &&
+test_expect_success '2b-R: Rename+Mod(A)/Mod(B), B mods subset of A' '
+	test_setup_2b R &&
 	(
-		cd 2b &&
+		cd 2b_R &&
 
 		git checkout B^0 &&
 
+		test_path_is_missing c &&
 		GIT_MERGE_VERBOSITY=3 git merge -s recursive A^0 >out 2>err &&
 
-		test_i18ngrep "Auto-merging c" out &&
+		# Make sure c now present (and thus was updated)
+		test_path_is_file c &&
+
 		test_must_be_empty err &&
 
 		git ls-files -s >index_files &&
@@ -332,7 +334,7 @@ test_expect_success '2b-check-R: Rename+Mod(A)/Mod(B), B mods subset of A' '
 #         skip the update, then we're in trouble.  This test verifies we do
 #         not make that particular mistake.
 
-test_expect_success '2c-setup: Modify b & add c VS rename b->c' '
+test_setup_2c () {
 	test_create_repo 2c &&
 	(
 		cd 2c &&
@@ -358,21 +360,26 @@ test_expect_success '2c-setup: Modify b & add c VS rename b->c' '
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '2c-check: Modify b & add c VS rename b->c' '
+test_expect_success '2c: Modify b & add c VS rename b->c' '
+	test_setup_2c &&
 	(
 		cd 2c &&
 
 		git checkout A^0 &&
 
+		test-tool chmtime --get -3600 c >old-mtime &&
 		GIT_MERGE_VERBOSITY=3 &&
 		export GIT_MERGE_VERBOSITY &&
 		test_must_fail git merge -s recursive B^0 >out 2>err &&
 
-		test_i18ngrep "CONFLICT (rename/add): Rename b->c" out &&
-		test_i18ngrep ! "Skipped c" out &&
-		test_must_be_empty err
+		test_i18ngrep "CONFLICT (.*/add):" out &&
+		test_must_be_empty err &&
+
+		# Make sure c WAS updated
+		test-tool chmtime --get c >new-mtime &&
+		test $(cat old-mtime) -lt $(cat new-mtime)
 
 		# FIXME: rename/add conflicts are horribly broken right now;
 		# when I get back to my patch series fixing it and
@@ -428,10 +435,10 @@ test_expect_success '2c-check: Modify b & add c VS rename b->c' '
 #   Commit B: bq_1, bar/whatever
 #   Expected: bar/{bq_2, whatever}
 
-test_expect_success '3a-setup: bq_1->foo/bq_2 on A, foo/->bar/ on B' '
-	test_create_repo 3a &&
+test_setup_3a () {
+	test_create_repo 3a_$1 &&
 	(
-		cd 3a &&
+		cd 3a_$1 &&
 
 		mkdir foo &&
 		test_seq 1 10 >bq &&
@@ -456,21 +463,22 @@ test_expect_success '3a-setup: bq_1->foo/bq_2 on A, foo/->bar/ on B' '
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '3a-check-L: bq_1->foo/bq_2 on A, foo/->bar/ on B' '
-	test_when_finished "git -C 3a reset --hard" &&
-	test_when_finished "git -C 3a clean -fd" &&
+test_expect_success '3a-L: bq_1->foo/bq_2 on A, foo/->bar/ on B' '
+	test_setup_3a L &&
 	(
-		cd 3a &&
+		cd 3a_L &&
 
 		git checkout A^0 &&
 
+		test_path_is_missing bar/bq &&
 		GIT_MERGE_VERBOSITY=3 git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err &&
 
-		test_i18ngrep ! "Skipped bar/bq" out &&
 		test_must_be_empty err &&
 
+		test_path_is_file bar/bq &&
+
 		git ls-files -s >index_files &&
 		test_line_count = 2 index_files &&
 
@@ -487,19 +495,20 @@ test_expect_success '3a-check-L: bq_1->foo/bq_2 on A, foo/->bar/ on B' '
 	)
 '
 
-test_expect_success '3a-check-R: bq_1->foo/bq_2 on A, foo/->bar/ on B' '
-	test_when_finished "git -C 3a reset --hard" &&
-	test_when_finished "git -C 3a clean -fd" &&
+test_expect_success '3a-R: bq_1->foo/bq_2 on A, foo/->bar/ on B' '
+	test_setup_3a R &&
 	(
-		cd 3a &&
+		cd 3a_R &&
 
 		git checkout B^0 &&
 
+		test_path_is_missing bar/bq &&
 		GIT_MERGE_VERBOSITY=3 git -c merge.directoryRenames=true merge -s recursive A^0 >out 2>err &&
 
-		test_i18ngrep ! "Skipped bar/bq" out &&
 		test_must_be_empty err &&
 
+		test_path_is_file bar/bq &&
+
 		git ls-files -s >index_files &&
 		test_line_count = 2 index_files &&
 
@@ -522,10 +531,10 @@ test_expect_success '3a-check-R: bq_1->foo/bq_2 on A, foo/->bar/ on B' '
 #   Commit B: bq_2, bar/whatever
 #   Expected: bar/{bq_2, whatever}
 
-test_expect_success '3b-setup: bq_1->foo/bq_2 on A, foo/->bar/ on B' '
-	test_create_repo 3b &&
+test_setup_3b () {
+	test_create_repo 3b_$1 &&
 	(
-		cd 3b &&
+		cd 3b_$1 &&
 
 		mkdir foo &&
 		test_seq 1 10 >bq &&
@@ -550,21 +559,22 @@ test_expect_success '3b-setup: bq_1->foo/bq_2 on A, foo/->bar/ on B' '
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '3b-check-L: bq_1->foo/bq_2 on A, foo/->bar/ on B' '
-	test_when_finished "git -C 3b reset --hard" &&
-	test_when_finished "git -C 3b clean -fd" &&
+test_expect_success '3b-L: bq_1->foo/bq_2 on A, foo/->bar/ on B' '
+	test_setup_3b L &&
 	(
-		cd 3b &&
+		cd 3b_L &&
 
 		git checkout A^0 &&
 
+		test_path_is_missing bar/bq &&
 		GIT_MERGE_VERBOSITY=3 git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err &&
 
-		test_i18ngrep ! "Skipped bar/bq" out &&
 		test_must_be_empty err &&
 
+		test_path_is_file bar/bq &&
+
 		git ls-files -s >index_files &&
 		test_line_count = 2 index_files &&
 
@@ -581,19 +591,20 @@ test_expect_success '3b-check-L: bq_1->foo/bq_2 on A, foo/->bar/ on B' '
 	)
 '
 
-test_expect_success '3b-check-R: bq_1->foo/bq_2 on A, foo/->bar/ on B' '
-	test_when_finished "git -C 3b reset --hard" &&
-	test_when_finished "git -C 3b clean -fd" &&
+test_expect_success '3b-R: bq_1->foo/bq_2 on A, foo/->bar/ on B' '
+	test_setup_3b R &&
 	(
-		cd 3b &&
+		cd 3b_R &&
 
 		git checkout B^0 &&
 
+		test_path_is_missing bar/bq &&
 		GIT_MERGE_VERBOSITY=3 git -c merge.directoryRenames=true merge -s recursive A^0 >out 2>err &&
 
-		test_i18ngrep ! "Skipped bar/bq" out &&
 		test_must_be_empty err &&
 
+		test_path_is_file bar/bq &&
+
 		git ls-files -s >index_files &&
 		test_line_count = 2 index_files &&
 
@@ -621,7 +632,7 @@ test_expect_success '3b-check-R: bq_1->foo/bq_2 on A, foo/->bar/ on B' '
 #   Working copy: b_4
 #   Expected: b_2 for merge, b_4 in working copy
 
-test_expect_success '4a-setup: Change on A, change on B subset of A, dirty mods present' '
+test_setup_4a () {
 	test_create_repo 4a &&
 	(
 		cd 4a &&
@@ -647,33 +658,31 @@ test_expect_success '4a-setup: Change on A, change on B subset of A, dirty mods
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
 # NOTE: For as long as we continue using unpack_trees() without index_only
-#   set to true, it will error out on a case like this claiming the the locally
+#   set to true, it will error out on a case like this claiming that the locally
 #   modified file would be overwritten by the merge.  Getting this testcase
 #   correct requires doing the merge in-memory first, then realizing that no
 #   updates to the file are necessary, and thus that we can just leave the path
 #   alone.
-test_expect_failure '4a-check: Change on A, change on B subset of A, dirty mods present' '
-	test_when_finished "git -C 4a reset --hard" &&
-	test_when_finished "git -C 4a clean -fd" &&
+test_expect_failure '4a: Change on A, change on B subset of A, dirty mods present' '
+	test_setup_4a &&
 	(
 		cd 4a &&
 
 		git checkout A^0 &&
 		echo "File rewritten" >b &&
 
-		test-tool chmtime =31337 b &&
-		test-tool chmtime -v +0 b >expected-mtime &&
+		test-tool chmtime --get -3600 b >old-mtime &&
 
 		GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err &&
 
-		test_i18ngrep "Skipped b" out &&
 		test_must_be_empty err &&
 
-		test-tool chmtime -v +0 b >actual-mtime &&
-		test_cmp expected-mtime actual-mtime &&
+		# Make sure b was NOT updated
+		test-tool chmtime --get b >new-mtime &&
+		test_cmp old-mtime new-mtime &&
 
 		git ls-files -s >index_files &&
 		test_line_count = 1 index_files &&
@@ -695,7 +704,7 @@ test_expect_failure '4a-check: Change on A, change on B subset of A, dirty mods
 #   Working copy: c_4
 #   Expected: c_2
 
-test_expect_success '4b-setup: Rename+Mod(A)/Mod(B), change on B subset of A, dirty mods present' '
+test_setup_4b () {
 	test_create_repo 4b &&
 	(
 		cd 4b &&
@@ -722,27 +731,25 @@ test_expect_success '4b-setup: Rename+Mod(A)/Mod(B), change on B subset of A, di
 		test_tick &&
 		git commit -m "B"
 	)
-'
+}
 
-test_expect_success '4b-check: Rename+Mod(A)/Mod(B), change on B subset of A, dirty mods present' '
-	test_when_finished "git -C 4b reset --hard" &&
-	test_when_finished "git -C 4b clean -fd" &&
+test_expect_success '4b: Rename+Mod(A)/Mod(B), change on B subset of A, dirty mods present' '
+	test_setup_4b &&
 	(
 		cd 4b &&
 
 		git checkout A^0 &&
 		echo "File rewritten" >c &&
 
-		test-tool chmtime =31337 c &&
-		test-tool chmtime -v +0 c >expected-mtime &&
+		test-tool chmtime --get -3600 c >old-mtime &&
 
 		GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err &&
 
-		test_i18ngrep "Skipped c" out &&
 		test_must_be_empty err &&
 
-		test-tool chmtime -v +0 c >actual-mtime &&
-		test_cmp expected-mtime actual-mtime &&
+		# Make sure c was NOT updated
+		test-tool chmtime --get c >new-mtime &&
+		test_cmp old-mtime new-mtime &&
 
 		git ls-files -s >index_files &&
 		test_line_count = 1 index_files &&
diff --git a/third_party/git/t/t6427-diff3-conflict-markers.sh b/third_party/git/t/t6427-diff3-conflict-markers.sh
new file mode 100755
index 0000000000..f4655bb358
--- /dev/null
+++ b/third_party/git/t/t6427-diff3-conflict-markers.sh
@@ -0,0 +1,211 @@
+#!/bin/sh
+
+test_description='recursive merge diff3 style conflict markers'
+
+. ./test-lib.sh
+
+# Setup:
+#          L1
+#            \
+#             ?
+#            /
+#          R1
+#
+# Where:
+#   L1 and R1 both have a file named 'content' but have no common history
+#
+
+test_expect_success 'setup no merge base' '
+	test_create_repo no_merge_base &&
+	(
+		cd no_merge_base &&
+
+		git checkout -b L &&
+		test_commit A content A &&
+
+		git checkout --orphan R &&
+		test_commit B content B
+	)
+'
+
+test_expect_success 'check no merge base' '
+	(
+		cd no_merge_base &&
+
+		git checkout L^0 &&
+
+		test_must_fail git -c merge.conflictstyle=diff3 merge --allow-unrelated-histories -s recursive R^0 &&
+
+		grep "|||||| empty tree" content
+	)
+'
+
+# Setup:
+#          L1
+#         /  \
+#   master    ?
+#         \  /
+#          R1
+#
+# Where:
+#   L1 and R1 have modified the same file ('content') in conflicting ways
+#
+
+test_expect_success 'setup unique merge base' '
+	test_create_repo unique_merge_base &&
+	(
+		cd unique_merge_base &&
+
+		test_commit base content "1
+2
+3
+4
+5
+" &&
+
+		git branch L &&
+		git branch R &&
+
+		git checkout L &&
+		test_commit L content "1
+2
+3
+4
+5
+7" &&
+
+		git checkout R &&
+		git rm content &&
+		test_commit R renamed "1
+2
+3
+4
+5
+six"
+	)
+'
+
+test_expect_success 'check unique merge base' '
+	(
+		cd unique_merge_base &&
+
+		git checkout L^0 &&
+		MASTER=$(git rev-parse --short master) &&
+
+		test_must_fail git -c merge.conflictstyle=diff3 merge -s recursive R^0 &&
+
+		grep "|||||| $MASTER:content" renamed
+	)
+'
+
+# Setup:
+#          L1---L2--L3
+#         /  \ /      \
+#   master    X1       ?
+#         \  / \      /
+#          R1---R2--R3
+#
+# Where:
+#   commits L1 and R1 have modified the same file in non-conflicting ways
+#   X1 is an auto-generated merge-base used when merging L1 and R1
+#   commits L2 and R2 are merges of R1 and L1 into L1 and R1, respectively
+#   commits L3 and R3 both modify 'content' in conflicting ways
+#
+
+test_expect_success 'setup multiple merge bases' '
+	test_create_repo multiple_merge_bases &&
+	(
+		cd multiple_merge_bases &&
+
+		test_commit initial content "1
+2
+3
+4
+5" &&
+
+		git branch L &&
+		git branch R &&
+
+		# Create L1
+		git checkout L &&
+		test_commit L1 content "0
+1
+2
+3
+4
+5" &&
+
+		# Create R1
+		git checkout R &&
+		test_commit R1 content "1
+2
+3
+4
+5
+6" &&
+
+		# Create L2
+		git checkout L &&
+		git merge R1 &&
+
+		# Create R2
+		git checkout R &&
+		git merge L1 &&
+
+		# Create L3
+		git checkout L &&
+		test_commit L3 content "0
+1
+2
+3
+4
+5
+A" &&
+
+		# Create R3
+		git checkout R &&
+		git rm content &&
+		test_commit R3 renamed "0
+2
+3
+4
+5
+six"
+	)
+'
+
+test_expect_success 'check multiple merge bases' '
+	(
+		cd multiple_merge_bases &&
+
+		git checkout L^0 &&
+
+		test_must_fail git -c merge.conflictstyle=diff3 merge -s recursive R^0 &&
+
+		grep "|||||| merged common ancestors:content" renamed
+	)
+'
+
+test_expect_success 'rebase --merge describes parent of commit being picked' '
+	test_create_repo rebase &&
+	(
+		cd rebase &&
+		test_commit base file &&
+		test_commit master file &&
+		git checkout -b side HEAD^ &&
+		test_commit side file &&
+		test_must_fail git -c merge.conflictstyle=diff3 rebase --merge master &&
+		grep "||||||| parent of" file
+	)
+'
+
+test_expect_success 'rebase --apply describes fake ancestor base' '
+	(
+		cd rebase &&
+		git rebase --abort &&
+		test_must_fail git -c merge.conflictstyle=diff3 rebase --apply master &&
+		grep "||||||| constructed merge base" file
+	)
+'
+
+test_done
diff --git a/third_party/git/t/t3030-merge-recursive.sh b/third_party/git/t/t6430-merge-recursive.sh
index ff641b348a..a328260d42 100755
--- a/third_party/git/t/t3030-merge-recursive.sh
+++ b/third_party/git/t/t6430-merge-recursive.sh
@@ -452,6 +452,34 @@ test_expect_success 'merge-recursive d/f conflict result' '
 
 '
 
+test_expect_success SYMLINKS 'dir in working tree with symlink ancestor does not produce d/f conflict' '
+	git init sym &&
+	(
+		cd sym &&
+		ln -s . foo &&
+		mkdir bar &&
+		>bar/file &&
+		git add foo bar/file &&
+		git commit -m "foo symlink" &&
+
+		git checkout -b branch1 &&
+		git commit --allow-empty -m "empty commit" &&
+
+		git checkout master &&
+		git rm foo &&
+		mkdir foo &&
+		>foo/bar &&
+		git add foo/bar &&
+		git commit -m "replace foo symlink with real foo dir and foo/bar file" &&
+
+		git checkout branch1 &&
+
+		git cherry-pick master &&
+		test_path_is_dir foo &&
+		test_path_is_file foo/bar
+	)
+'
+
 test_expect_success 'reset and 3-way merge' '
 
 	git reset --hard "$c2" &&
@@ -576,7 +604,7 @@ test_expect_success 'merge removes empty directories' '
 	git commit -mremoved-d/e &&
 	git checkout master &&
 	git merge -s recursive rm &&
-	test_must_fail test -d d
+	test_path_is_missing d
 '
 
 test_expect_success 'merge-recursive simple w/submodule' '
@@ -635,7 +663,7 @@ test_expect_failure 'merge-recursive rename vs. rename/symlink' '
 
 test_expect_success 'merging with triple rename across D/F conflict' '
 	git reset --hard HEAD &&
-	git checkout -b main &&
+	git checkout -b topic &&
 	git rm -rf . &&
 
 	echo "just a file" >sub1 &&
@@ -654,7 +682,7 @@ test_expect_success 'merging with triple rename across D/F conflict' '
 	test_tick &&
 	git commit -a -m changesimplefile &&
 
-	git checkout main &&
+	git checkout topic &&
 	git rm sub1 &&
 	git mv sub2 sub1 &&
 	test_tick &&
@@ -667,15 +695,22 @@ test_expect_success 'merging with triple rename across D/F conflict' '
 test_expect_success 'merge-recursive remembers the names of all base trees' '
 	git reset --hard HEAD &&
 
+	# make the index match $c1 so that merge-recursive below does not
+	# fail early
+	git diff --binary HEAD $c1 -- | git apply --cached &&
+
 	# more trees than static slots used by oid_to_hex()
 	for commit in $c0 $c2 $c4 $c5 $c6 $c7
 	do
 		git rev-parse "$commit^{tree}"
 	done >trees &&
 
-	# ignore the return code -- it only fails because the input is weird
+	# ignore the return code; it only fails because the input is weird...
 	test_must_fail git -c merge.verbosity=5 merge-recursive $(cat trees) -- $c1 $c3 >out &&
 
+	# ...but make sure it fails in the expected way
+	test_i18ngrep CONFLICT.*rename/rename out &&
+
 	# merge-recursive prints in reverse order, but we do not care
 	sort <trees >expect &&
 	sed -n "s/^virtual //p" out | sort >actual &&
diff --git a/third_party/git/t/t3031-merge-criscross.sh b/third_party/git/t/t6431-merge-criscross.sh
index 3824756a02..3824756a02 100755
--- a/third_party/git/t/t3031-merge-criscross.sh
+++ b/third_party/git/t/t6431-merge-criscross.sh
diff --git a/third_party/git/t/t3032-merge-recursive-space-options.sh b/third_party/git/t/t6432-merge-recursive-space-options.sh
index b56180ee4a..b56180ee4a 100755
--- a/third_party/git/t/t3032-merge-recursive-space-options.sh
+++ b/third_party/git/t/t6432-merge-recursive-space-options.sh
diff --git a/third_party/git/t/t3033-merge-toplevel.sh b/third_party/git/t/t6433-merge-toplevel.sh
index d314599428..e29c284b9b 100755
--- a/third_party/git/t/t3033-merge-toplevel.sh
+++ b/third_party/git/t/t6433-merge-toplevel.sh
@@ -142,6 +142,17 @@ test_expect_success 'refuse two-project merge by default' '
 	test_must_fail git merge five
 '
 
+test_expect_success 'refuse two-project merge by default, quit before --autostash happens' '
+	t3033_reset &&
+	git reset --hard four &&
+	echo change >>one.t &&
+	git diff >expect &&
+	test_must_fail git merge --autostash five 2>err &&
+	test_i18ngrep ! "stash" err &&
+	git diff >actual &&
+	test_cmp expect actual
+'
+
 test_expect_success 'two-project merge with --allow-unrelated-histories' '
 	t3033_reset &&
 	git reset --hard four &&
@@ -149,4 +160,15 @@ test_expect_success 'two-project merge with --allow-unrelated-histories' '
 	git diff --exit-code five
 '
 
+test_expect_success 'two-project merge with --allow-unrelated-histories with --autostash' '
+	t3033_reset &&
+	git reset --hard four &&
+	echo change >>one.t &&
+	git diff one.t >expect &&
+	git merge --allow-unrelated-histories --autostash five 2>err &&
+	test_i18ngrep "Applied autostash." err &&
+	git diff one.t >actual &&
+	test_cmp expect actual
+'
+
 test_done
diff --git a/third_party/git/t/t3034-merge-recursive-rename-options.sh b/third_party/git/t/t6434-merge-recursive-rename-options.sh
index 3d9fae68c4..3d9fae68c4 100755
--- a/third_party/git/t/t3034-merge-recursive-rename-options.sh
+++ b/third_party/git/t/t6434-merge-recursive-rename-options.sh
diff --git a/third_party/git/t/t3035-merge-sparse.sh b/third_party/git/t/t6435-merge-sparse.sh
index c4b4a94324..74562e1235 100755
--- a/third_party/git/t/t3035-merge-sparse.sh
+++ b/third_party/git/t/t6435-merge-sparse.sh
@@ -28,7 +28,7 @@ test_expect_success 'setup' '
 	git config core.sparseCheckout true &&
 	echo "/checked-out" >.git/info/sparse-checkout &&
 	git reset --hard &&
-	! git merge theirs
+	test_must_fail git merge theirs
 '
 
 test_expect_success 'reset --hard works after the conflict' '
@@ -42,7 +42,7 @@ test_expect_success 'is reset properly' '
 '
 
 test_expect_success 'setup: conflict back' '
-	! git merge theirs
+	test_must_fail git merge theirs
 '
 
 test_expect_success 'Merge abort works after the conflict' '
diff --git a/third_party/git/t/t7607-merge-overwrite.sh b/third_party/git/t/t6436-merge-overwrite.sh
index dd8ab7ede1..dd8ab7ede1 100755
--- a/third_party/git/t/t7607-merge-overwrite.sh
+++ b/third_party/git/t/t6436-merge-overwrite.sh
diff --git a/third_party/git/t/t7405-submodule-merge.sh b/third_party/git/t/t6437-submodule-merge.sh
index aa33978ed2..6a1e5f8232 100755
--- a/third_party/git/t/t7405-submodule-merge.sh
+++ b/third_party/git/t/t6437-submodule-merge.sh
@@ -195,7 +195,7 @@ test_expect_success 'git submodule status should display the merge conflict prop
        url = $TRASH_DIRECTORY/sub
 EOF
        cat >expect <<EOF &&
-U0000000000000000000000000000000000000000 sub
+U$ZERO_OID sub
 EOF
        git submodule status > actual &&
        test_cmp expect actual &&
@@ -214,7 +214,7 @@ test_expect_success 'git submodule status should display the merge conflict prop
        url = $TRASH_DIRECTORY/sub
 EOF
        cat >expect <<EOF &&
-U0000000000000000000000000000000000000000 sub
+U$ZERO_OID sub
 EOF
        git submodule status > actual &&
        test_cmp expect actual &&
diff --git a/third_party/git/t/t7613-merge-submodule.sh b/third_party/git/t/t6438-submodule-directory-file-conflicts.sh
index d1e9fcc781..04bf4be7d7 100755
--- a/third_party/git/t/t7613-merge-submodule.sh
+++ b/third_party/git/t/t6438-submodule-directory-file-conflicts.sh
@@ -6,14 +6,14 @@ test_description='merge can handle submodules'
 . "$TEST_DIRECTORY"/lib-submodule-update.sh
 
 # merges without conflicts
-test_submodule_switch "git merge"
+test_submodule_switch "merge"
 
-test_submodule_switch "git merge --ff"
+test_submodule_switch "merge --ff"
 
-test_submodule_switch "git merge --ff-only"
+test_submodule_switch "merge --ff-only"
 
 KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1
 KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES=1
-test_submodule_switch "git merge --no-ff"
+test_submodule_switch "merge --no-ff"
 
 test_done
diff --git a/third_party/git/t/t7609-merge-co-error-msgs.sh b/third_party/git/t/t6439-merge-co-error-msgs.sh
index e90413204e..5c8894d94f 100755
--- a/third_party/git/t/t7609-merge-co-error-msgs.sh
+++ b/third_party/git/t/t6439-merge-co-error-msgs.sh
@@ -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 &&
diff --git a/third_party/git/t/t6500-gc.sh b/third_party/git/t/t6500-gc.sh
index c0f04dc6b0..4a3b8f48ac 100755
--- a/third_party/git/t/t6500-gc.sh
+++ b/third_party/git/t/t6500-gc.sh
@@ -10,7 +10,24 @@ test_expect_success 'setup' '
 	# do not let the amount of physical memory affects gc
 	# behavior, make sure we always pack everything to one pack by
 	# default
-	git config gc.bigPackThreshold 2g
+	git config gc.bigPackThreshold 2g &&
+
+	# These are simply values which, when hashed as a blob with a newline,
+	# produce a hash where the first byte is 0x17 in their respective
+	# algorithms.
+	test_oid_cache <<-EOF
+	obj1 sha1:263
+	obj1 sha256:34
+
+	obj2 sha1:410
+	obj2 sha256:174
+
+	obj3 sha1:523
+	obj3 sha256:313
+
+	obj4 sha1:790
+	obj4 sha256:481
+	EOF
 '
 
 test_expect_success 'gc empty repository' '
@@ -85,13 +102,13 @@ test_expect_success 'auto gc with too many loose objects does not attempt to cre
 	# We need to create two object whose sha1s start with 17
 	# since this is what git gc counts.  As it happens, these
 	# two blobs will do so.
-	test_commit 263 &&
-	test_commit 410 &&
+	test_commit "$(test_oid obj1)" &&
+	test_commit "$(test_oid obj2)" &&
 	# Our first gc will create a pack; our second will create a second pack
 	git gc --auto &&
 	ls .git/objects/pack | sort >existing_packs &&
-	test_commit 523 &&
-	test_commit 790 &&
+	test_commit "$(test_oid obj3)" &&
+	test_commit "$(test_oid obj4)" &&
 
 	git gc --auto 2>err &&
 	test_i18ngrep ! "^warning:" err &&
@@ -103,14 +120,14 @@ test_expect_success 'auto gc with too many loose objects does not attempt to cre
 '
 
 test_expect_success 'gc --no-quiet' '
-	git -c gc.writeCommitGraph=true gc --no-quiet >stdout 2>stderr &&
+	GIT_PROGRESS_DELAY=0 git -c gc.writeCommitGraph=true gc --no-quiet >stdout 2>stderr &&
 	test_must_be_empty stdout &&
-	test_line_count = 1 stderr &&
 	test_i18ngrep "Computing commit graph generation numbers" stderr
 '
 
 test_expect_success TTY 'with TTY: gc --no-quiet' '
-	test_terminal git -c gc.writeCommitGraph=true gc --no-quiet >stdout 2>stderr &&
+	test_terminal env GIT_PROGRESS_DELAY=0 \
+		git -c gc.writeCommitGraph=true gc --no-quiet >stdout 2>stderr &&
 	test_must_be_empty stdout &&
 	test_i18ngrep "Enumerating objects" stderr &&
 	test_i18ngrep "Computing commit graph generation numbers" stderr
diff --git a/third_party/git/t/t6501-freshen-objects.sh b/third_party/git/t/t6501-freshen-objects.sh
index 033871ee5f..8a3bb4105b 100755
--- a/third_party/git/t/t6501-freshen-objects.sh
+++ b/third_party/git/t/t6501-freshen-objects.sh
@@ -128,33 +128,33 @@ for repack in '' true; do
 done
 
 test_expect_success 'do not complain about existing broken links (commit)' '
-	cat >broken-commit <<-\EOF &&
-	tree 0000000000000000000000000000000000000001
-	parent 0000000000000000000000000000000000000002
+	cat >broken-commit <<-EOF &&
+	tree $(test_oid 001)
+	parent $(test_oid 002)
 	author whatever <whatever@example.com> 1234 -0000
 	committer whatever <whatever@example.com> 1234 -0000
 
 	some message
 	EOF
 	commit=$(git hash-object -t commit -w broken-commit) &&
-	git gc 2>stderr &&
+	git gc -q 2>stderr &&
 	verbose git cat-file -e $commit &&
 	test_must_be_empty stderr
 '
 
 test_expect_success 'do not complain about existing broken links (tree)' '
-	cat >broken-tree <<-\EOF &&
-	100644 blob 0000000000000000000000000000000000000003	foo
+	cat >broken-tree <<-EOF &&
+	100644 blob $(test_oid 003)	foo
 	EOF
 	tree=$(git mktree --missing <broken-tree) &&
-	git gc 2>stderr &&
+	git gc -q 2>stderr &&
 	git cat-file -e $tree &&
 	test_must_be_empty stderr
 '
 
 test_expect_success 'do not complain about existing broken links (tag)' '
-	cat >broken-tag <<-\EOF &&
-	object 0000000000000000000000000000000000000004
+	cat >broken-tag <<-EOF &&
+	object $(test_oid 004)
 	type commit
 	tag broken
 	tagger whatever <whatever@example.com> 1234 -0000
@@ -162,7 +162,7 @@ test_expect_success 'do not complain about existing broken links (tag)' '
 	this is a broken tag
 	EOF
 	tag=$(git hash-object -t tag -w broken-tag) &&
-	git gc 2>stderr &&
+	git gc -q 2>stderr &&
 	git cat-file -e $tag &&
 	test_must_be_empty stderr
 '
diff --git a/third_party/git/t/t6600-test-reach.sh b/third_party/git/t/t6600-test-reach.sh
index b24d850036..f807276337 100755
--- a/third_party/git/t/t6600-test-reach.sh
+++ b/third_party/git/t/t6600-test-reach.sh
@@ -51,8 +51,10 @@ test_expect_success 'setup' '
 	done &&
 	git commit-graph write --reachable &&
 	mv .git/objects/info/commit-graph commit-graph-full &&
+	chmod u+w commit-graph-full &&
 	git show-ref -s commit-5-5 | git commit-graph write --stdin-commits &&
 	mv .git/objects/info/commit-graph commit-graph-half &&
+	chmod u+w commit-graph-half &&
 	git config core.commitGraph true
 '
 
@@ -108,6 +110,36 @@ test_expect_success 'in_merge_bases:miss' '
 	test_three_modes in_merge_bases
 '
 
+test_expect_success 'in_merge_bases_many:hit' '
+	cat >input <<-\EOF &&
+	A:commit-6-8
+	X:commit-6-9
+	X:commit-5-7
+	EOF
+	echo "in_merge_bases_many(A,X):1" >expect &&
+	test_three_modes in_merge_bases_many
+'
+
+test_expect_success 'in_merge_bases_many:miss' '
+	cat >input <<-\EOF &&
+	A:commit-6-8
+	X:commit-7-7
+	X:commit-8-6
+	EOF
+	echo "in_merge_bases_many(A,X):0" >expect &&
+	test_three_modes in_merge_bases_many
+'
+
+test_expect_success 'in_merge_bases_many:miss-heuristic' '
+	cat >input <<-\EOF &&
+	A:commit-6-8
+	X:commit-7-5
+	X:commit-6-6
+	EOF
+	echo "in_merge_bases_many(A,X):0" >expect &&
+	test_three_modes in_merge_bases_many
+'
+
 test_expect_success 'is_descendant_of:hit' '
 	cat >input <<-\EOF &&
 	A:commit-5-7
diff --git a/third_party/git/t/t7001-mv.sh b/third_party/git/t/t7001-mv.sh
index 36b50d0b4c..63d5f41a12 100755
--- a/third_party/git/t/t7001-mv.sh
+++ b/third_party/git/t/t7001-mv.sh
@@ -177,7 +177,7 @@ test_expect_success "Sergey Vlasov's test case" '
 	date >ab.c &&
 	date >ab/d &&
 	git add ab.c ab &&
-	git commit -m 'initial' &&
+	git commit -m "initial" &&
 	git mv ab a
 '
 
@@ -248,6 +248,23 @@ test_expect_success 'git mv should not change sha1 of moved cache entry' '
 
 rm -f dirty dirty2
 
+# NB: This test is about the error message
+# as well as the failure.
+test_expect_success 'git mv error on conflicted file' '
+	rm -fr .git &&
+	git init &&
+	>conflict &&
+	test_when_finished "rm -f conflict" &&
+	cfhash=$(git hash-object -w conflict) &&
+	q_to_tab <<-EOF | git update-index --index-info &&
+	0 $cfhash 0Qconflict
+	100644 $cfhash 1Qconflict
+	EOF
+
+	test_must_fail git mv conflict newname 2>actual &&
+	test_i18ngrep "conflicted" actual
+'
+
 test_expect_success 'git mv should overwrite symlink to a file' '
 
 	rm -fr .git &&
diff --git a/third_party/git/t/t7003-filter-branch.sh b/third_party/git/t/t7003-filter-branch.sh
index e23de7d0b5..36477cb1f4 100755
--- a/third_party/git/t/t7003-filter-branch.sh
+++ b/third_party/git/t/t7003-filter-branch.sh
@@ -463,10 +463,11 @@ test_expect_success 'rewrite submodule with another content' '
 '
 
 test_expect_success 'replace submodule revision' '
+	invalid=$(test_oid numeric) &&
 	git reset --hard original &&
 	git filter-branch -f --tree-filter \
 	    "if git ls-files --error-unmatch -- submod > /dev/null 2>&1
-	     then git update-index --cacheinfo 160000 0123456789012345678901234567890123456789 submod
+	     then git update-index --cacheinfo 160000 $invalid submod
 	     fi" HEAD &&
 	test $orig_head != $(git show-ref --hash --head HEAD)
 '
diff --git a/third_party/git/t/t7004-tag.sh b/third_party/git/t/t7004-tag.sh
index 80eb13d94e..05f411c821 100755
--- a/third_party/git/t/t7004-tag.sh
+++ b/third_party/git/t/t7004-tag.sh
@@ -227,10 +227,10 @@ test_expect_success \
 test_expect_success \
 	'trying to delete two tags, existing and not, should fail in the 2nd' '
 	tag_exists mytag &&
-	! tag_exists myhead &&
-	test_must_fail git tag -d mytag anothertag &&
+	! tag_exists nonexistingtag &&
+	test_must_fail git tag -d mytag nonexistingtag &&
 	! tag_exists mytag &&
-	! tag_exists myhead
+	! tag_exists nonexistingtag
 '
 
 test_expect_success 'trying to delete an already deleted tag should fail' \
@@ -517,7 +517,6 @@ test_expect_success \
 test_expect_success \
 	'trying to create tags giving both -m or -F options should fail' '
 	echo "message file 1" >msgfile1 &&
-	echo "message file 2" >msgfile2 &&
 	! tag_exists msgtag &&
 	test_must_fail git tag -m "message 1" -F msgfile1 msgtag &&
 	! tag_exists msgtag &&
@@ -1420,7 +1419,7 @@ test_expect_success \
 get_tag_header reuse $commit commit $time >expect
 echo "An annotation to be reused" >> expect
 test_expect_success \
-	'overwriting an annoted tag should use its previous body' '
+	'overwriting an annotated tag should use its previous body' '
 	git tag -a -m "An annotation to be reused" reuse &&
 	GIT_EDITOR=true git tag -f -a reuse &&
 	get_tag_msg reuse >actual &&
@@ -1727,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
@@ -2015,8 +2015,8 @@ test_expect_success '--merged can be used in non-list mode' '
 	test_cmp expect actual
 '
 
-test_expect_success '--merged is incompatible with --no-merged' '
-	test_must_fail git tag --merged HEAD --no-merged HEAD
+test_expect_success '--merged is compatible with --no-merged' '
+	git tag --merged HEAD --no-merged HEAD
 '
 
 test_expect_success '--merged shows merged tags' '
diff --git a/third_party/git/t/t7009-filter-branch-null-sha1.sh b/third_party/git/t/t7008-filter-branch-null-sha1.sh
index 9ba9f24ad2..9ba9f24ad2 100755
--- a/third_party/git/t/t7009-filter-branch-null-sha1.sh
+++ b/third_party/git/t/t7008-filter-branch-null-sha1.sh
diff --git a/third_party/git/t/t7012-skip-worktree-writing.sh b/third_party/git/t/t7012-skip-worktree-writing.sh
index 9d1abe50ef..7476781979 100755
--- a/third_party/git/t/t7012-skip-worktree-writing.sh
+++ b/third_party/git/t/t7012-skip-worktree-writing.sh
@@ -134,6 +134,21 @@ test_expect_success 'git-clean, dirty case' '
 	test_i18ncmp expected result
 '
 
+test_expect_success '--ignore-skip-worktree-entries leaves worktree alone' '
+	test_commit keep-me &&
+	git update-index --skip-worktree keep-me.t &&
+	rm keep-me.t &&
+
+	: ignoring the worktree &&
+	git update-index --remove --ignore-skip-worktree-entries keep-me.t &&
+	git diff-index --cached --exit-code HEAD &&
+
+	: not ignoring the worktree, a deletion is staged &&
+	git update-index --remove keep-me.t &&
+	test_must_fail git diff-index --cached --exit-code HEAD \
+		--diff-filter=D -- keep-me.t
+'
+
 #TODO test_expect_failure 'git-apply adds file' false
 #TODO test_expect_failure 'git-apply updates file' false
 #TODO test_expect_failure 'git-apply removes file' false
diff --git a/third_party/git/t/t7030-verify-tag.sh b/third_party/git/t/t7030-verify-tag.sh
index 041e319e79..5c5bc32ccb 100755
--- a/third_party/git/t/t7030-verify-tag.sh
+++ b/third_party/git/t/t7030-verify-tag.sh
@@ -44,8 +44,8 @@ test_expect_success GPG 'create signed tags' '
 test_expect_success GPGSM 'create signed tags x509 ' '
 	test_config gpg.format x509 &&
 	test_config user.signingkey $GIT_COMMITTER_EMAIL &&
-	echo 9 >file && test_tick && git commit -a -m "nineth gpgsm-signed" &&
-	git tag -s -m nineth nineth-signed-x509
+	echo 9 >file && test_tick && git commit -a -m "ninth gpgsm-signed" &&
+	git tag -s -m ninth ninth-signed-x509
 '
 
 test_expect_success GPG 'verify and show signatures' '
@@ -80,10 +80,34 @@ test_expect_success GPG 'verify and show signatures' '
 '
 
 test_expect_success GPGSM 'verify and show signatures x509' '
-	git verify-tag nineth-signed-x509 2>actual &&
+	git verify-tag ninth-signed-x509 2>actual &&
 	grep "Good signature from" actual &&
 	! grep "BAD signature from" actual &&
-	echo nineth-signed-x509 OK
+	echo ninth-signed-x509 OK
+'
+
+test_expect_success GPGSM 'verify and show signatures x509 with low minTrustLevel' '
+	test_config gpg.minTrustLevel undefined &&
+	git verify-tag ninth-signed-x509 2>actual &&
+	grep "Good signature from" actual &&
+	! grep "BAD signature from" actual &&
+	echo ninth-signed-x509 OK
+'
+
+test_expect_success GPGSM 'verify and show signatures x509 with matching minTrustLevel' '
+	test_config gpg.minTrustLevel fully &&
+	git verify-tag ninth-signed-x509 2>actual &&
+	grep "Good signature from" actual &&
+	! grep "BAD signature from" actual &&
+	echo ninth-signed-x509 OK
+'
+
+test_expect_success GPGSM 'verify and show signatures x509 with high minTrustLevel' '
+	test_config gpg.minTrustLevel ultimate &&
+	test_must_fail git verify-tag ninth-signed-x509 2>actual &&
+	grep "Good signature from" actual &&
+	! grep "BAD signature from" actual &&
+	echo ninth-signed-x509 OK
 '
 
 test_expect_success GPG 'detect fudged signature' '
@@ -127,10 +151,10 @@ test_expect_success GPG 'verify signatures with --raw' '
 '
 
 test_expect_success GPGSM 'verify signatures with --raw x509' '
-	git verify-tag --raw nineth-signed-x509 2>actual &&
+	git verify-tag --raw ninth-signed-x509 2>actual &&
 	grep "GOODSIG" actual &&
 	! grep "BADSIG" actual &&
-	echo nineth-signed-x509 OK
+	echo ninth-signed-x509 OK
 '
 
 test_expect_success GPG 'verify multiple tags' '
@@ -147,7 +171,7 @@ test_expect_success GPG 'verify multiple tags' '
 '
 
 test_expect_success GPGSM 'verify multiple tags x509' '
-	tags="seventh-signed nineth-signed-x509" &&
+	tags="seventh-signed ninth-signed-x509" &&
 	for i in $tags
 	do
 		git verify-tag -v --raw $i || return 1
diff --git a/third_party/git/t/t7061-wtstatus-ignore.sh b/third_party/git/t/t7061-wtstatus-ignore.sh
index 0c394cf995..2f9bea9793 100755
--- a/third_party/git/t/t7061-wtstatus-ignore.sh
+++ b/third_party/git/t/t7061-wtstatus-ignore.sh
@@ -30,6 +30,31 @@ test_expect_success 'same with gitignore starting with BOM' '
 	test_cmp expected actual
 '
 
+test_expect_success 'status untracked files --ignored with pathspec (no match)' '
+	git status --porcelain --ignored -- untracked/i >actual &&
+	test_must_be_empty actual &&
+	git status --porcelain --ignored -- untracked/u >actual &&
+	test_must_be_empty actual
+'
+
+test_expect_success 'status untracked files --ignored with pathspec (literal match)' '
+	git status --porcelain --ignored -- untracked/ignored >actual &&
+	echo "!! untracked/ignored" >expected &&
+	test_cmp expected actual &&
+	git status --porcelain --ignored -- untracked/uncommitted >actual &&
+	echo "?? untracked/uncommitted" >expected &&
+	test_cmp expected actual
+'
+
+test_expect_success 'status untracked files --ignored with pathspec (glob match)' '
+	git status --porcelain --ignored -- untracked/i\* >actual &&
+	echo "!! untracked/ignored" >expected &&
+	test_cmp expected actual &&
+	git status --porcelain --ignored -- untracked/u\* >actual &&
+	echo "?? untracked/uncommitted" >expected &&
+	test_cmp expected actual
+'
+
 cat >expected <<\EOF
 ?? .gitignore
 ?? actual
@@ -43,11 +68,16 @@ test_expect_success 'status untracked directory with --ignored -u' '
 	test_cmp expected actual
 '
 cat >expected <<\EOF
-?? untracked/uncommitted
+?? untracked/
 !! untracked/ignored
 EOF
 
-test_expect_success 'status prefixed untracked directory with --ignored' '
+test_expect_success 'status of untracked directory with --ignored works with or without prefix' '
+	git status --porcelain --ignored >tmp &&
+	grep untracked/ tmp >actual &&
+	rm tmp &&
+	test_cmp expected actual &&
+
 	git status --porcelain --ignored untracked/ >actual &&
 	test_cmp expected actual
 '
diff --git a/third_party/git/t/t7063-status-untracked-cache.sh b/third_party/git/t/t7063-status-untracked-cache.sh
index 190ae149cf..a682a3d826 100755
--- a/third_party/git/t/t7063-status-untracked-cache.sh
+++ b/third_party/git/t/t7063-status-untracked-cache.sh
@@ -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() {
@@ -30,6 +30,30 @@ status_is_clean() {
 	test_must_be_empty ../status.actual
 }
 
+# Ignore_Untracked_Cache, abbreviated to 3 letters because then people can
+# compare commands side-by-side, e.g.
+#    iuc status --porcelain >expect &&
+#    git status --porcelain >actual &&
+#    test_cmp expect actual
+iuc () {
+	git ls-files -s >../current-index-entries
+	git ls-files -t | sed -ne s/^S.//p >../current-sparse-entries
+
+	GIT_INDEX_FILE=.git/tmp_index
+	export GIT_INDEX_FILE
+	git update-index --index-info <../current-index-entries
+	git update-index --skip-worktree $(cat ../current-sparse-entries)
+
+	git -c core.untrackedCache=false "$@"
+	ret=$?
+
+	rm ../current-index-entries
+	rm $GIT_INDEX_FILE
+	unset GIT_INDEX_FILE
+
+	return $ret
+}
+
 test_lazy_prereq UNTRACKED_CACHE '
 	{ git update-index --test-untracked-cache; ret=$?; } &&
 	test $ret -ne 1
@@ -51,14 +75,24 @@ test_expect_success 'setup' '
 	touch one two three done/one dtwo/two dthree/three &&
 	git add one two done/one &&
 	: >.git/info/exclude &&
-	git update-index --untracked-cache
+	git update-index --untracked-cache &&
+	test_oid_cache <<-EOF
+	root sha1:e6fcc8f2ee31bae321d66afd183fcb7237afae6e
+	root sha256:b90c672088c015b9c83876e919da311bad4cd39639fb139f988af6a11493b974
+
+	exclude sha1:13263c0978fb9fad16b2d580fb800b6d811c3ff0
+	exclude sha256:fe4aaa1bbbbce4cb8f73426748a14c5ad6026b26f90505a0bf2494b165a5b76c
+
+	done sha1:1946f0437f90c5005533cbe1736a6451ca301714
+	done sha256:7f079501d79f665b3acc50f5e0e9e94509084d5032ac20113a37dd5029b757cc
+	EOF
 '
 
 test_expect_success 'untracked cache is empty' '
 	test-tool dump-untracked-cache >../actual &&
 	cat >../expect-empty <<EOF &&
-info/exclude 0000000000000000000000000000000000000000
-core.excludesfile 0000000000000000000000000000000000000000
+info/exclude $ZERO_OID
+core.excludesfile $ZERO_OID
 exclude_per_dir .gitignore
 flags 00000006
 EOF
@@ -76,17 +110,17 @@ EOF
 
 cat >../dump.expect <<EOF &&
 info/exclude $EMPTY_BLOB
-core.excludesfile 0000000000000000000000000000000000000000
+core.excludesfile $ZERO_OID
 exclude_per_dir .gitignore
 flags 00000006
-/ 0000000000000000000000000000000000000000 recurse valid
+/ $ZERO_OID recurse valid
 dthree/
 dtwo/
 three
-/done/ 0000000000000000000000000000000000000000 recurse valid
-/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid
+/done/ $ZERO_OID recurse valid
+/dthree/ $ZERO_OID recurse check_only valid
 three
-/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid
+/dtwo/ $ZERO_OID recurse check_only valid
 two
 EOF
 
@@ -95,6 +129,8 @@ test_expect_success 'status first time (empty cache)' '
 	: >../trace &&
 	GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
 	git status --porcelain >../actual &&
+	iuc status --porcelain >../status.iuc &&
+	test_cmp ../status.expect ../status.iuc &&
 	test_cmp ../status.expect ../actual &&
 	cat >../trace.expect <<EOF &&
 node creation: 3
@@ -115,6 +151,8 @@ test_expect_success 'status second time (fully populated cache)' '
 	: >../trace &&
 	GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
 	git status --porcelain >../actual &&
+	iuc status --porcelain >../status.iuc &&
+	test_cmp ../status.expect ../status.iuc &&
 	test_cmp ../status.expect ../actual &&
 	cat >../trace.expect <<EOF &&
 node creation: 0
@@ -136,6 +174,7 @@ test_expect_success 'modify in root directory, one dir invalidation' '
 	: >../trace &&
 	GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
 	git status --porcelain >../actual &&
+	iuc status --porcelain >../status.iuc &&
 	cat >../status.expect <<EOF &&
 A  done/one
 A  one
@@ -145,6 +184,7 @@ A  two
 ?? four
 ?? three
 EOF
+	test_cmp ../status.expect ../status.iuc &&
 	test_cmp ../status.expect ../actual &&
 	cat >../trace.expect <<EOF &&
 node creation: 0
@@ -160,18 +200,18 @@ test_expect_success 'verify untracked cache dump' '
 	test-tool dump-untracked-cache >../actual &&
 	cat >../expect <<EOF &&
 info/exclude $EMPTY_BLOB
-core.excludesfile 0000000000000000000000000000000000000000
+core.excludesfile $ZERO_OID
 exclude_per_dir .gitignore
 flags 00000006
-/ 0000000000000000000000000000000000000000 recurse valid
+/ $ZERO_OID recurse valid
 dthree/
 dtwo/
 four
 three
-/done/ 0000000000000000000000000000000000000000 recurse valid
-/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid
+/done/ $ZERO_OID recurse valid
+/dthree/ $ZERO_OID recurse check_only valid
 three
-/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid
+/dtwo/ $ZERO_OID recurse check_only valid
 two
 EOF
 	test_cmp ../expect ../actual
@@ -183,6 +223,7 @@ test_expect_success 'new .gitignore invalidates recursively' '
 	: >../trace &&
 	GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
 	git status --porcelain >../actual &&
+	iuc status --porcelain >../status.iuc &&
 	cat >../status.expect <<EOF &&
 A  done/one
 A  one
@@ -192,6 +233,7 @@ A  two
 ?? dtwo/
 ?? three
 EOF
+	test_cmp ../status.expect ../status.iuc &&
 	test_cmp ../status.expect ../actual &&
 	cat >../trace.expect <<EOF &&
 node creation: 0
@@ -207,18 +249,18 @@ test_expect_success 'verify untracked cache dump' '
 	test-tool dump-untracked-cache >../actual &&
 	cat >../expect <<EOF &&
 info/exclude $EMPTY_BLOB
-core.excludesfile 0000000000000000000000000000000000000000
+core.excludesfile $ZERO_OID
 exclude_per_dir .gitignore
 flags 00000006
-/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse valid
+/ $(test_oid root) recurse valid
 .gitignore
 dthree/
 dtwo/
 three
-/done/ 0000000000000000000000000000000000000000 recurse valid
-/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid
+/done/ $ZERO_OID recurse valid
+/dthree/ $ZERO_OID recurse check_only valid
 three
-/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid
+/dtwo/ $ZERO_OID recurse check_only valid
 two
 EOF
 	test_cmp ../expect ../actual
@@ -230,6 +272,7 @@ test_expect_success 'new info/exclude invalidates everything' '
 	: >../trace &&
 	GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
 	git status --porcelain >../actual &&
+	iuc status --porcelain >../status.iuc &&
 	cat >../status.expect <<EOF &&
 A  done/one
 A  one
@@ -237,6 +280,7 @@ A  two
 ?? .gitignore
 ?? dtwo/
 EOF
+	test_cmp ../status.expect ../status.iuc &&
 	test_cmp ../status.expect ../actual &&
 	cat >../trace.expect <<EOF &&
 node creation: 0
@@ -250,16 +294,16 @@ EOF
 test_expect_success 'verify untracked cache dump' '
 	test-tool dump-untracked-cache >../actual &&
 	cat >../expect <<EOF &&
-info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0
-core.excludesfile 0000000000000000000000000000000000000000
+info/exclude $(test_oid exclude)
+core.excludesfile $ZERO_OID
 exclude_per_dir .gitignore
 flags 00000006
-/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse valid
+/ $(test_oid root) recurse valid
 .gitignore
 dtwo/
-/done/ 0000000000000000000000000000000000000000 recurse valid
-/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid
-/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid
+/done/ $ZERO_OID recurse valid
+/dthree/ $ZERO_OID recurse check_only valid
+/dtwo/ $ZERO_OID recurse check_only valid
 two
 EOF
 	test_cmp ../expect ../actual
@@ -269,14 +313,14 @@ test_expect_success 'move two from tracked to untracked' '
 	git rm --cached two &&
 	test-tool dump-untracked-cache >../actual &&
 	cat >../expect <<EOF &&
-info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0
-core.excludesfile 0000000000000000000000000000000000000000
+info/exclude $(test_oid exclude)
+core.excludesfile $ZERO_OID
 exclude_per_dir .gitignore
 flags 00000006
-/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse
-/done/ 0000000000000000000000000000000000000000 recurse valid
-/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid
-/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid
+/ $(test_oid root) recurse
+/done/ $ZERO_OID recurse valid
+/dthree/ $ZERO_OID recurse check_only valid
+/dtwo/ $ZERO_OID recurse check_only valid
 two
 EOF
 	test_cmp ../expect ../actual
@@ -286,6 +330,7 @@ test_expect_success 'status after the move' '
 	: >../trace &&
 	GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
 	git status --porcelain >../actual &&
+	iuc status --porcelain >../status.iuc &&
 	cat >../status.expect <<EOF &&
 A  done/one
 A  one
@@ -293,6 +338,7 @@ A  one
 ?? dtwo/
 ?? two
 EOF
+	test_cmp ../status.expect ../status.iuc &&
 	test_cmp ../status.expect ../actual &&
 	cat >../trace.expect <<EOF &&
 node creation: 0
@@ -306,17 +352,17 @@ EOF
 test_expect_success 'verify untracked cache dump' '
 	test-tool dump-untracked-cache >../actual &&
 	cat >../expect <<EOF &&
-info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0
-core.excludesfile 0000000000000000000000000000000000000000
+info/exclude $(test_oid exclude)
+core.excludesfile $ZERO_OID
 exclude_per_dir .gitignore
 flags 00000006
-/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse valid
+/ $(test_oid root) recurse valid
 .gitignore
 dtwo/
 two
-/done/ 0000000000000000000000000000000000000000 recurse valid
-/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid
-/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid
+/done/ $ZERO_OID recurse valid
+/dthree/ $ZERO_OID recurse check_only valid
+/dtwo/ $ZERO_OID recurse check_only valid
 two
 EOF
 	test_cmp ../expect ../actual
@@ -326,14 +372,14 @@ test_expect_success 'move two from untracked to tracked' '
 	git add two &&
 	test-tool dump-untracked-cache >../actual &&
 	cat >../expect <<EOF &&
-info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0
-core.excludesfile 0000000000000000000000000000000000000000
+info/exclude $(test_oid exclude)
+core.excludesfile $ZERO_OID
 exclude_per_dir .gitignore
 flags 00000006
-/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse
-/done/ 0000000000000000000000000000000000000000 recurse valid
-/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid
-/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid
+/ $(test_oid root) recurse
+/done/ $ZERO_OID recurse valid
+/dthree/ $ZERO_OID recurse check_only valid
+/dtwo/ $ZERO_OID recurse check_only valid
 two
 EOF
 	test_cmp ../expect ../actual
@@ -343,6 +389,7 @@ test_expect_success 'status after the move' '
 	: >../trace &&
 	GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
 	git status --porcelain >../actual &&
+	iuc status --porcelain >../status.iuc &&
 	cat >../status.expect <<EOF &&
 A  done/one
 A  one
@@ -350,6 +397,7 @@ A  two
 ?? .gitignore
 ?? dtwo/
 EOF
+	test_cmp ../status.expect ../status.iuc &&
 	test_cmp ../status.expect ../actual &&
 	cat >../trace.expect <<EOF &&
 node creation: 0
@@ -363,16 +411,16 @@ EOF
 test_expect_success 'verify untracked cache dump' '
 	test-tool dump-untracked-cache >../actual &&
 	cat >../expect <<EOF &&
-info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0
-core.excludesfile 0000000000000000000000000000000000000000
+info/exclude $(test_oid exclude)
+core.excludesfile $ZERO_OID
 exclude_per_dir .gitignore
 flags 00000006
-/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse valid
+/ $(test_oid root) recurse valid
 .gitignore
 dtwo/
-/done/ 0000000000000000000000000000000000000000 recurse valid
-/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid
-/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid
+/done/ $ZERO_OID recurse valid
+/dthree/ $ZERO_OID recurse check_only valid
+/dtwo/ $ZERO_OID recurse check_only valid
 two
 EOF
 	test_cmp ../expect ../actual
@@ -390,10 +438,12 @@ test_expect_success 'status after commit' '
 	: >../trace &&
 	GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
 	git status --porcelain >../actual &&
+	iuc status --porcelain >../status.iuc &&
 	cat >../status.expect <<EOF &&
 ?? .gitignore
 ?? dtwo/
 EOF
+	test_cmp ../status.expect ../status.iuc &&
 	test_cmp ../status.expect ../actual &&
 	cat >../trace.expect <<EOF &&
 node creation: 0
@@ -407,16 +457,16 @@ EOF
 test_expect_success 'untracked cache correct after commit' '
 	test-tool dump-untracked-cache >../actual &&
 	cat >../expect <<EOF &&
-info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0
-core.excludesfile 0000000000000000000000000000000000000000
+info/exclude $(test_oid exclude)
+core.excludesfile $ZERO_OID
 exclude_per_dir .gitignore
 flags 00000006
-/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse valid
+/ $(test_oid root) recurse valid
 .gitignore
 dtwo/
-/done/ 0000000000000000000000000000000000000000 recurse valid
-/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid
-/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid
+/done/ $ZERO_OID recurse valid
+/dthree/ $ZERO_OID recurse check_only valid
+/dtwo/ $ZERO_OID recurse check_only valid
 two
 EOF
 	test_cmp ../expect ../actual
@@ -447,12 +497,14 @@ test_expect_success 'test sparse status with untracked cache' '
 	avoid_racy &&
 	GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
 	git status --porcelain >../status.actual &&
+	iuc status --porcelain >../status.iuc &&
 	cat >../status.expect <<EOF &&
  M done/two
 ?? .gitignore
 ?? done/five
 ?? dtwo/
 EOF
+	test_cmp ../status.expect ../status.iuc &&
 	test_cmp ../status.expect ../status.actual &&
 	cat >../trace.expect <<EOF &&
 node creation: 0
@@ -466,17 +518,17 @@ EOF
 test_expect_success 'untracked cache correct after status' '
 	test-tool dump-untracked-cache >../actual &&
 	cat >../expect <<EOF &&
-info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0
-core.excludesfile 0000000000000000000000000000000000000000
+info/exclude $(test_oid exclude)
+core.excludesfile $ZERO_OID
 exclude_per_dir .gitignore
 flags 00000006
-/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse valid
+/ $(test_oid root) recurse valid
 .gitignore
 dtwo/
-/done/ 1946f0437f90c5005533cbe1736a6451ca301714 recurse valid
+/done/ $(test_oid done) recurse valid
 five
-/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid
-/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid
+/dthree/ $ZERO_OID recurse check_only valid
+/dtwo/ $ZERO_OID recurse check_only valid
 two
 EOF
 	test_cmp ../expect ../actual
@@ -487,12 +539,14 @@ test_expect_success 'test sparse status again with untracked cache' '
 	: >../trace &&
 	GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
 	git status --porcelain >../status.actual &&
+	iuc status --porcelain >../status.iuc &&
 	cat >../status.expect <<EOF &&
  M done/two
 ?? .gitignore
 ?? done/five
 ?? dtwo/
 EOF
+	test_cmp ../status.expect ../status.iuc &&
 	test_cmp ../status.expect ../status.actual &&
 	cat >../trace.expect <<EOF &&
 node creation: 0
@@ -514,6 +568,7 @@ test_expect_success 'test sparse status with untracked cache and subdir' '
 	: >../trace &&
 	GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
 	git status --porcelain >../status.actual &&
+	iuc status --porcelain >../status.iuc &&
 	cat >../status.expect <<EOF &&
  M done/two
 ?? .gitignore
@@ -521,6 +576,7 @@ test_expect_success 'test sparse status with untracked cache and subdir' '
 ?? done/sub/
 ?? dtwo/
 EOF
+	test_cmp ../status.expect ../status.iuc &&
 	test_cmp ../status.expect ../status.actual &&
 	cat >../trace.expect <<EOF &&
 node creation: 2
@@ -534,22 +590,22 @@ EOF
 test_expect_success 'verify untracked cache dump (sparse/subdirs)' '
 	test-tool dump-untracked-cache >../actual &&
 	cat >../expect-from-test-dump <<EOF &&
-info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0
-core.excludesfile 0000000000000000000000000000000000000000
+info/exclude $(test_oid exclude)
+core.excludesfile $ZERO_OID
 exclude_per_dir .gitignore
 flags 00000006
-/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse valid
+/ $(test_oid root) recurse valid
 .gitignore
 dtwo/
-/done/ 1946f0437f90c5005533cbe1736a6451ca301714 recurse valid
+/done/ $(test_oid done) recurse valid
 five
 sub/
-/done/sub/ 0000000000000000000000000000000000000000 recurse check_only valid
+/done/sub/ $ZERO_OID recurse check_only valid
 sub/
-/done/sub/sub/ 0000000000000000000000000000000000000000 recurse check_only valid
+/done/sub/sub/ $ZERO_OID recurse check_only valid
 file
-/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid
-/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid
+/dthree/ $ZERO_OID recurse check_only valid
+/dtwo/ $ZERO_OID recurse check_only valid
 two
 EOF
 	test_cmp ../expect-from-test-dump ../actual
@@ -560,6 +616,8 @@ test_expect_success 'test sparse status again with untracked cache and subdir' '
 	: >../trace &&
 	GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
 	git status --porcelain >../status.actual &&
+	iuc status --porcelain >../status.iuc &&
+	test_cmp ../status.expect ../status.iuc &&
 	test_cmp ../status.expect ../status.actual &&
 	cat >../trace.expect <<EOF &&
 node creation: 0
@@ -573,6 +631,7 @@ EOF
 test_expect_success 'move entry in subdir from untracked to cached' '
 	git add dtwo/two &&
 	git status --porcelain >../status.actual &&
+	iuc status --porcelain >../status.iuc &&
 	cat >../status.expect <<EOF &&
  M done/two
 A  dtwo/two
@@ -580,12 +639,14 @@ A  dtwo/two
 ?? done/five
 ?? done/sub/
 EOF
+	test_cmp ../status.expect ../status.iuc &&
 	test_cmp ../status.expect ../status.actual
 '
 
 test_expect_success 'move entry in subdir from cached to untracked' '
 	git rm --cached dtwo/two &&
 	git status --porcelain >../status.actual &&
+	iuc status --porcelain >../status.iuc &&
 	cat >../status.expect <<EOF &&
  M done/two
 ?? .gitignore
@@ -593,6 +654,7 @@ test_expect_success 'move entry in subdir from cached to untracked' '
 ?? done/sub/
 ?? dtwo/
 EOF
+	test_cmp ../status.expect ../status.iuc &&
 	test_cmp ../status.expect ../status.actual
 '
 
@@ -754,8 +816,8 @@ test_expect_success '"status" after file replacement should be clean with UC=tru
 	test-tool dump-untracked-cache >../actual &&
 	grep -F "recurse valid" ../actual >../actual.grep &&
 	cat >../expect.grep <<EOF &&
-/ 0000000000000000000000000000000000000000 recurse valid
-/two/ 0000000000000000000000000000000000000000 recurse valid
+/ $ZERO_OID recurse valid
+/two/ $ZERO_OID recurse valid
 EOF
 	status_is_clean &&
 	test_cmp ../expect.grep ../actual.grep
diff --git a/third_party/git/t/t7102-reset.sh b/third_party/git/t/t7102-reset.sh
index 97be0d968d..22161b3b2d 100755
--- a/third_party/git/t/t7102-reset.sh
+++ b/third_party/git/t/t7102-reset.sh
@@ -37,17 +37,23 @@ test_expect_success 'creating initial files and commits' '
 
 	echo "2nd line 1st file" >>first &&
 	git commit -a -m "modify 1st file" &&
+	head5p2=$(git rev-parse --verify HEAD) &&
+	head5p2f=$(git rev-parse --short HEAD:first) &&
 
 	git rm first &&
 	git mv second secondfile &&
 	git commit -a -m "remove 1st and rename 2nd" &&
+	head5p1=$(git rev-parse --verify HEAD) &&
+	head5p1s=$(git rev-parse --short HEAD:secondfile) &&
 
 	echo "1st line 2nd file" >secondfile &&
 	echo "2nd line 2nd file" >>secondfile &&
 	# "git commit -m" would break MinGW, as Windows refuse to pass
 	# $test_encoding encoded parameter to git.
 	commit_msg $test_encoding | git -c "i18n.commitEncoding=$test_encoding" commit -a -F - &&
-	head5=$(git rev-parse --verify HEAD)
+	head5=$(git rev-parse --verify HEAD) &&
+	head5s=$(git rev-parse --short HEAD:secondfile) &&
+	head5sl=$(git rev-parse HEAD:secondfile)
 '
 # git log --pretty=oneline # to see those SHA1 involved
 
@@ -94,7 +100,7 @@ test_expect_success 'giving a non existing revision should fail' '
 
 test_expect_success 'reset --soft with unmerged index should fail' '
 	touch .git/MERGE_HEAD &&
-	echo "100644 44c5b5884550c17758737edcced463447b91d42b 1	un" |
+	echo "100644 $head5sl 1	un" |
 		git update-index --index-info &&
 	test_must_fail git reset --soft HEAD &&
 	rm .git/MERGE_HEAD &&
@@ -192,7 +198,7 @@ test_expect_success \
 >.diff_expect
 cat >.cached_expect <<EOF
 diff --git a/secondfile b/secondfile
-index 1bbba79..44c5b58 100644
+index $head5p1s..$head5s 100644
 --- a/secondfile
 +++ b/secondfile
 @@ -1 +1,2 @@
@@ -207,7 +213,7 @@ secondfile:
 EOF
 test_expect_success '--soft reset only should show changes in diff --cached' '
 	git reset --soft HEAD^ &&
-	check_changes d1a4bc3abce4829628ae2dcb0d60ef3d1a78b1c4 &&
+	check_changes $head5p1 &&
 	test "$(git rev-parse ORIG_HEAD)" = \
 			$head5
 '
@@ -242,7 +248,7 @@ EOF
 test_expect_success \
 	'--hard reset should change the files and undo commits permanently' '
 	git reset --hard HEAD~2 &&
-	check_changes ddaefe00f1da16864591c61fdc7adb5d7cd6b74e &&
+	check_changes $head5p2 &&
 	test "$(git rev-parse ORIG_HEAD)" = \
 			$head4
 '
@@ -251,7 +257,7 @@ test_expect_success \
 cat >.cached_expect <<EOF
 diff --git a/first b/first
 deleted file mode 100644
-index 8206c22..0000000
+index $head5p2f..0000000
 --- a/first
 +++ /dev/null
 @@ -1,2 +0,0 @@
@@ -259,14 +265,14 @@ index 8206c22..0000000
 -2nd line 1st file
 diff --git a/second b/second
 deleted file mode 100644
-index 1bbba79..0000000
+index $head5p1s..0000000
 --- a/second
 +++ /dev/null
 @@ -1 +0,0 @@
 -2nd file
 diff --git a/secondfile b/secondfile
 new file mode 100644
-index 0000000..44c5b58
+index 0000000..$head5s
 --- /dev/null
 +++ b/secondfile
 @@ -0,0 +1,2 @@
@@ -286,13 +292,13 @@ test_expect_success \
 	echo "1st line 2nd file" >secondfile &&
 	echo "2nd line 2nd file" >>secondfile &&
 	git add secondfile &&
-	check_changes ddaefe00f1da16864591c61fdc7adb5d7cd6b74e
+	check_changes $head5p2
 '
 
 cat >.diff_expect <<EOF
 diff --git a/first b/first
 deleted file mode 100644
-index 8206c22..0000000
+index $head5p2f..0000000
 --- a/first
 +++ /dev/null
 @@ -1,2 +0,0 @@
@@ -300,7 +306,7 @@ index 8206c22..0000000
 -2nd line 1st file
 diff --git a/second b/second
 deleted file mode 100644
-index 1bbba79..0000000
+index $head5p1s..0000000
 --- a/second
 +++ /dev/null
 @@ -1 +0,0 @@
@@ -314,9 +320,8 @@ secondfile:
 EOF
 test_expect_success '--mixed reset to HEAD should unadd the files' '
 	git reset &&
-	check_changes ddaefe00f1da16864591c61fdc7adb5d7cd6b74e &&
-	test "$(git rev-parse ORIG_HEAD)" = \
-			ddaefe00f1da16864591c61fdc7adb5d7cd6b74e
+	check_changes $head5p2 &&
+	test "$(git rev-parse ORIG_HEAD)" = $head5p2
 '
 
 >.diff_expect
@@ -328,7 +333,7 @@ secondfile:
 EOF
 test_expect_success 'redoing the last two commits should succeed' '
 	git add secondfile &&
-	git reset --hard ddaefe00f1da16864591c61fdc7adb5d7cd6b74e &&
+	git reset --hard $head5p2 &&
 
 	git rm first &&
 	git mv second secondfile &&
@@ -389,47 +394,55 @@ test_expect_success \
 	check_changes $head5
 '
 
-cat > expect << EOF
-diff --git a/file1 b/file1
-index d00491f..7ed6ff8 100644
---- a/file1
-+++ b/file1
-@@ -1 +1 @@
--1
-+5
-diff --git a/file2 b/file2
-deleted file mode 100644
-index 0cfbf08..0000000
---- a/file2
-+++ /dev/null
-@@ -1 +0,0 @@
--2
-EOF
-cat > cached_expect << EOF
-diff --git a/file4 b/file4
-new file mode 100644
-index 0000000..b8626c4
---- /dev/null
-+++ b/file4
-@@ -0,0 +1 @@
-+4
-EOF
 test_expect_success 'test --mixed <paths>' '
 	echo 1 > file1 &&
 	echo 2 > file2 &&
 	git add file1 file2 &&
 	test_tick &&
 	git commit -m files &&
+	before1=$(git rev-parse --short HEAD:file1) &&
+	before2=$(git rev-parse --short HEAD:file2) &&
 	git rm file2 &&
 	echo 3 > file3 &&
 	echo 4 > file4 &&
 	echo 5 > file1 &&
+	after1=$(git rev-parse --short $(git hash-object file1)) &&
+	after4=$(git rev-parse --short $(git hash-object file4)) &&
 	git add file1 file3 file4 &&
 	git reset HEAD -- file1 file2 file3 &&
 	test_must_fail git diff --quiet &&
 	git diff > output &&
+
+	cat > expect <<-EOF &&
+	diff --git a/file1 b/file1
+	index $before1..$after1 100644
+	--- a/file1
+	+++ b/file1
+	@@ -1 +1 @@
+	-1
+	+5
+	diff --git a/file2 b/file2
+	deleted file mode 100644
+	index $before2..0000000
+	--- a/file2
+	+++ /dev/null
+	@@ -1 +0,0 @@
+	-2
+	EOF
+
 	test_cmp expect output &&
 	git diff --cached > output &&
+
+	cat > cached_expect <<-EOF &&
+	diff --git a/file4 b/file4
+	new file mode 100644
+	index 0000000..$after4
+	--- /dev/null
+	+++ b/file4
+	@@ -0,0 +1 @@
+	+4
+	EOF
+
 	test_cmp cached_expect output
 '
 
diff --git a/third_party/git/t/t7105-reset-patch.sh b/third_party/git/t/t7105-reset-patch.sh
index bd10a96727..fc2a6cf5c7 100755
--- a/third_party/git/t/t7105-reset-patch.sh
+++ b/third_party/git/t/t7105-reset-patch.sh
@@ -38,6 +38,27 @@ test_expect_success PERL 'git reset -p HEAD^' '
 	test_i18ngrep "Apply" output
 '
 
+test_expect_success PERL 'git reset -p HEAD^^{tree}' '
+	test_write_lines n y | git reset -p HEAD^^{tree} >output &&
+	verify_state dir/foo work parent &&
+	verify_saved_state bar &&
+	test_i18ngrep "Apply" output
+'
+
+test_expect_success PERL 'git reset -p HEAD^:dir/foo (blob fails)' '
+	set_and_save_state dir/foo work work &&
+	test_must_fail git reset -p HEAD^:dir/foo &&
+	verify_saved_state dir/foo &&
+	verify_saved_state bar
+'
+
+test_expect_success PERL 'git reset -p aaaaaaaa (unknown fails)' '
+	set_and_save_state dir/foo work work &&
+	test_must_fail git reset -p aaaaaaaa &&
+	verify_saved_state dir/foo &&
+	verify_saved_state bar
+'
+
 # The idea in the rest is that bar sorts first, so we always say 'y'
 # first and if the path limiter fails it'll apply to bar instead of
 # dir/foo.  There's always an extra 'n' to reject edits to dir/foo in
diff --git a/third_party/git/t/t7107-reset-pathspec-file.sh b/third_party/git/t/t7107-reset-pathspec-file.sh
new file mode 100755
index 0000000000..15ccb14f7e
--- /dev/null
+++ b/third_party/git/t/t7107-reset-pathspec-file.sh
@@ -0,0 +1,178 @@
+#!/bin/sh
+
+test_description='reset --pathspec-from-file'
+
+. ./test-lib.sh
+
+test_tick
+
+test_expect_success setup '
+	echo A >fileA.t &&
+	echo B >fileB.t &&
+	echo C >fileC.t &&
+	echo D >fileD.t &&
+	git add . &&
+	git commit --include . -m "Commit" &&
+	git tag checkpoint
+'
+
+restore_checkpoint () {
+	git reset --hard checkpoint
+}
+
+verify_expect () {
+	git status --porcelain -- fileA.t fileB.t fileC.t fileD.t >actual &&
+	if test "x$1" = 'x!'
+	then
+		! test_cmp expect actual
+	else
+		test_cmp expect actual
+	fi
+}
+
+test_expect_success '--pathspec-from-file from stdin' '
+	restore_checkpoint &&
+
+	git rm fileA.t &&
+	echo fileA.t | git reset --pathspec-from-file=- &&
+
+	cat >expect <<-\EOF &&
+	 D fileA.t
+	EOF
+	verify_expect
+'
+
+test_expect_success '--pathspec-from-file from file' '
+	restore_checkpoint &&
+
+	git rm fileA.t &&
+	echo fileA.t >list &&
+	git reset --pathspec-from-file=list &&
+
+	cat >expect <<-\EOF &&
+	 D fileA.t
+	EOF
+	verify_expect
+'
+
+test_expect_success 'NUL delimiters' '
+	restore_checkpoint &&
+
+	git rm fileA.t fileB.t &&
+	printf "fileA.t\0fileB.t\0" | git reset --pathspec-from-file=- --pathspec-file-nul &&
+
+	cat >expect <<-\EOF &&
+	 D fileA.t
+	 D fileB.t
+	EOF
+	verify_expect
+'
+
+test_expect_success 'LF delimiters' '
+	restore_checkpoint &&
+
+	git rm fileA.t fileB.t &&
+	printf "fileA.t\nfileB.t\n" | git reset --pathspec-from-file=- &&
+
+	cat >expect <<-\EOF &&
+	 D fileA.t
+	 D fileB.t
+	EOF
+	verify_expect
+'
+
+test_expect_success 'no trailing delimiter' '
+	restore_checkpoint &&
+
+	git rm fileA.t fileB.t &&
+	printf "fileA.t\nfileB.t" | git reset --pathspec-from-file=- &&
+
+	cat >expect <<-\EOF &&
+	 D fileA.t
+	 D fileB.t
+	EOF
+	verify_expect
+'
+
+test_expect_success 'CRLF delimiters' '
+	restore_checkpoint &&
+
+	git rm fileA.t fileB.t &&
+	printf "fileA.t\r\nfileB.t\r\n" | git reset --pathspec-from-file=- &&
+
+	cat >expect <<-\EOF &&
+	 D fileA.t
+	 D fileB.t
+	EOF
+	verify_expect
+'
+
+test_expect_success 'quotes' '
+	restore_checkpoint &&
+
+	cat >list <<-\EOF &&
+	"file\101.t"
+	EOF
+
+	git rm fileA.t &&
+	git reset --pathspec-from-file=list &&
+
+	cat >expect <<-\EOF &&
+	 D fileA.t
+	EOF
+	verify_expect
+'
+
+test_expect_success 'quotes not compatible with --pathspec-file-nul' '
+	restore_checkpoint &&
+
+	cat >list <<-\EOF &&
+	"file\101.t"
+	EOF
+
+	# Note: "git reset" has not yet learned to fail on wrong pathspecs
+	git reset --pathspec-from-file=list --pathspec-file-nul &&
+
+	cat >expect <<-\EOF &&
+	 D fileA.t
+	EOF
+	verify_expect !
+'
+
+test_expect_success 'only touches what was listed' '
+	restore_checkpoint &&
+
+	git rm fileA.t fileB.t fileC.t fileD.t &&
+	printf "fileB.t\nfileC.t\n" | git reset --pathspec-from-file=- &&
+
+	cat >expect <<-\EOF &&
+	D  fileA.t
+	 D fileB.t
+	 D fileC.t
+	D  fileD.t
+	EOF
+	verify_expect
+'
+
+test_expect_success 'error conditions' '
+	restore_checkpoint &&
+	echo fileA.t >list &&
+	git rm fileA.t &&
+
+	test_must_fail git reset --pathspec-from-file=list --patch 2>err &&
+	test_i18ngrep -e "--pathspec-from-file is incompatible with --patch" err &&
+
+	test_must_fail git reset --pathspec-from-file=list -- fileA.t 2>err &&
+	test_i18ngrep -e "--pathspec-from-file is incompatible with pathspec arguments" err &&
+
+	test_must_fail git reset --pathspec-file-nul 2>err &&
+	test_i18ngrep -e "--pathspec-file-nul requires --pathspec-from-file" err &&
+
+	test_must_fail git reset --soft --pathspec-from-file=list 2>err &&
+	test_i18ngrep -e "fatal: Cannot do soft reset with paths" err &&
+
+	test_must_fail git reset --hard --pathspec-from-file=list 2>err &&
+	test_i18ngrep -e "fatal: Cannot do hard reset with paths" err
+'
+
+test_done
diff --git a/third_party/git/t/t7112-reset-submodule.sh b/third_party/git/t/t7112-reset-submodule.sh
index a1cb9ff858..19830d9036 100755
--- a/third_party/git/t/t7112-reset-submodule.sh
+++ b/third_party/git/t/t7112-reset-submodule.sh
@@ -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
 
@@ -13,10 +12,10 @@ test_submodule_switch_recursing_with_args "reset --keep"
 
 test_submodule_forced_switch_recursing_with_args "reset --hard"
 
-test_submodule_switch "git reset --keep"
+test_submodule_switch "reset --keep"
 
-test_submodule_switch "git reset --merge"
+test_submodule_switch "reset --merge"
 
-test_submodule_forced_switch "git reset --hard"
+test_submodule_forced_switch "reset --hard"
 
 test_done
diff --git a/third_party/git/t/t7201-co.sh b/third_party/git/t/t7201-co.sh
index b696bae5f5..4d62b9b00f 100755
--- a/third_party/git/t/t7201-co.sh
+++ b/third_party/git/t/t7201-co.sh
@@ -230,9 +230,10 @@ test_expect_success 'switch to another branch while carrying a deletion' '
 test_expect_success 'checkout to detach HEAD (with advice declined)' '
 
 	git config advice.detachedHead false &&
+	rev=$(git rev-parse --short renamer^) &&
 	git checkout -f renamer && git clean -f &&
 	git checkout renamer^ 2>messages &&
-	test_i18ngrep "HEAD is now at 7329388" messages &&
+	test_i18ngrep "HEAD is now at $rev" messages &&
 	test_line_count = 1 messages &&
 	H=$(git rev-parse --verify HEAD) &&
 	M=$(git show-ref -s --verify refs/heads/master) &&
@@ -248,9 +249,10 @@ test_expect_success 'checkout to detach HEAD (with advice declined)' '
 
 test_expect_success 'checkout to detach HEAD' '
 	git config advice.detachedHead true &&
+	rev=$(git rev-parse --short renamer^) &&
 	git checkout -f renamer && git clean -f &&
 	GIT_TEST_GETTEXT_POISON=false git checkout renamer^ 2>messages &&
-	grep "HEAD is now at 7329388" messages &&
+	grep "HEAD is now at $rev" messages &&
 	test_line_count -gt 1 messages &&
 	H=$(git rev-parse --verify HEAD) &&
 	M=$(git show-ref -s --verify refs/heads/master) &&
diff --git a/third_party/git/t/t7300-clean.sh b/third_party/git/t/t7300-clean.sh
index a2c45d1902..cb5e34d94c 100755
--- a/third_party/git/t/t7300-clean.sh
+++ b/third_party/git/t/t7300-clean.sh
@@ -117,6 +117,7 @@ test_expect_success C_LOCALE_OUTPUT 'git clean with relative prefix' '
 	would_clean=$(
 		cd docs &&
 		git clean -n ../src |
+		grep part3 |
 		sed -n -e "s|^Would remove ||p"
 	) &&
 	verbose test "$would_clean" = ../src/part3.c
@@ -129,6 +130,7 @@ test_expect_success C_LOCALE_OUTPUT 'git clean with absolute path' '
 	would_clean=$(
 		cd docs &&
 		git clean -n "$(pwd)/../src" |
+		grep part3 |
 		sed -n -e "s|^Would remove ||p"
 	) &&
 	verbose test "$would_clean" = ../src/part3.c
@@ -547,7 +549,7 @@ test_expect_failure 'nested (non-empty) bare repositories should be cleaned even
 	test_path_is_missing strange_bare
 '
 
-test_expect_success 'giving path in nested git work tree will remove it' '
+test_expect_success 'giving path in nested git work tree will NOT remove it' '
 	rm -fr repo &&
 	mkdir repo &&
 	(
@@ -559,7 +561,7 @@ test_expect_success 'giving path in nested git work tree will remove it' '
 	git clean -f -d repo/bar/baz &&
 	test_path_is_file repo/.git/HEAD &&
 	test_path_is_dir repo/bar/ &&
-	test_path_is_missing repo/bar/baz
+	test_path_is_file repo/bar/baz/hello.world
 '
 
 test_expect_success 'giving path to nested .git will not remove it' '
@@ -577,7 +579,7 @@ test_expect_success 'giving path to nested .git will not remove it' '
 	test_path_is_dir untracked/
 '
 
-test_expect_success 'giving path to nested .git/ will remove contents' '
+test_expect_success 'giving path to nested .git/ will NOT remove contents' '
 	rm -fr repo untracked &&
 	mkdir repo untracked &&
 	(
@@ -587,7 +589,7 @@ test_expect_success 'giving path to nested .git/ will remove contents' '
 	) &&
 	git clean -f -d repo/.git/ &&
 	test_path_is_dir repo/.git &&
-	test_dir_is_empty repo/.git &&
+	test_path_is_file repo/.git/HEAD &&
 	test_path_is_dir untracked/
 '
 
@@ -669,6 +671,60 @@ test_expect_success 'git clean -d skips untracked dirs containing ignored files'
 	test_path_is_missing foo/b/bb
 '
 
+test_expect_success 'git clean -d skips nested repo containing ignored files' '
+	test_when_finished "rm -rf nested-repo-with-ignored-file" &&
+
+	git init nested-repo-with-ignored-file &&
+	(
+		cd nested-repo-with-ignored-file &&
+		>file &&
+		git add file &&
+		git commit -m Initial &&
+
+		# This file is ignored by a .gitignore rule in the outer repo
+		# added in the previous test.
+		>ignoreme
+	) &&
+
+	git clean -fd &&
+
+	test_path_is_file nested-repo-with-ignored-file/.git/index &&
+	test_path_is_file nested-repo-with-ignored-file/ignoreme &&
+	test_path_is_file nested-repo-with-ignored-file/file
+'
+
+test_expect_success 'git clean handles being told what to clean' '
+	mkdir -p d1 d2 &&
+	touch d1/ut d2/ut &&
+	git clean -f */ut &&
+	test_path_is_missing d1/ut &&
+	test_path_is_missing d2/ut
+'
+
+test_expect_success 'git clean handles being told what to clean, with -d' '
+	mkdir -p d1 d2 &&
+	touch d1/ut d2/ut &&
+	git clean -ffd */ut &&
+	test_path_is_missing d1/ut &&
+	test_path_is_missing d2/ut
+'
+
+test_expect_success 'git clean works if a glob is passed without -d' '
+	mkdir -p d1 d2 &&
+	touch d1/ut d2/ut &&
+	git clean -f "*ut" &&
+	test_path_is_missing d1/ut &&
+	test_path_is_missing d2/ut
+'
+
+test_expect_success 'git clean works if a glob is passed with -d' '
+	mkdir -p d1 d2 &&
+	touch d1/ut d2/ut &&
+	git clean -ffd "*ut" &&
+	test_path_is_missing d1/ut &&
+	test_path_is_missing d2/ut
+'
+
 test_expect_success MINGW 'handle clean & core.longpaths = false nicely' '
 	test_config core.longpaths false &&
 	a50=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa &&
@@ -681,4 +737,13 @@ test_expect_success MINGW 'handle clean & core.longpaths = false nicely' '
 	test_i18ngrep "too long" .git/err
 '
 
+test_expect_success 'clean untracked paths by pathspec' '
+	git init untracked &&
+	mkdir untracked/dir &&
+	echo >untracked/dir/file.txt &&
+	git -C untracked clean -f dir/file.txt &&
+	ls untracked/dir >actual &&
+	test_must_be_empty actual
+'
+
 test_done
diff --git a/third_party/git/t/t7400-submodule-basic.sh b/third_party/git/t/t7400-submodule-basic.sh
index a208cb26e1..fec7e0299d 100755
--- a/third_party/git/t/t7400-submodule-basic.sh
+++ b/third_party/git/t/t7400-submodule-basic.sh
@@ -55,6 +55,21 @@ test_expect_success 'add aborts on repository with no commits' '
 	test_i18ncmp expect actual
 '
 
+test_expect_success 'status should ignore inner git repo when not added' '
+	rm -fr inner &&
+	mkdir inner &&
+	(
+		cd inner &&
+		git init &&
+		>t &&
+		git add t &&
+		git commit -m "initial"
+	) &&
+	test_must_fail git submodule status inner 2>output.err &&
+	rm -fr inner &&
+	test_i18ngrep "^error: .*did not match any file(s) known to git" output.err
+'
+
 test_expect_success 'setup - repository in init subdirectory' '
 	mkdir init &&
 	(
@@ -156,9 +171,11 @@ test_expect_success 'submodule add to .gitignored path fails' '
 	(
 		cd addtest-ignore &&
 		cat <<-\EOF >expect &&
-		The following path is ignored by one of your .gitignore files:
+		The following paths are ignored by one of your .gitignore files:
 		submod
-		Use -f if you really want to add it.
+		hint: Use -f if you really want to add them.
+		hint: Turn this message off by running
+		hint: "git config advice.addIgnoredFile false"
 		EOF
 		# Does not use test_commit due to the ignore
 		echo "*" > .gitignore &&
@@ -191,6 +208,17 @@ test_expect_success 'submodule add to reconfigure existing submodule with --forc
 	)
 '
 
+test_expect_success 'submodule add relays add --dry-run stderr' '
+	test_when_finished "rm -rf addtest/.git/index.lock" &&
+	(
+		cd addtest &&
+		: >.git/index.lock &&
+		! git submodule add "$submodurl" sub-while-locked 2>output.err &&
+		test_i18ngrep "^fatal: .*index\.lock" output.err &&
+		test_path_is_missing sub-while-locked
+	)
+'
+
 test_expect_success 'submodule add --branch' '
 	echo "refs/heads/initial" >expect-head &&
 	cat <<-\EOF >expect-heads &&
@@ -356,6 +384,28 @@ test_expect_success 'status should only print one line' '
 	test_line_count = 1 lines
 '
 
+test_expect_success 'status from subdirectory should have the same SHA1' '
+	test_when_finished "rmdir addtest/subdir" &&
+	(
+		cd addtest &&
+		mkdir subdir &&
+		git submodule status >output &&
+		awk "{print \$1}" <output >expect &&
+		cd subdir &&
+		git submodule status >../output &&
+		awk "{print \$1}" <../output >../actual &&
+		test_cmp ../expect ../actual &&
+		git -C ../submod checkout HEAD^ &&
+		git submodule status >../output &&
+		awk "{print \$1}" <../output >../actual2 &&
+		cd .. &&
+		git submodule status >output &&
+		awk "{print \$1}" <output >expect2 &&
+		test_cmp expect2 actual2 &&
+		! test_cmp actual actual2
+	)
+'
+
 test_expect_success 'setup - fetch commit name from submodule' '
 	rev1=$(cd .subrepo && git rev-parse HEAD) &&
 	printf "rev1: %s\n" "$rev1" &&
@@ -377,6 +427,14 @@ test_expect_success 'init should register submodule url in .git/config' '
 	test_cmp expect url
 '
 
+test_expect_success 'status should still be "missing" after initializing' '
+	rm -fr init &&
+	mkdir init &&
+	git submodule status >lines &&
+	rm -fr init &&
+	grep "^-$rev1" lines
+'
+
 test_failure_with_unknown_submodule () {
 	test_must_fail git submodule $1 no-such-submodule 2>output.err &&
 	test_i18ngrep "^error: .*no-such-submodule" output.err
@@ -527,7 +585,6 @@ test_expect_success 'update --init' '
 	test_must_fail git config submodule.example.url &&
 
 	git submodule update init 2> update.out &&
-	cat update.out &&
 	test_i18ngrep "not initialized" update.out &&
 	test_must_fail git rev-parse --resolve-git-dir init/.git &&
 
@@ -545,7 +602,6 @@ test_expect_success 'update --init from subdirectory' '
 	(
 		cd sub &&
 		git submodule update ../init 2>update.out &&
-		cat update.out &&
 		test_i18ngrep "not initialized" update.out &&
 		test_must_fail git rev-parse --resolve-git-dir ../init/.git &&
 
@@ -882,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 &&
@@ -954,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 &&
@@ -1175,7 +1231,7 @@ test_expect_success 'submodule helper list is not confused by common prefixes' '
 	git submodule add /dir1/b dir1/b &&
 	git submodule add /dir2/b dir2/b &&
 	git commit -m "first submodule commit" &&
-	git submodule--helper list dir1/b |cut -c51- >actual &&
+	git submodule--helper list dir1/b | cut -f 2 >actual &&
 	echo "dir1/b" >expect &&
 	test_cmp expect actual
 '
@@ -1204,7 +1260,7 @@ test_expect_success 'submodule update --init with a specification' '
 	pwd=$(pwd) &&
 	git clone file://"$pwd"/multisuper multisuper_clone &&
 	git -C multisuper_clone submodule update --init . ":(exclude)sub0" &&
-	git -C multisuper_clone submodule status |cut -c 1,43- >actual &&
+	git -C multisuper_clone submodule status | sed "s/$OID_REGEX //" >actual &&
 	test_cmp expect actual
 '
 
@@ -1215,7 +1271,7 @@ test_expect_success 'submodule update --init with submodule.active set' '
 	git -C multisuper_clone config submodule.active "." &&
 	git -C multisuper_clone config --add submodule.active ":(exclude)sub0" &&
 	git -C multisuper_clone submodule update --init &&
-	git -C multisuper_clone submodule status |cut -c 1,43- >actual &&
+	git -C multisuper_clone submodule status | sed "s/$OID_REGEX //" >actual &&
 	test_cmp expect actual
 '
 
@@ -1234,7 +1290,7 @@ test_expect_success 'submodule update and setting submodule.<name>.active' '
 	-sub3
 	EOF
 	git -C multisuper_clone submodule update &&
-	git -C multisuper_clone submodule status |cut -c 1,43- >actual &&
+	git -C multisuper_clone submodule status | sed "s/$OID_REGEX //" >actual &&
 	test_cmp expect actual
 '
 
@@ -1251,12 +1307,12 @@ test_expect_success 'clone active submodule without submodule url set' '
 		git submodule update &&
 		git submodule status >actual_raw &&
 
-		cut -c 1,43- actual_raw >actual &&
+		cut -d" " -f3- actual_raw >actual &&
 		cat >expect <<-\EOF &&
-		 sub0 (test2)
-		 sub1 (test2)
-		 sub2 (test2)
-		 sub3 (test2)
+		sub0 (test2)
+		sub1 (test2)
+		sub2 (test2)
+		sub3 (test2)
 		EOF
 		test_cmp expect actual
 	)
@@ -1272,7 +1328,7 @@ test_expect_success 'clone --recurse-submodules with a pathspec works' '
 	EOF
 
 	git clone --recurse-submodules="sub0" multisuper multisuper_clone &&
-	git -C multisuper_clone submodule status |cut -c1,43- >actual &&
+	git -C multisuper_clone submodule status | sed "s/$OID_REGEX //" >actual &&
 	test_cmp expected actual
 '
 
@@ -1289,7 +1345,7 @@ test_expect_success 'clone with multiple --recurse-submodules options' '
 		  --recurse-submodules=":(exclude)sub0" \
 		  --recurse-submodules=":(exclude)sub2" \
 		  multisuper multisuper_clone &&
-	git -C multisuper_clone submodule status |cut -c1,43- >actual &&
+	git -C multisuper_clone submodule status | sed "s/$OID_REGEX //" >actual &&
 	test_cmp expect actual
 '
 
@@ -1317,7 +1373,7 @@ test_expect_success 'clone and subsequent updates correctly auto-initialize subm
 		  --recurse-submodules=":(exclude)sub4" \
 		  multisuper multisuper_clone &&
 
-	git -C multisuper_clone submodule status |cut -c1,43- >actual &&
+	git -C multisuper_clone submodule status | sed "s/$OID_REGEX //" >actual &&
 	test_cmp expect actual &&
 
 	git -C multisuper submodule add ../sub1 sub4 &&
@@ -1326,7 +1382,7 @@ test_expect_success 'clone and subsequent updates correctly auto-initialize subm
 	# obtain the new superproject
 	git -C multisuper_clone pull &&
 	git -C multisuper_clone submodule update --init &&
-	git -C multisuper_clone submodule status |cut -c1,43- >actual &&
+	git -C multisuper_clone submodule status | sed "s/$OID_REGEX //" >actual &&
 	test_cmp expect2 actual
 '
 
diff --git a/third_party/git/t/t7401-submodule-summary.sh b/third_party/git/t/t7401-submodule-summary.sh
index 9bc841d085..7608814708 100755
--- a/third_party/git/t/t7401-submodule-summary.sh
+++ b/third_party/git/t/t7401-submodule-summary.sh
@@ -5,9 +5,18 @@
 
 test_description='Summary support for submodules
 
-This test tries to verify the sanity of summary subcommand of git submodule.
+This test script tries to verify the sanity of summary subcommand of git submodule.
 '
 
+# NOTE: This test script uses 'git add' instead of 'git submodule add' to add
+# submodules to the superproject. Some submodule subcommands such as init and
+# deinit might not work as expected in this script. t7421 does not have this
+# caveat.
+#
+# NEEDSWORK: This test script is old fashioned and may need a big cleanup due to
+# various reasons, one of them being that there are lots of commands taking place
+# outside of 'test_expect_success' block, which is no longer in good-style.
+
 . ./test-lib.sh
 
 add_file () {
@@ -16,12 +25,12 @@ add_file () {
 	owd=$(pwd)
 	cd "$sm"
 	for name; do
-		echo "$name" > "$name" &&
+		echo "$name" >"$name" &&
 		git add "$name" &&
 		test_tick &&
 		git commit -m "Add $name"
 	done >/dev/null
-	git rev-parse --verify HEAD | cut -c1-7
+	git rev-parse --short HEAD
 	cd "$owd"
 }
 commit_file () {
@@ -38,10 +47,10 @@ test_expect_success 'added submodule' "
 	git add sm1 &&
 	git submodule summary >actual &&
 	cat >expected <<-EOF &&
-* sm1 0000000...$head1 (2):
-  > Add foo2
+	* sm1 0000000...$head1 (2):
+	  > Add foo2
 
-EOF
+	EOF
 	test_cmp expected actual
 "
 
@@ -52,10 +61,10 @@ test_expect_success 'added submodule (subdirectory)' "
 		git submodule summary >../actual
 	) &&
 	cat >expected <<-EOF &&
-* ../sm1 0000000...$head1 (2):
-  > Add foo2
+	* ../sm1 0000000...$head1 (2):
+	  > Add foo2
 
-EOF
+	EOF
 	test_cmp expected actual
 "
 
@@ -73,10 +82,10 @@ test_expect_success 'added submodule (subdirectory with explicit path)' "
 		git submodule summary ../sm1 >../actual
 	) &&
 	cat >expected <<-EOF &&
-* ../sm1 0000000...$head1 (2):
-  > Add foo2
+	* ../sm1 0000000...$head1 (2):
+	  > Add foo2
 
-EOF
+	EOF
 	test_cmp expected actual
 "
 
@@ -86,20 +95,20 @@ head2=$(add_file sm1 foo3)
 test_expect_success 'modified submodule(forward)' "
 	git submodule summary >actual &&
 	cat >expected <<-EOF &&
-* sm1 $head1...$head2 (1):
-  > Add foo3
+	* sm1 $head1...$head2 (1):
+	  > Add foo3
 
-EOF
+	EOF
 	test_cmp expected actual
 "
 
 test_expect_success 'modified submodule(forward), --files' "
 	git submodule summary --files >actual &&
 	cat >expected <<-EOF &&
-* sm1 $head1...$head2 (1):
-  > Add foo3
+	* sm1 $head1...$head2 (1):
+	  > Add foo3
 
-EOF
+	EOF
 	test_cmp expected actual
 "
 
@@ -110,10 +119,10 @@ test_expect_success 'no ignore=all setting has any effect' "
 	git config diff.ignoreSubmodules all &&
 	git submodule summary >actual &&
 	cat >expected <<-EOF &&
-* sm1 $head1...$head2 (1):
-  > Add foo3
+	* sm1 $head1...$head2 (1):
+	  > Add foo3
 
-EOF
+	EOF
 	test_cmp expected actual &&
 	git config --unset diff.ignoreSubmodules &&
 	git config --remove-section submodule.sm1 &&
@@ -125,17 +134,17 @@ commit_file sm1 &&
 head3=$(
 	cd sm1 &&
 	git reset --hard HEAD~2 >/dev/null &&
-	git rev-parse --verify HEAD | cut -c1-7
+	git rev-parse --short HEAD
 )
 
 test_expect_success 'modified submodule(backward)' "
 	git submodule summary >actual &&
 	cat >expected <<-EOF &&
-* sm1 $head2...$head3 (2):
-  < Add foo3
-  < Add foo2
+	* sm1 $head2...$head3 (2):
+	  < Add foo3
+	  < Add foo2
 
-EOF
+	EOF
 	test_cmp expected actual
 "
 
@@ -144,25 +153,25 @@ head4_full=$(GIT_DIR=sm1/.git git rev-parse --verify HEAD)
 test_expect_success 'modified submodule(backward and forward)' "
 	git submodule summary >actual &&
 	cat >expected <<-EOF &&
-* sm1 $head2...$head4 (4):
-  > Add foo5
-  > Add foo4
-  < Add foo3
-  < Add foo2
+	* sm1 $head2...$head4 (4):
+	  > Add foo5
+	  > Add foo4
+	  < Add foo3
+	  < Add foo2
 
-EOF
+	EOF
 	test_cmp expected actual
 "
 
 test_expect_success '--summary-limit' "
 	git submodule summary -n 3 >actual &&
 	cat >expected <<-EOF &&
-* sm1 $head2...$head4 (4):
-  > Add foo5
-  > Add foo4
-  < Add foo3
+	* sm1 $head2...$head4 (4):
+	  > Add foo5
+	  > Add foo4
+	  < Add foo3
 
-EOF
+	EOF
 	test_cmp expected actual
 "
 
@@ -177,21 +186,21 @@ mv sm1-bak sm1
 test_expect_success 'typechanged submodule(submodule->blob), --cached' "
 	git submodule summary --cached >actual &&
 	cat >expected <<-EOF &&
-* sm1 $head4(submodule)->$head5(blob) (3):
-  < Add foo5
+	* sm1 $head4(submodule)->$head5(blob) (3):
+	  < Add foo5
 
-EOF
-	test_i18ncmp actual expected
+	EOF
+	test_i18ncmp expected actual
 "
 
 test_expect_success 'typechanged submodule(submodule->blob), --files' "
 	git submodule summary --files >actual &&
 	cat >expected <<-EOF &&
-* sm1 $head5(blob)->$head4(submodule) (3):
-  > Add foo5
+	* sm1 $head5(blob)->$head4(submodule) (3):
+	  > Add foo5
 
-EOF
-	test_i18ncmp actual expected
+	EOF
+	test_i18ncmp expected actual
 "
 
 rm -rf sm1 &&
@@ -199,10 +208,10 @@ git checkout-index sm1
 test_expect_success 'typechanged submodule(submodule->blob)' "
 	git submodule summary >actual &&
 	cat >expected <<-EOF &&
-* sm1 $head4(submodule)->$head5(blob):
+	* sm1 $head4(submodule)->$head5(blob):
 
-EOF
-	test_i18ncmp actual expected
+	EOF
+	test_i18ncmp expected actual
 "
 
 rm -f sm1 &&
@@ -211,21 +220,21 @@ head6=$(add_file sm1 foo6 foo7)
 test_expect_success 'nonexistent commit' "
 	git submodule summary >actual &&
 	cat >expected <<-EOF &&
-* sm1 $head4...$head6:
-  Warn: sm1 doesn't contain commit $head4_full
+	* sm1 $head4...$head6:
+	  Warn: sm1 doesn't contain commit $head4_full
 
-EOF
-	test_i18ncmp actual expected
+	EOF
+	test_i18ncmp expected actual
 "
 
 commit_file
 test_expect_success 'typechanged submodule(blob->submodule)' "
 	git submodule summary >actual &&
 	cat >expected <<-EOF &&
-* sm1 $head5(blob)->$head6(submodule) (2):
-  > Add foo7
+	* sm1 $head5(blob)->$head6(submodule) (2):
+	  > Add foo7
 
-EOF
+	EOF
 	test_i18ncmp expected actual
 "
 
@@ -234,9 +243,9 @@ rm -rf sm1
 test_expect_success 'deleted submodule' "
 	git submodule summary >actual &&
 	cat >expected <<-EOF &&
-* sm1 $head6...0000000:
+	* sm1 $head6...0000000:
 
-EOF
+	EOF
 	test_cmp expected actual
 "
 
@@ -249,22 +258,22 @@ test_expect_success 'create second submodule' '
 test_expect_success 'multiple submodules' "
 	git submodule summary >actual &&
 	cat >expected <<-EOF &&
-* sm1 $head6...0000000:
+	* sm1 $head6...0000000:
 
-* sm2 0000000...$head7 (2):
-  > Add foo9
+	* sm2 0000000...$head7 (2):
+	  > Add foo9
 
-EOF
+	EOF
 	test_cmp expected actual
 "
 
 test_expect_success 'path filter' "
 	git submodule summary sm2 >actual &&
 	cat >expected <<-EOF &&
-* sm2 0000000...$head7 (2):
-  > Add foo9
+	* sm2 0000000...$head7 (2):
+	  > Add foo9
 
-EOF
+	EOF
 	test_cmp expected actual
 "
 
@@ -272,24 +281,24 @@ commit_file sm2
 test_expect_success 'given commit' "
 	git submodule summary HEAD^ >actual &&
 	cat >expected <<-EOF &&
-* sm1 $head6...0000000:
+	* sm1 $head6...0000000:
 
-* sm2 0000000...$head7 (2):
-  > Add foo9
+	* sm2 0000000...$head7 (2):
+	  > Add foo9
 
-EOF
+	EOF
 	test_cmp expected actual
 "
 
 test_expect_success '--for-status' "
 	git submodule summary --for-status HEAD^ >actual &&
-	test_i18ncmp actual - <<EOF
-* sm1 $head6...0000000:
+	test_i18ncmp - actual <<-EOF
+	* sm1 $head6...0000000:
 
-* sm2 0000000...$head7 (2):
-  > Add foo9
+	* sm2 0000000...$head7 (2):
+	  > Add foo9
 
-EOF
+	EOF
 "
 
 test_expect_success 'fail when using --files together with --cached' "
diff --git a/third_party/git/t/t7406-submodule-update.sh b/third_party/git/t/t7406-submodule-update.sh
index c973278300..acb8766ac2 100755
--- a/third_party/git/t/t7406-submodule-update.sh
+++ b/third_party/git/t/t7406-submodule-update.sh
@@ -70,6 +70,22 @@ test_expect_success 'setup a submodule tree' '
 	)
 '
 
+test_expect_success 'update --remote falls back to using HEAD' '
+	test_create_repo main-branch-submodule &&
+	test_commit -C main-branch-submodule initial &&
+
+	test_create_repo main-branch &&
+	git -C main-branch submodule add ../main-branch-submodule &&
+	git -C main-branch commit -m add-submodule &&
+
+	git -C main-branch-submodule switch -c hello &&
+	test_commit -C main-branch-submodule world &&
+
+	git clone --recursive main-branch main-branch-clone &&
+	git -C main-branch-clone submodule update --remote main-branch-submodule &&
+	test_path_exists main-branch-clone/main-branch-submodule/world.t
+'
+
 test_expect_success 'submodule update detaching the HEAD ' '
 	(cd super/submodule &&
 	 git reset --hard HEAD~1
@@ -158,7 +174,6 @@ test_expect_success 'submodule update --init from and of subdirectory' '
 	test_i18ncmp expect2 actual2
 '
 
-apos="'";
 test_expect_success 'submodule update does not fetch already present commits' '
 	(cd submodule &&
 	  echo line3 >> file &&
@@ -168,7 +183,7 @@ test_expect_success 'submodule update does not fetch already present commits' '
 	) &&
 	(cd super/submodule &&
 	  head=$(git rev-parse --verify HEAD) &&
-	  echo "Submodule path ${apos}submodule$apos: checked out $apos$head$apos" > ../../expected &&
+	  echo "Submodule path ${SQ}submodule$SQ: checked out $SQ$head$SQ" > ../../expected &&
 	  git reset --hard HEAD~1
 	) &&
 	(cd super &&
@@ -407,12 +422,26 @@ test_expect_success 'submodule update - command in .git/config' '
 	)
 '
 
-test_expect_success 'submodule update - command in .gitmodules is ignored' '
+test_expect_success 'submodule update - command in .gitmodules is rejected' '
 	test_when_finished "git -C super reset --hard HEAD^" &&
 	git -C super config -f .gitmodules submodule.submodule.update "!false" &&
 	git -C super commit -a -m "add command to .gitmodules file" &&
 	git -C super/submodule reset --hard $submodulesha1^ &&
-	git -C super submodule update submodule
+	test_must_fail git -C super submodule update submodule
+'
+
+test_expect_success 'fsck detects command in .gitmodules' '
+	git init command-in-gitmodules &&
+	(
+		cd command-in-gitmodules &&
+		git submodule add ../submodule submodule &&
+		test_commit adding-submodule &&
+
+		git config -f .gitmodules submodule.submodule.update "!false" &&
+		git add .gitmodules &&
+		test_commit configuring-update &&
+		test_must_fail git fsck
+	)
 '
 
 cat << EOF >expect
@@ -481,6 +510,9 @@ test_expect_success 'recursive submodule update - command in .git/config catches
 '
 
 test_expect_success 'submodule init does not copy command into .git/config' '
+	test_when_finished "git -C super update-index --force-remove submodule1" &&
+	test_when_finished git config -f super/.gitmodules \
+		--remove-section submodule.submodule1 &&
 	(cd super &&
 	 git ls-files -s submodule >out &&
 	 H=$(cut -d" " -f2 out) &&
@@ -489,10 +521,9 @@ test_expect_success 'submodule init does not copy command into .git/config' '
 	 git config -f .gitmodules submodule.submodule1.path submodule1 &&
 	 git config -f .gitmodules submodule.submodule1.url ../submodule &&
 	 git config -f .gitmodules submodule.submodule1.update !false &&
-	 git submodule init submodule1 &&
-	 echo "none" >expect &&
-	 git config submodule.submodule1.update >actual &&
-	 test_cmp expect actual
+	 test_must_fail git submodule init submodule1 &&
+	 test_expect_code 1 git config submodule.submodule1.update >actual &&
+	 test_must_be_empty actual
 	)
 '
 
@@ -945,7 +976,7 @@ test_expect_success 'submodule update clone shallow submodule outside of depth'
 		mv -f .gitmodules.tmp .gitmodules &&
 		# Some protocol versions (e.g. 2) support fetching
 		# unadvertised objects, so restrict this test to v0.
-		test_must_fail env GIT_TEST_PROTOCOL_VERSION= \
+		test_must_fail env GIT_TEST_PROTOCOL_VERSION=0 \
 			git submodule update --init --depth=1 2>actual &&
 		test_i18ngrep "Direct fetching of that commit failed." actual &&
 		git -C ../submodule config uploadpack.allowReachableSHA1InWant true &&
@@ -991,4 +1022,16 @@ test_expect_success 'git clone passes the parallel jobs config on to submodules'
 	rm -rf super4
 '
 
+test_expect_success 'submodule update --quiet passes quietness to merge/rebase' '
+	(cd super &&
+	 test_commit -C rebasing message &&
+	 git submodule update --rebase --quiet >out 2>err &&
+	 test_must_be_empty out &&
+	 test_must_be_empty err &&
+	 git submodule update --rebase -v >out 2>err &&
+	 test_file_not_empty out &&
+	 test_must_be_empty err
+	)
+'
+
 test_done
diff --git a/third_party/git/t/t7408-submodule-reference.sh b/third_party/git/t/t7408-submodule-reference.sh
index 34ac28c056..a3892f494b 100755
--- a/third_party/git/t/t7408-submodule-reference.sh
+++ b/third_party/git/t/t7408-submodule-reference.sh
@@ -122,8 +122,8 @@ test_expect_success 'missing submodule alternate fails clone and submodule updat
 		# update of the submodule succeeds
 		test_must_fail git submodule update --init &&
 		# and we have no alternates:
-		test_must_fail test_alternate_is_used .git/modules/sub/objects/info/alternates sub &&
-		test_must_fail test_path_is_file sub/file1
+		test_path_is_missing .git/modules/sub/objects/info/alternates &&
+		test_path_is_missing sub/file1
 	)
 '
 
@@ -137,7 +137,7 @@ test_expect_success 'ignoring missing submodule alternates passes clone and subm
 		# update of the submodule succeeds
 		git submodule update --init &&
 		# and we have no alternates:
-		test_must_fail test_alternate_is_used .git/modules/sub/objects/info/alternates sub &&
+		test_path_is_missing .git/modules/sub/objects/info/alternates &&
 		test_path_is_file sub/file1
 	)
 '
@@ -182,7 +182,7 @@ check_that_two_of_three_alternates_are_used() {
 	# immediate submodule has alternate:
 	test_alternate_is_used .git/modules/subwithsub/objects/info/alternates subwithsub &&
 	# but nested submodule has no alternate:
-	test_must_fail test_alternate_is_used .git/modules/subwithsub/modules/sub/objects/info/alternates subwithsub/sub
+	test_path_is_missing .git/modules/subwithsub/modules/sub/objects/info/alternates
 }
 
 
diff --git a/third_party/git/t/t7410-submodule-checkout-to.sh b/third_party/git/t/t7410-submodule-checkout-to.sh
deleted file mode 100755
index f1b492ebc4..0000000000
--- a/third_party/git/t/t7410-submodule-checkout-to.sh
+++ /dev/null
@@ -1,77 +0,0 @@
-#!/bin/sh
-
-test_description='Combination of submodules and multiple workdirs'
-
-. ./test-lib.sh
-
-base_path=$(pwd -P)
-
-test_expect_success 'setup: make origin'  '
-	mkdir -p origin/sub &&
-	(
-		cd origin/sub && git init &&
-		echo file1 >file1 &&
-		git add file1 &&
-		git commit -m file1
-	) &&
-	mkdir -p origin/main &&
-	(
-		cd origin/main && git init &&
-		git submodule add ../sub &&
-		git commit -m "add sub"
-	) &&
-	(
-		cd origin/sub &&
-		echo file1updated >file1 &&
-		git add file1 &&
-		git commit -m "file1 updated"
-	) &&
-	git -C origin/main/sub pull &&
-	(
-		cd origin/main &&
-		git add sub &&
-		git commit -m "sub updated"
-	)
-'
-
-test_expect_success 'setup: clone' '
-	mkdir clone &&
-	git -C clone clone --recursive "$base_path/origin/main"
-'
-
-rev1_hash_main=$(git --git-dir=origin/main/.git show --pretty=format:%h -q "HEAD~1")
-rev1_hash_sub=$(git --git-dir=origin/sub/.git show --pretty=format:%h -q "HEAD~1")
-
-test_expect_success 'checkout main' '
-	mkdir default_checkout &&
-	git -C clone/main worktree add "$base_path/default_checkout/main" "$rev1_hash_main"
-'
-
-test_expect_failure 'can see submodule diffs just after checkout' '
-	git -C default_checkout/main diff --submodule master"^!" >out &&
-	grep "file1 updated" out
-'
-
-test_expect_success 'checkout main and initialize independent clones' '
-	mkdir fully_cloned_submodule &&
-	git -C clone/main worktree add "$base_path/fully_cloned_submodule/main" "$rev1_hash_main" &&
-	git -C fully_cloned_submodule/main submodule update
-'
-
-test_expect_success 'can see submodule diffs after independent cloning' '
-	git -C fully_cloned_submodule/main diff --submodule master"^!" >out &&
-	grep "file1 updated" out
-'
-
-test_expect_success 'checkout sub manually' '
-	mkdir linked_submodule &&
-	git -C clone/main worktree add "$base_path/linked_submodule/main" "$rev1_hash_main" &&
-	git -C clone/main/sub worktree add "$base_path/linked_submodule/main/sub" "$rev1_hash_sub"
-'
-
-test_expect_success 'can see submodule diffs after manual checkout of linked submodule' '
-	git -C linked_submodule/main diff --submodule master"^!" >out &&
-	grep "file1 updated" out
-'
-
-test_done
diff --git a/third_party/git/t/t7415-submodule-names.sh b/third_party/git/t/t7415-submodule-names.sh
index 49a37efe9c..f70368bc2e 100755
--- a/third_party/git/t/t7415-submodule-names.sh
+++ b/third_party/git/t/t7415-submodule-names.sh
@@ -191,4 +191,61 @@ test_expect_success 'fsck detects corrupt .gitmodules' '
 	)
 '
 
+test_expect_success MINGW 'prevent git~1 squatting on Windows' '
+	git init squatting &&
+	(
+		cd squatting &&
+		mkdir a &&
+		touch a/..git &&
+		git add a/..git &&
+		test_tick &&
+		git commit -m initial &&
+
+		modules="$(test_write_lines \
+			"[submodule \"b.\"]" "url = ." "path = c" \
+			"[submodule \"b\"]" "url = ." "path = d\\\\a" |
+			git hash-object -w --stdin)" &&
+		rev="$(git rev-parse --verify HEAD)" &&
+		hash="$(echo x | git hash-object -w --stdin)" &&
+		test_must_fail git update-index --add \
+			--cacheinfo 160000,$rev,d\\a 2>err &&
+		test_i18ngrep "Invalid path" err &&
+		git -c core.protectNTFS=false update-index --add \
+			--cacheinfo 100644,$modules,.gitmodules \
+			--cacheinfo 160000,$rev,c \
+			--cacheinfo 160000,$rev,d\\a \
+			--cacheinfo 100644,$hash,d./a/x \
+			--cacheinfo 100644,$hash,d./a/..git &&
+		test_tick &&
+		git -c core.protectNTFS=false commit -m "module"
+	) &&
+	test_must_fail git -c core.protectNTFS=false \
+		clone --recurse-submodules squatting squatting-clone 2>err &&
+	test_i18ngrep -e "directory not empty" -e "not an empty directory" err &&
+	! grep gitdir squatting-clone/d/a/git~2
+'
+
+test_expect_success 'git dirs of sibling submodules must not be nested' '
+	git init nested &&
+	test_commit -C nested nested &&
+	(
+		cd nested &&
+		cat >.gitmodules <<-EOF &&
+		[submodule "hippo"]
+			url = .
+			path = thing1
+		[submodule "hippo/hooks"]
+			url = .
+			path = thing2
+		EOF
+		git clone . thing1 &&
+		git clone . thing2 &&
+		git add .gitmodules thing1 thing2 &&
+		test_tick &&
+		git commit -m nested
+	) &&
+	test_must_fail git clone --recurse-submodules nested clone 2>err &&
+	test_i18ngrep "is inside git dir" err
+'
+
 test_done
diff --git a/third_party/git/t/t7416-submodule-dash-url.sh b/third_party/git/t/t7416-submodule-dash-url.sh
index 1cd2c1c1ea..eec96e0ba9 100755
--- a/third_party/git/t/t7416-submodule-dash-url.sh
+++ b/third_party/git/t/t7416-submodule-dash-url.sh
@@ -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' '
@@ -46,4 +46,159 @@ test_expect_success 'fsck rejects unprotected dash' '
 	grep gitmodulesUrl err
 '
 
+test_expect_success 'trailing backslash is handled correctly' '
+	git init testmodule &&
+	test_commit -C testmodule c &&
+	git submodule add ./testmodule &&
+	: ensure that the name ends in a double backslash &&
+	sed -e "s|\\(submodule \"testmodule\\)\"|\\1\\\\\\\\\"|" \
+		-e "s|url = .*|url = \" --should-not-be-an-option\"|" \
+		<.gitmodules >.new &&
+	mv .new .gitmodules &&
+	git commit -am "Add testmodule" &&
+	test_must_fail git clone --verbose --recurse-submodules . dolly 2>err &&
+	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
diff --git a/third_party/git/t/t7417-submodule-path-url.sh b/third_party/git/t/t7417-submodule-path-url.sh
index 756af8c4d6..f7e7e94d7b 100755
--- a/third_party/git/t/t7417-submodule-path-url.sh
+++ b/third_party/git/t/t7417-submodule-path-url.sh
@@ -25,4 +25,21 @@ test_expect_success 'fsck rejects unprotected dash' '
 	grep gitmodulesPath err
 '
 
+test_expect_success MINGW 'submodule paths disallows trailing spaces' '
+	git init super &&
+	test_must_fail git -C super submodule add ../upstream "sub " &&
+
+	: add "sub", then rename "sub" to "sub ", the hard way &&
+	git -C super submodule add ../upstream sub &&
+	tree=$(git -C super write-tree) &&
+	git -C super ls-tree $tree >tree &&
+	sed "s/sub/sub /" <tree >tree.new &&
+	tree=$(git -C super mktree <tree.new) &&
+	commit=$(echo with space | git -C super commit-tree $tree) &&
+	git -C super update-ref refs/heads/master $commit &&
+
+	test_must_fail git clone --recurse-submodules super dst 2>err &&
+	test_i18ngrep "sub " err
+'
+
 test_done
diff --git a/third_party/git/t/t7419-submodule-set-branch.sh b/third_party/git/t/t7419-submodule-set-branch.sh
index c4b370ea85..3b925c302f 100755
--- a/third_party/git/t/t7419-submodule-set-branch.sh
+++ b/third_party/git/t/t7419-submodule-set-branch.sh
@@ -34,7 +34,7 @@ test_expect_success 'submodule config cache setup' '
 
 test_expect_success 'ensure submodule branch is unset' '
 	(cd super &&
-		test_must_fail grep branch .gitmodules
+		! grep branch .gitmodules
 	)
 '
 
@@ -52,12 +52,13 @@ test_expect_success 'test submodule set-branch --branch' '
 '
 
 test_expect_success 'test submodule set-branch --default' '
+	test_commit -C submodule c &&
 	(cd super &&
 		git submodule set-branch --default submodule &&
-		test_must_fail grep branch .gitmodules &&
+		! grep branch .gitmodules &&
 		git submodule update --remote &&
 		cat <<-\EOF >expect &&
-		a
+		c
 		EOF
 		git -C submodule show -s --pretty=%s >actual &&
 		test_cmp expect actual
@@ -65,6 +66,7 @@ test_expect_success 'test submodule set-branch --default' '
 '
 
 test_expect_success 'test submodule set-branch -b' '
+	test_commit -C submodule b &&
 	(cd super &&
 		git submodule set-branch -b topic submodule &&
 		grep "branch = topic" .gitmodules &&
@@ -78,12 +80,13 @@ test_expect_success 'test submodule set-branch -b' '
 '
 
 test_expect_success 'test submodule set-branch -d' '
+	test_commit -C submodule d &&
 	(cd super &&
 		git submodule set-branch -d submodule &&
-		test_must_fail grep branch .gitmodules &&
+		! grep branch .gitmodules &&
 		git submodule update --remote &&
 		cat <<-\EOF >expect &&
-		a
+		d
 		EOF
 		git -C submodule show -s --pretty=%s >actual &&
 		test_cmp expect actual
diff --git a/third_party/git/t/t7420-submodule-set-url.sh b/third_party/git/t/t7420-submodule-set-url.sh
new file mode 100755
index 0000000000..ef0cb6e8e1
--- /dev/null
+++ b/third_party/git/t/t7420-submodule-set-url.sh
@@ -0,0 +1,55 @@
+#!/bin/sh
+#
+# Copyright (c) 2019 Denton Liu
+#
+
+test_description='Test submodules set-url subcommand
+
+This test verifies that the set-url subcommand of git-submodule is working
+as expected.
+'
+
+TEST_NO_CREATE_REPO=1
+. ./test-lib.sh
+
+test_expect_success 'submodule config cache setup' '
+	mkdir submodule &&
+	(
+		cd submodule &&
+		git init &&
+		echo a >file &&
+		git add file &&
+		git commit -ma
+	) &&
+	mkdir super &&
+	(
+		cd super &&
+		git init &&
+		git submodule add ../submodule &&
+		git commit -m "add submodule"
+	)
+'
+
+test_expect_success 'test submodule set-url' '
+	# add a commit and move the submodule (change the url)
+	(
+		cd submodule &&
+		echo b >>file &&
+		git add file &&
+		git commit -mb
+	) &&
+	mv submodule newsubmodule &&
+
+	git -C newsubmodule show >expect &&
+	(
+		cd super &&
+		test_must_fail git submodule update --remote &&
+		git submodule set-url submodule ../newsubmodule &&
+		grep -F "url = ../newsubmodule" .gitmodules &&
+		git submodule update --remote
+	) &&
+	git -C super/submodule show >actual &&
+	test_cmp expect actual
+'
+
+test_done
diff --git a/third_party/git/t/t7421-submodule-summary-add.sh b/third_party/git/t/t7421-submodule-summary-add.sh
new file mode 100755
index 0000000000..b070f13714
--- /dev/null
+++ b/third_party/git/t/t7421-submodule-summary-add.sh
@@ -0,0 +1,69 @@
+#!/bin/sh
+#
+# Copyright (C) 2020 Shourya Shukla
+#
+
+test_description='Summary support for submodules, adding them using git submodule add
+
+This test script tries to verify the sanity of summary subcommand of git submodule
+while making sure to add submodules using `git submodule add` instead of
+`git add` as done in t7401.
+'
+
+. ./test-lib.sh
+
+test_expect_success 'summary test environment setup' '
+	git init sm &&
+	test_commit -C sm "add file" file file-content file-tag &&
+
+	git submodule add ./sm my-subm &&
+	test_tick &&
+	git commit -m "add submodule"
+'
+
+test_expect_success 'submodule summary output for initialized submodule' '
+	test_commit -C sm "add file2" file2 file2-content file2-tag &&
+	git submodule update --remote &&
+	test_tick &&
+	git commit -m "update submodule" my-subm &&
+	git submodule summary HEAD^ >actual &&
+	rev1=$(git -C sm rev-parse --short HEAD^) &&
+	rev2=$(git -C sm rev-parse --short HEAD) &&
+	cat >expected <<-EOF &&
+	* my-subm ${rev1}...${rev2} (1):
+	  > add file2
+
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'submodule summary output for deinitialized submodule' '
+	git submodule deinit my-subm &&
+	git submodule summary HEAD^ >actual &&
+	test_must_be_empty actual &&
+	git submodule update --init my-subm &&
+	git submodule summary HEAD^ >actual &&
+	rev1=$(git -C sm rev-parse --short HEAD^) &&
+	rev2=$(git -C sm rev-parse --short HEAD) &&
+	cat >expected <<-EOF &&
+	* my-subm ${rev1}...${rev2} (1):
+	  > add file2
+
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'submodule summary output for submodules with changed paths' '
+	git mv my-subm subm &&
+	git commit -m "change submodule path" &&
+	rev=$(git -C sm rev-parse --short HEAD^) &&
+	git submodule summary HEAD^^ -- my-subm >actual 2>err &&
+	test_must_be_empty err &&
+	cat >expected <<-EOF &&
+	* my-subm ${rev}...0000000:
+
+	EOF
+	test_cmp expected actual
+'
+
+test_done
diff --git a/third_party/git/t/t7500-commit-template-squash-signoff.sh b/third_party/git/t/t7500-commit-template-squash-signoff.sh
index 46a5cd4b73..6d19ece05d 100755
--- a/third_party/git/t/t7500-commit-template-squash-signoff.sh
+++ b/third_party/git/t/t7500-commit-template-squash-signoff.sh
@@ -382,4 +382,13 @@ test_expect_success 'check commit with unstaged rename and copy' '
 	)
 '
 
+test_expect_success 'commit without staging files fails and displays hints' '
+	echo "initial" >file &&
+	git add file &&
+	git commit -m initial &&
+	echo "changes" >>file &&
+	test_must_fail git commit -m update >actual &&
+	test_i18ngrep "no changes added to commit (use \"git add\" and/or \"git commit -a\")" actual
+'
+
 test_done
diff --git a/third_party/git/t/t7501-commit-basic-functionality.sh b/third_party/git/t/t7501-commit-basic-functionality.sh
index f1349af56e..110b4bf459 100755
--- a/third_party/git/t/t7501-commit-basic-functionality.sh
+++ b/third_party/git/t/t7501-commit-basic-functionality.sh
@@ -150,7 +150,7 @@ test_expect_success 'setup: commit message from file' '
 test_expect_success 'amend commit' '
 	cat >editor <<-\EOF &&
 	#!/bin/sh
-	sed -e "s/a file/an amend commit/g" < "$1" > "$1-"
+	sed -e "s/a file/an amend commit/g" <"$1" >"$1-"
 	mv "$1-" "$1"
 	EOF
 	chmod 755 editor &&
@@ -263,7 +263,7 @@ test_expect_success 'using message from other commit' '
 test_expect_success 'editing message from other commit' '
 	cat >editor <<-\EOF &&
 	#!/bin/sh
-	sed -e "s/amend/older/g"  < "$1" > "$1-"
+	sed -e "s/amend/older/g"  <"$1" >"$1-"
 	mv "$1-" "$1"
 	EOF
 	chmod 755 editor &&
@@ -285,9 +285,8 @@ test_expect_success 'overriding author from command line' '
 '
 
 test_expect_success PERL 'interactive add' '
-	echo 7 |
-	git commit --interactive |
-	grep "What now"
+	echo 7 | test_must_fail git commit --interactive >out &&
+	grep "What now" out
 '
 
 test_expect_success PERL "commit --interactive doesn't change index if editor aborts" '
@@ -362,12 +361,12 @@ test_expect_success 'amend commit to fix author' '
 	oldtick=$GIT_AUTHOR_DATE &&
 	test_tick &&
 	git reset --hard &&
-	git cat-file -p HEAD |
+	git cat-file -p HEAD >commit &&
 	sed -e "s/author.*/author $author $oldtick/" \
-		-e "s/^\(committer.*> \).*$/\1$GIT_COMMITTER_DATE/" > \
-		expected &&
+		-e "s/^\(committer.*> \).*$/\1$GIT_COMMITTER_DATE/" \
+		commit >expected &&
 	git commit --amend --author="$author" &&
-	git cat-file -p HEAD > current &&
+	git cat-file -p HEAD >current &&
 	test_cmp expected current
 
 '
@@ -377,12 +376,12 @@ test_expect_success 'amend commit to fix date' '
 	test_tick &&
 	newtick=$GIT_AUTHOR_DATE &&
 	git reset --hard &&
-	git cat-file -p HEAD |
+	git cat-file -p HEAD >commit &&
 	sed -e "s/author.*/author $author $newtick/" \
-		-e "s/^\(committer.*> \).*$/\1$GIT_COMMITTER_DATE/" > \
-		expected &&
+		-e "s/^\(committer.*> \).*$/\1$GIT_COMMITTER_DATE/" \
+		commit >expected &&
 	git commit --amend --date="$newtick" &&
-	git cat-file -p HEAD > current &&
+	git cat-file -p HEAD >current &&
 	test_cmp expected current
 
 '
@@ -409,12 +408,13 @@ test_expect_success 'sign off (1)' '
 	echo 1 >positive &&
 	git add positive &&
 	git commit -s -m "thank you" &&
-	git cat-file commit HEAD | sed -e "1,/^\$/d" >actual &&
+	git cat-file commit HEAD >commit &&
+	sed -e "1,/^\$/d" commit >actual &&
 	(
 		echo thank you &&
 		echo &&
-		git var GIT_COMMITTER_IDENT |
-		sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /"
+		git var GIT_COMMITTER_IDENT >ident &&
+		sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /" ident
 	) >expected &&
 	test_cmp expected actual
 
@@ -428,13 +428,14 @@ test_expect_success 'sign off (2)' '
 	git commit -s -m "thank you
 
 $existing" &&
-	git cat-file commit HEAD | sed -e "1,/^\$/d" >actual &&
+	git cat-file commit HEAD >commit &&
+	sed -e "1,/^\$/d" commit >actual &&
 	(
 		echo thank you &&
 		echo &&
 		echo $existing &&
-		git var GIT_COMMITTER_IDENT |
-		sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /"
+		git var GIT_COMMITTER_IDENT >ident &&
+		sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /" ident
 	) >expected &&
 	test_cmp expected actual
 
@@ -448,13 +449,14 @@ test_expect_success 'signoff gap' '
 	git commit -s -m "welcome
 
 $alt" &&
-	git cat-file commit HEAD | sed -e "1,/^\$/d" > actual &&
+	git cat-file commit HEAD >commit &&
+	sed -e "1,/^\$/d" commit >actual &&
 	(
 		echo welcome &&
 		echo &&
 		echo $alt &&
-		git var GIT_COMMITTER_IDENT |
-		sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /"
+		git var GIT_COMMITTER_IDENT >ident &&
+		sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /" ident
 	) >expected &&
 	test_cmp expected actual
 '
@@ -468,15 +470,16 @@ test_expect_success 'signoff gap 2' '
 
 We have now
 $alt" &&
-	git cat-file commit HEAD | sed -e "1,/^\$/d" > actual &&
+	git cat-file commit HEAD >commit &&
+	sed -e "1,/^\$/d" commit >actual &&
 	(
 		echo welcome &&
 		echo &&
 		echo We have now &&
 		echo $alt &&
 		echo &&
-		git var GIT_COMMITTER_IDENT |
-		sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /"
+		git var GIT_COMMITTER_IDENT >ident &&
+		sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /" ident
 	) >expected &&
 	test_cmp expected actual
 '
@@ -489,7 +492,8 @@ test_expect_success 'signoff respects trailer config' '
 
 non-trailer line
 Myfooter: x" &&
-	git cat-file commit HEAD | sed -e "1,/^\$/d" > actual &&
+	git cat-file commit HEAD >commit &&
+	sed -e "1,/^\$/d" commit >actual &&
 	(
 		echo subject &&
 		echo &&
@@ -506,7 +510,8 @@ Myfooter: x" &&
 
 non-trailer line
 Myfooter: x" &&
-	git cat-file commit HEAD | sed -e "1,/^\$/d" > actual &&
+	git cat-file commit HEAD >commit &&
+	sed -e "1,/^\$/d" commit >actual &&
 	(
 		echo subject &&
 		echo &&
@@ -538,7 +543,8 @@ test_expect_success 'multiple -m' '
 	>negative &&
 	git add negative &&
 	git commit -m "one" -m "two" -m "three" &&
-	git cat-file commit HEAD | sed -e "1,/^\$/d" >actual &&
+	git cat-file commit HEAD >commit &&
+	sed -e "1,/^\$/d" commit >actual &&
 	(
 		echo one &&
 		echo &&
@@ -555,23 +561,25 @@ test_expect_success 'amend commit to fix author' '
 	oldtick=$GIT_AUTHOR_DATE &&
 	test_tick &&
 	git reset --hard &&
-	git cat-file -p HEAD |
+	git cat-file -p HEAD >commit &&
 	sed -e "s/author.*/author $author $oldtick/" \
-		-e "s/^\(committer.*> \).*$/\1$GIT_COMMITTER_DATE/" > \
-		expected &&
+		-e "s/^\(committer.*> \).*$/\1$GIT_COMMITTER_DATE/" \
+		commit >expected &&
 	git commit --amend --author="$author" &&
-	git cat-file -p HEAD > current &&
+	git cat-file -p HEAD >current &&
 	test_cmp expected current
 
 '
 
 test_expect_success 'git commit <file> with dirty index' '
-	echo tacocat > elif &&
-	echo tehlulz > chz &&
+	echo tacocat >elif &&
+	echo tehlulz >chz &&
 	git add chz &&
 	git commit elif -m "tacocat is a palindrome" &&
-	git show --stat | grep elif &&
-	git diff --cached | grep chz
+	git show --stat >stat &&
+	grep elif stat &&
+	git diff --cached >diff &&
+	grep chz diff
 '
 
 test_expect_success 'same tree (single parent)' '
@@ -584,7 +592,8 @@ test_expect_success 'same tree (single parent)' '
 test_expect_success 'same tree (single parent) --allow-empty' '
 
 	git commit --allow-empty -m "forced empty" &&
-	git cat-file commit HEAD | grep forced
+	git cat-file commit HEAD >commit &&
+	grep forced commit
 
 '
 
diff --git a/third_party/git/t/t7503-pre-commit-and-pre-merge-commit-hooks.sh b/third_party/git/t/t7503-pre-commit-and-pre-merge-commit-hooks.sh
new file mode 100755
index 0000000000..b3485450a2
--- /dev/null
+++ b/third_party/git/t/t7503-pre-commit-and-pre-merge-commit-hooks.sh
@@ -0,0 +1,281 @@
+#!/bin/sh
+
+test_description='pre-commit and pre-merge-commit hooks'
+
+. ./test-lib.sh
+
+HOOKDIR="$(git rev-parse --git-dir)/hooks"
+PRECOMMIT="$HOOKDIR/pre-commit"
+PREMERGE="$HOOKDIR/pre-merge-commit"
+
+# Prepare sample scripts that write their $0 to actual_hooks
+test_expect_success 'sample script setup' '
+	mkdir -p "$HOOKDIR" &&
+	write_script "$HOOKDIR/success.sample" <<-\EOF &&
+	echo $0 >>actual_hooks
+	exit 0
+	EOF
+	write_script "$HOOKDIR/fail.sample" <<-\EOF &&
+	echo $0 >>actual_hooks
+	exit 1
+	EOF
+	write_script "$HOOKDIR/non-exec.sample" <<-\EOF &&
+	echo $0 >>actual_hooks
+	exit 1
+	EOF
+	chmod -x "$HOOKDIR/non-exec.sample" &&
+	write_script "$HOOKDIR/require-prefix.sample" <<-\EOF &&
+	echo $0 >>actual_hooks
+	test $GIT_PREFIX = "success/"
+	EOF
+	write_script "$HOOKDIR/check-author.sample" <<-\EOF
+	echo $0 >>actual_hooks
+	test "$GIT_AUTHOR_NAME" = "New Author" &&
+	test "$GIT_AUTHOR_EMAIL" = "newauthor@example.com"
+	EOF
+'
+
+test_expect_success 'root commit' '
+	echo "root" >file &&
+	git add file &&
+	git commit -m "zeroth" &&
+	git checkout -b side &&
+	echo "foo" >foo &&
+	git add foo &&
+	git commit -m "make it non-ff" &&
+	git branch side-orig side &&
+	git checkout master
+'
+
+test_expect_success 'setup conflicting branches' '
+	test_when_finished "git checkout master" &&
+	git checkout -b conflicting-a master &&
+	echo a >conflicting &&
+	git add conflicting &&
+	git commit -m conflicting-a &&
+	git checkout -b conflicting-b master &&
+	echo b >conflicting &&
+	git add conflicting &&
+	git commit -m conflicting-b
+'
+
+test_expect_success 'with no hook' '
+	test_when_finished "rm -f actual_hooks" &&
+	echo "foo" >file &&
+	git add file &&
+	git commit -m "first" &&
+	test_path_is_missing actual_hooks
+'
+
+test_expect_success 'with no hook (merge)' '
+	test_when_finished "rm -f actual_hooks" &&
+	git branch -f side side-orig &&
+	git checkout side &&
+	git merge -m "merge master" master &&
+	git checkout master &&
+	test_path_is_missing actual_hooks
+'
+
+test_expect_success '--no-verify with no hook' '
+	test_when_finished "rm -f actual_hooks" &&
+	echo "bar" >file &&
+	git add file &&
+	git commit --no-verify -m "bar" &&
+	test_path_is_missing actual_hooks
+'
+
+test_expect_success '--no-verify with no hook (merge)' '
+	test_when_finished "rm -f actual_hooks" &&
+	git branch -f side side-orig &&
+	git checkout side &&
+	git merge --no-verify -m "merge master" master &&
+	git checkout master &&
+	test_path_is_missing actual_hooks
+'
+
+test_expect_success 'with succeeding hook' '
+	test_when_finished "rm -f \"$PRECOMMIT\" expected_hooks actual_hooks" &&
+	cp "$HOOKDIR/success.sample" "$PRECOMMIT" &&
+	echo "$PRECOMMIT" >expected_hooks &&
+	echo "more" >>file &&
+	git add file &&
+	git commit -m "more" &&
+	test_cmp expected_hooks actual_hooks
+'
+
+test_expect_success 'with succeeding hook (merge)' '
+	test_when_finished "rm -f \"$PREMERGE\" expected_hooks actual_hooks" &&
+	cp "$HOOKDIR/success.sample" "$PREMERGE" &&
+	echo "$PREMERGE" >expected_hooks &&
+	git checkout side &&
+	git merge -m "merge master" master &&
+	git checkout master &&
+	test_cmp expected_hooks actual_hooks
+'
+
+test_expect_success 'automatic merge fails; both hooks are available' '
+	test_when_finished "rm -f \"$PREMERGE\" \"$PRECOMMIT\"" &&
+	test_when_finished "rm -f expected_hooks actual_hooks" &&
+	test_when_finished "git checkout master" &&
+	cp "$HOOKDIR/success.sample" "$PREMERGE" &&
+	cp "$HOOKDIR/success.sample" "$PRECOMMIT" &&
+
+	git checkout conflicting-a &&
+	test_must_fail git merge -m "merge conflicting-b" conflicting-b &&
+	test_path_is_missing actual_hooks &&
+
+	echo "$PRECOMMIT" >expected_hooks &&
+	echo a+b >conflicting &&
+	git add conflicting &&
+	git commit -m "resolve conflict" &&
+	test_cmp expected_hooks actual_hooks
+'
+
+test_expect_success '--no-verify with succeeding hook' '
+	test_when_finished "rm -f \"$PRECOMMIT\" actual_hooks" &&
+	cp "$HOOKDIR/success.sample" "$PRECOMMIT" &&
+	echo "even more" >>file &&
+	git add file &&
+	git commit --no-verify -m "even more" &&
+	test_path_is_missing actual_hooks
+'
+
+test_expect_success '--no-verify with succeeding hook (merge)' '
+	test_when_finished "rm -f \"$PREMERGE\" actual_hooks" &&
+	cp "$HOOKDIR/success.sample" "$PREMERGE" &&
+	git branch -f side side-orig &&
+	git checkout side &&
+	git merge --no-verify -m "merge master" master &&
+	git checkout master &&
+	test_path_is_missing actual_hooks
+'
+
+test_expect_success 'with failing hook' '
+	test_when_finished "rm -f \"$PRECOMMIT\" expected_hooks actual_hooks" &&
+	cp "$HOOKDIR/fail.sample" "$PRECOMMIT" &&
+	echo "$PRECOMMIT" >expected_hooks &&
+	echo "another" >>file &&
+	git add file &&
+	test_must_fail git commit -m "another" &&
+	test_cmp expected_hooks actual_hooks
+'
+
+test_expect_success '--no-verify with failing hook' '
+	test_when_finished "rm -f \"$PRECOMMIT\" actual_hooks" &&
+	cp "$HOOKDIR/fail.sample" "$PRECOMMIT" &&
+	echo "stuff" >>file &&
+	git add file &&
+	git commit --no-verify -m "stuff" &&
+	test_path_is_missing actual_hooks
+'
+
+test_expect_success 'with failing hook (merge)' '
+	test_when_finished "rm -f \"$PREMERGE\" expected_hooks actual_hooks" &&
+	cp "$HOOKDIR/fail.sample" "$PREMERGE" &&
+	echo "$PREMERGE" >expected_hooks &&
+	git checkout side &&
+	test_must_fail git merge -m "merge master" master &&
+	git checkout master &&
+	test_cmp expected_hooks actual_hooks
+'
+
+test_expect_success '--no-verify with failing hook (merge)' '
+	test_when_finished "rm -f \"$PREMERGE\" actual_hooks" &&
+	cp "$HOOKDIR/fail.sample" "$PREMERGE" &&
+	git branch -f side side-orig &&
+	git checkout side &&
+	git merge --no-verify -m "merge master" master &&
+	git checkout master &&
+	test_path_is_missing actual_hooks
+'
+
+test_expect_success POSIXPERM 'with non-executable hook' '
+	test_when_finished "rm -f \"$PRECOMMIT\" actual_hooks" &&
+	cp "$HOOKDIR/non-exec.sample" "$PRECOMMIT" &&
+	echo "content" >>file &&
+	git add file &&
+	git commit -m "content" &&
+	test_path_is_missing actual_hooks
+'
+
+test_expect_success POSIXPERM '--no-verify with non-executable hook' '
+	test_when_finished "rm -f \"$PRECOMMIT\" actual_hooks" &&
+	cp "$HOOKDIR/non-exec.sample" "$PRECOMMIT" &&
+	echo "more content" >>file &&
+	git add file &&
+	git commit --no-verify -m "more content" &&
+	test_path_is_missing actual_hooks
+'
+
+test_expect_success POSIXPERM 'with non-executable hook (merge)' '
+	test_when_finished "rm -f \"$PREMERGE\" actual_hooks" &&
+	cp "$HOOKDIR/non-exec.sample" "$PREMERGE" &&
+	git branch -f side side-orig &&
+	git checkout side &&
+	git merge -m "merge master" master &&
+	git checkout master &&
+	test_path_is_missing actual_hooks
+'
+
+test_expect_success POSIXPERM '--no-verify with non-executable hook (merge)' '
+	test_when_finished "rm -f \"$PREMERGE\" actual_hooks" &&
+	cp "$HOOKDIR/non-exec.sample" "$PREMERGE" &&
+	git branch -f side side-orig &&
+	git checkout side &&
+	git merge --no-verify -m "merge master" master &&
+	git checkout master &&
+	test_path_is_missing actual_hooks
+'
+
+test_expect_success 'with hook requiring GIT_PREFIX' '
+	test_when_finished "rm -rf \"$PRECOMMIT\" expected_hooks actual_hooks success" &&
+	cp "$HOOKDIR/require-prefix.sample" "$PRECOMMIT" &&
+	echo "$PRECOMMIT" >expected_hooks &&
+	echo "more content" >>file &&
+	git add file &&
+	mkdir success &&
+	(
+		cd success &&
+		git commit -m "hook requires GIT_PREFIX = success/"
+	) &&
+	test_cmp expected_hooks actual_hooks
+'
+
+test_expect_success 'with failing hook requiring GIT_PREFIX' '
+	test_when_finished "rm -rf \"$PRECOMMIT\" expected_hooks actual_hooks fail" &&
+	cp "$HOOKDIR/require-prefix.sample" "$PRECOMMIT" &&
+	echo "$PRECOMMIT" >expected_hooks &&
+	echo "more content" >>file &&
+	git add file &&
+	mkdir fail &&
+	(
+		cd fail &&
+		test_must_fail git commit -m "hook must fail"
+	) &&
+	git checkout -- file &&
+	test_cmp expected_hooks actual_hooks
+'
+
+test_expect_success 'check the author in hook' '
+	test_when_finished "rm -f \"$PRECOMMIT\" expected_hooks actual_hooks" &&
+	cp "$HOOKDIR/check-author.sample" "$PRECOMMIT" &&
+	cat >expected_hooks <<-EOF &&
+	$PRECOMMIT
+	$PRECOMMIT
+	$PRECOMMIT
+	EOF
+	test_must_fail git commit --allow-empty -m "by a.u.thor" &&
+	(
+		GIT_AUTHOR_NAME="New Author" &&
+		GIT_AUTHOR_EMAIL="newauthor@example.com" &&
+		export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL &&
+		git commit --allow-empty -m "by new.author via env" &&
+		git show -s
+	) &&
+	git commit --author="New Author <newauthor@example.com>" \
+		--allow-empty -m "by new.author via command line" &&
+	git show -s &&
+	test_cmp expected_hooks actual_hooks
+'
+
+test_done
diff --git a/third_party/git/t/t7503-pre-commit-hook.sh b/third_party/git/t/t7503-pre-commit-hook.sh
deleted file mode 100755
index 984889b39d..0000000000
--- a/third_party/git/t/t7503-pre-commit-hook.sh
+++ /dev/null
@@ -1,139 +0,0 @@
-#!/bin/sh
-
-test_description='pre-commit hook'
-
-. ./test-lib.sh
-
-test_expect_success 'with no hook' '
-
-	echo "foo" > file &&
-	git add file &&
-	git commit -m "first"
-
-'
-
-test_expect_success '--no-verify with no hook' '
-
-	echo "bar" > file &&
-	git add file &&
-	git commit --no-verify -m "bar"
-
-'
-
-# now install hook that always succeeds
-HOOKDIR="$(git rev-parse --git-dir)/hooks"
-HOOK="$HOOKDIR/pre-commit"
-mkdir -p "$HOOKDIR"
-cat > "$HOOK" <<EOF
-#!/bin/sh
-exit 0
-EOF
-chmod +x "$HOOK"
-
-test_expect_success 'with succeeding hook' '
-
-	echo "more" >> file &&
-	git add file &&
-	git commit -m "more"
-
-'
-
-test_expect_success '--no-verify with succeeding hook' '
-
-	echo "even more" >> file &&
-	git add file &&
-	git commit --no-verify -m "even more"
-
-'
-
-# now a hook that fails
-cat > "$HOOK" <<EOF
-#!/bin/sh
-exit 1
-EOF
-
-test_expect_success 'with failing hook' '
-
-	echo "another" >> file &&
-	git add file &&
-	test_must_fail git commit -m "another"
-
-'
-
-test_expect_success '--no-verify with failing hook' '
-
-	echo "stuff" >> file &&
-	git add file &&
-	git commit --no-verify -m "stuff"
-
-'
-
-chmod -x "$HOOK"
-test_expect_success POSIXPERM 'with non-executable hook' '
-
-	echo "content" >> file &&
-	git add file &&
-	git commit -m "content"
-
-'
-
-test_expect_success POSIXPERM '--no-verify with non-executable hook' '
-
-	echo "more content" >> file &&
-	git add file &&
-	git commit --no-verify -m "more content"
-
-'
-chmod +x "$HOOK"
-
-# a hook that checks $GIT_PREFIX and succeeds inside the
-# success/ subdirectory only
-cat > "$HOOK" <<EOF
-#!/bin/sh
-test \$GIT_PREFIX = success/
-EOF
-
-test_expect_success 'with hook requiring GIT_PREFIX' '
-
-	echo "more content" >> file &&
-	git add file &&
-	mkdir success &&
-	(
-		cd success &&
-		git commit -m "hook requires GIT_PREFIX = success/"
-	) &&
-	rmdir success
-'
-
-test_expect_success 'with failing hook requiring GIT_PREFIX' '
-
-	echo "more content" >> file &&
-	git add file &&
-	mkdir fail &&
-	(
-		cd fail &&
-		test_must_fail git commit -m "hook must fail"
-	) &&
-	rmdir fail &&
-	git checkout -- file
-'
-
-test_expect_success 'check the author in hook' '
-	write_script "$HOOK" <<-\EOF &&
-	test "$GIT_AUTHOR_NAME" = "New Author" &&
-	test "$GIT_AUTHOR_EMAIL" = "newauthor@example.com"
-	EOF
-	test_must_fail git commit --allow-empty -m "by a.u.thor" &&
-	(
-		GIT_AUTHOR_NAME="New Author" &&
-		GIT_AUTHOR_EMAIL="newauthor@example.com" &&
-		export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL &&
-		git commit --allow-empty -m "by new.author via env" &&
-		git show -s
-	) &&
-	git commit --author="New Author <newauthor@example.com>" \
-		--allow-empty -m "by new.author via command line" &&
-	git show -s
-'
-
-test_done
diff --git a/third_party/git/t/t7505-prepare-commit-msg-hook.sh b/third_party/git/t/t7505-prepare-commit-msg-hook.sh
index ba8bd1b514..94f85cdf83 100755
--- a/third_party/git/t/t7505-prepare-commit-msg-hook.sh
+++ b/third_party/git/t/t7505-prepare-commit-msg-hook.sh
@@ -241,13 +241,7 @@ test_rebase () {
 			git add b &&
 			git rebase --continue
 		) &&
-		if test "$mode" = -p # reword amended after pick
-		then
-			n=18
-		else
-			n=17
-		fi &&
-		git log --pretty=%s -g -n$n HEAD@{1} >actual &&
+		git log --pretty=%s -g -n18 HEAD@{1} >actual &&
 		test_cmp "$TEST_DIRECTORY/t7505/expected-rebase${mode:--i}" actual
 	'
 }
diff --git a/third_party/git/t/t7505/expected-rebase-i b/third_party/git/t/t7505/expected-rebase-i
index c514bdbb94..93bada596e 100644
--- a/third_party/git/t/t7505/expected-rebase-i
+++ b/third_party/git/t/t7505/expected-rebase-i
@@ -7,7 +7,8 @@ message (no editor) [edit rebase-10]
 message [fixup rebase-9]
 message (no editor) [fixup rebase-8]
 message (no editor) [squash rebase-7]
-message [reword rebase-6]
+HEAD [reword rebase-6]
+message (no editor) [reword rebase-6]
 message [squash rebase-5]
 message (no editor) [fixup rebase-4]
 message (no editor) [pick rebase-3]
diff --git a/third_party/git/t/t7506-status-submodule.sh b/third_party/git/t/t7506-status-submodule.sh
index 08629a6e70..3fcb44767f 100755
--- a/third_party/git/t/t7506-status-submodule.sh
+++ b/third_party/git/t/t7506-status-submodule.sh
@@ -22,6 +22,10 @@ sanitize_output () {
 	mv output2 output
 }
 
+sanitize_diff () {
+	sed -e "/^index [0-9a-f,]*\.\.[0-9a-f]*/d" "$1"
+}
+
 
 test_expect_success 'setup' '
 	test_create_repo_with_commit sub &&
@@ -269,7 +273,6 @@ short_sha1_merge_sub1=$(cd sub1 && git rev-parse --short HEAD)
 short_sha1_merge_sub2=$(cd sub2 && git rev-parse --short HEAD)
 cat >diff_expect <<\EOF
 diff --cc .gitmodules
-index badaa4c,44f999a..0000000
 --- a/.gitmodules
 +++ b/.gitmodules
 @@@ -1,3 -1,3 +1,9 @@@
@@ -286,7 +289,6 @@ EOF
 
 cat >diff_submodule_expect <<\EOF
 diff --cc .gitmodules
-index badaa4c,44f999a..0000000
 --- a/.gitmodules
 +++ b/.gitmodules
 @@@ -1,3 -1,3 +1,9 @@@
@@ -306,7 +308,8 @@ test_expect_success 'diff with merge conflict in .gitmodules' '
 		cd super &&
 		git diff >../diff_actual 2>&1
 	) &&
-	test_cmp diff_expect diff_actual
+	sanitize_diff diff_actual >diff_sanitized &&
+	test_cmp diff_expect diff_sanitized
 '
 
 test_expect_success 'diff --submodule with merge conflict in .gitmodules' '
@@ -314,7 +317,8 @@ test_expect_success 'diff --submodule with merge conflict in .gitmodules' '
 		cd super &&
 		git diff --submodule >../diff_submodule_actual 2>&1
 	) &&
-	test_cmp diff_submodule_expect diff_submodule_actual
+	sanitize_diff diff_submodule_actual >diff_sanitized &&
+	test_cmp diff_submodule_expect diff_sanitized
 '
 
 # We'll setup different cases for further testing:
diff --git a/third_party/git/t/t7508-status.sh b/third_party/git/t/t7508-status.sh
index 4e676cdce8..963fed6821 100755
--- a/third_party/git/t/t7508-status.sh
+++ b/third_party/git/t/t7508-status.sh
@@ -814,6 +814,33 @@ test_expect_success 'status -s without relative paths' '
 
 '
 
+cat >expect <<\EOF
+ M dir1/modified
+A  dir2/added
+A  "file with spaces"
+?? dir1/untracked
+?? dir2/modified
+?? dir2/untracked
+?? "file with spaces 2"
+?? untracked
+EOF
+
+test_expect_success 'status -s without relative paths' '
+	test_when_finished "git rm --cached \"file with spaces\"; rm -f file*" &&
+	>"file with spaces" &&
+	>"file with spaces 2" &&
+	>"expect with spaces" &&
+	git add "file with spaces" &&
+
+	git status -s >output &&
+	test_cmp expect output &&
+
+	git status -s --ignored >output &&
+	grep "^!! \"expect with spaces\"$" output &&
+	grep -v "^!! " output >output-wo-ignored &&
+	test_cmp expect output-wo-ignored
+'
+
 test_expect_success 'dry-run of partial commit excluding new file in index' '
 	cat >expect <<EOF &&
 On branch master
@@ -837,7 +864,7 @@ EOF
 '
 
 cat >expect <<EOF
-:100644 100644 $EMPTY_BLOB 0000000000000000000000000000000000000000 M	dir1/modified
+:100644 100644 $EMPTY_BLOB $ZERO_OID M	dir1/modified
 EOF
 test_expect_success 'status refreshes the index' '
 	touch dir2/added &&
@@ -846,6 +873,18 @@ test_expect_success 'status refreshes the index' '
 	test_cmp expect output
 '
 
+test_expect_success 'status shows detached HEAD properly after checking out non-local upstream branch' '
+	test_when_finished rm -rf upstream downstream actual &&
+
+	test_create_repo upstream &&
+	test_commit -C upstream foo &&
+
+	git clone upstream downstream &&
+	git -C downstream checkout @{u} &&
+	git -C downstream status >actual &&
+	test_i18ngrep "HEAD detached at [0-9a-f]\\+" actual
+'
+
 test_expect_success 'setup status submodule summary' '
 	test_create_repo sm && (
 		cd sm &&
@@ -1471,7 +1510,7 @@ test_expect_success '"status.branch=true" same as "-b"' '
 test_expect_success '"status.branch=true" different from "--no-branch"' '
 	git status -s --no-branch  >expected_nobranch &&
 	git -c status.branch=true status -s >actual &&
-	test_must_fail test_cmp expected_nobranch actual
+	! test_cmp expected_nobranch actual
 '
 
 test_expect_success '"status.branch=true" weaker than "--no-branch"' '
@@ -1571,7 +1610,7 @@ test_expect_success '"status.showStash=true" weaker than "--no-show-stash"' '
 	test_cmp expected_without_stash actual
 '
 
-test_expect_success 'no additionnal info if no stash entries' '
+test_expect_success 'no additional info if no stash entries' '
 	git stash clear &&
 	git -c status.showStash=true status >actual &&
 	test_cmp expected_without_stash actual
diff --git a/third_party/git/t/t7510-signed-commit.sh b/third_party/git/t/t7510-signed-commit.sh
index 682b23a068..6baaa1ad91 100755
--- a/third_party/git/t/t7510-signed-commit.sh
+++ b/third_party/git/t/t7510-signed-commit.sh
@@ -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 &&
@@ -109,6 +114,21 @@ test_expect_success GPG 'verify-commit exits success on untrusted signature' '
 	grep "not certified" actual
 '
 
+test_expect_success GPG 'verify-commit exits success with matching minTrustLevel' '
+	test_config gpg.minTrustLevel ultimate &&
+	git verify-commit sixth-signed
+'
+
+test_expect_success GPG 'verify-commit exits success with low minTrustLevel' '
+	test_config gpg.minTrustLevel fully &&
+	git verify-commit sixth-signed
+'
+
+test_expect_success GPG 'verify-commit exits failure with high minTrustLevel' '
+	test_config gpg.minTrustLevel ultimate &&
+	test_must_fail git verify-commit eighth-signed-alt
+'
+
 test_expect_success GPG 'verify signatures with --raw' '
 	(
 		for commit in initial second merge fourth-signed fifth-signed sixth-signed seventh-signed
@@ -140,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 &&
@@ -147,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
@@ -219,6 +244,30 @@ test_expect_success GPG 'show untrusted signature with custom format' '
 	test_cmp expect actual
 '
 
+test_expect_success GPG 'show untrusted signature with undefined trust level' '
+	cat >expect <<-\EOF &&
+	undefined
+	65A0EEA02E30CAD7
+	Eris Discordia <discord@example.net>
+	F8364A59E07FFE9F4D63005A65A0EEA02E30CAD7
+	D4BE22311AD3131E5EDA29A461092E85B7227189
+	EOF
+	git log -1 --format="%GT%n%GK%n%GS%n%GF%n%GP" eighth-signed-alt >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success GPG 'show untrusted signature with ultimate trust level' '
+	cat >expect <<-\EOF &&
+	ultimate
+	13B6F51ECDDE430D
+	C O Mitter <committer@example.com>
+	73D758744BE721698EC54E8713B6F51ECDDE430D
+	73D758744BE721698EC54E8713B6F51ECDDE430D
+	EOF
+	git log -1 --format="%GT%n%GK%n%GS%n%GF%n%GP" sixth-signed >actual &&
+	test_cmp expect actual
+'
+
 test_expect_success GPG 'show unknown signature with custom format' '
 	cat >expect <<-\EOF &&
 	E
@@ -260,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 &&
diff --git a/third_party/git/t/t7512-status-help.sh b/third_party/git/t/t7512-status-help.sh
index e01c285cbf..29518e0949 100755
--- a/third_party/git/t/t7512-status-help.sh
+++ b/third_party/git/t/t7512-status-help.sh
@@ -71,10 +71,10 @@ test_expect_success 'prepare for rebase conflicts' '
 '
 
 
-test_expect_success 'status when rebase in progress before resolving conflicts' '
+test_expect_success 'status when rebase --apply in progress before resolving conflicts' '
 	test_when_finished "git rebase --abort" &&
 	ONTO=$(git rev-parse --short HEAD^^) &&
-	test_must_fail git rebase HEAD^ --onto HEAD^^ &&
+	test_must_fail git rebase --apply HEAD^ --onto HEAD^^ &&
 	cat >expected <<EOF &&
 rebase in progress; onto $ONTO
 You are currently rebasing branch '\''rebase_conflicts'\'' on '\''$ONTO'\''.
@@ -94,11 +94,11 @@ EOF
 '
 
 
-test_expect_success 'status when rebase in progress before rebase --continue' '
+test_expect_success 'status when rebase --apply in progress before rebase --continue' '
 	git reset --hard rebase_conflicts &&
 	test_when_finished "git rebase --abort" &&
 	ONTO=$(git rev-parse --short HEAD^^) &&
-	test_must_fail git rebase HEAD^ --onto HEAD^^ &&
+	test_must_fail git rebase --apply HEAD^ --onto HEAD^^ &&
 	echo three >main.txt &&
 	git add main.txt &&
 	cat >expected <<EOF &&
@@ -688,7 +688,7 @@ EOF
 '
 
 
-test_expect_success 'status when rebase conflicts with statushints disabled' '
+test_expect_success 'status when rebase --apply conflicts with statushints disabled' '
 	git reset --hard master &&
 	git checkout -b statushints_disabled &&
 	test_when_finished "git config --local advice.statushints true" &&
@@ -698,7 +698,7 @@ test_expect_success 'status when rebase conflicts with statushints disabled' '
 	test_commit three_statushints main.txt three &&
 	test_when_finished "git rebase --abort" &&
 	ONTO=$(git rev-parse --short HEAD^^) &&
-	test_must_fail git rebase HEAD^ --onto HEAD^^ &&
+	test_must_fail git rebase --apply HEAD^ --onto HEAD^^ &&
 	cat >expected <<EOF &&
 rebase in progress; onto $ONTO
 You are currently rebasing branch '\''statushints_disabled'\'' on '\''$ONTO'\''.
@@ -733,6 +733,7 @@ test_expect_success 'status when cherry-picking before resolving conflicts' '
 On branch cherry_branch
 You are currently cherry-picking commit $TO_CHERRY_PICK.
   (fix conflicts and run "git cherry-pick --continue")
+  (use "git cherry-pick --skip" to skip this patch)
   (use "git cherry-pick --abort" to cancel the cherry-pick operation)
 
 Unmerged paths:
@@ -757,6 +758,7 @@ test_expect_success 'status when cherry-picking after resolving conflicts' '
 On branch cherry_branch
 You are currently cherry-picking commit $TO_CHERRY_PICK.
   (all conflicts fixed: run "git cherry-pick --continue")
+  (use "git cherry-pick --skip" to skip this patch)
   (use "git cherry-pick --abort" to cancel the cherry-pick operation)
 
 Changes to be committed:
@@ -778,6 +780,7 @@ test_expect_success 'status when cherry-picking after committing conflict resolu
 On branch cherry_branch
 Cherry-pick currently in progress.
   (run "git cherry-pick --continue" to continue)
+  (use "git cherry-pick --skip" to skip this patch)
   (use "git cherry-pick --abort" to cancel the cherry-pick operation)
 
 nothing to commit (use -u to show untracked files)
@@ -835,6 +838,7 @@ test_expect_success 'status while reverting commit (conflicts)' '
 On branch master
 You are currently reverting commit $TO_REVERT.
   (fix conflicts and run "git revert --continue")
+  (use "git revert --skip" to skip this patch)
   (use "git revert --abort" to cancel the revert operation)
 
 Unmerged paths:
@@ -855,6 +859,7 @@ test_expect_success 'status while reverting commit (conflicts resolved)' '
 On branch master
 You are currently reverting commit $TO_REVERT.
   (all conflicts fixed: run "git revert --continue")
+  (use "git revert --skip" to skip this patch)
   (use "git revert --abort" to cancel the revert operation)
 
 Changes to be committed:
@@ -887,6 +892,7 @@ test_expect_success 'status while reverting after committing conflict resolution
 On branch master
 Revert currently in progress.
   (run "git revert --continue" to continue)
+  (use "git revert --skip" to skip this patch)
   (use "git revert --abort" to cancel the revert operation)
 
 nothing to commit (use -u to show untracked files)
diff --git a/third_party/git/t/t7513-interpret-trailers.sh b/third_party/git/t/t7513-interpret-trailers.sh
index f19202b509..6602790b5f 100755
--- a/third_party/git/t/t7513-interpret-trailers.sh
+++ b/third_party/git/t/t7513-interpret-trailers.sh
@@ -1234,7 +1234,7 @@ test_expect_success 'with simple command' '
 	test_cmp expected actual
 '
 
-test_expect_success 'with command using commiter information' '
+test_expect_success 'with command using committer information' '
 	git config trailer.sign.ifExists "addIfDifferent" &&
 	git config trailer.sign.command "echo \"\$GIT_COMMITTER_NAME <\$GIT_COMMITTER_EMAIL>\"" &&
 	cat complex_message_body >expected &&
diff --git a/third_party/git/t/t7518-ident-corner-cases.sh b/third_party/git/t/t7518-ident-corner-cases.sh
index b22f631261..dc3e9c8c88 100755
--- a/third_party/git/t/t7518-ident-corner-cases.sh
+++ b/third_party/git/t/t7518-ident-corner-cases.sh
@@ -29,7 +29,18 @@ test_expect_success 'empty configured name does not auto-detect' '
 		sane_unset GIT_AUTHOR_NAME &&
 		test_must_fail \
 			git -c user.name= commit --allow-empty -m foo 2>err &&
-		test_i18ngrep "empty ident name" err
+		test_i18ngrep "empty ident name" err &&
+		test_i18ngrep "Author identity unknown" err
+	)
+'
+
+test_expect_success 'empty configured name does not auto-detect for committer' '
+	(
+		sane_unset GIT_COMMITTER_NAME &&
+		test_must_fail \
+			git -c user.name= commit --allow-empty -m foo 2>err &&
+		test_i18ngrep "empty ident name" err &&
+		test_i18ngrep "Committer identity unknown" err
 	)
 '
 
diff --git a/third_party/git/t/t7519-status-fsmonitor.sh b/third_party/git/t/t7519-status-fsmonitor.sh
index 81a375fa0f..fbfdcca000 100755
--- a/third_party/git/t/t7519-status-fsmonitor.sh
+++ b/third_party/git/t/t7519-status-fsmonitor.sh
@@ -32,11 +32,12 @@ write_integration_script () {
 		echo "$0: exactly 2 arguments expected"
 		exit 2
 	fi
-	if test "$1" != 1
+	if test "$1" != 2
 	then
 		echo "Unsupported core.fsmonitor hook version." >&2
 		exit 1
 	fi
+	printf "last_update_token\0"
 	printf "untracked\0"
 	printf "dir1/untracked\0"
 	printf "dir2/untracked\0"
@@ -106,6 +107,9 @@ EOF
 
 # test that "update-index --fsmonitor-valid" sets the fsmonitor valid bit
 test_expect_success 'update-index --fsmonitor-valid" sets the fsmonitor valid bit' '
+	write_script .git/hooks/fsmonitor-test<<-\EOF &&
+		printf "last_update_token\0"
+	EOF
 	git update-index --fsmonitor &&
 	git update-index --fsmonitor-valid dir1/modified &&
 	git update-index --fsmonitor-valid dir2/modified &&
@@ -164,6 +168,9 @@ EOF
 
 # test that newly added files are marked valid
 test_expect_success 'newly added files are marked valid' '
+	write_script .git/hooks/fsmonitor-test<<-\EOF &&
+		printf "last_update_token\0"
+	EOF
 	git add new &&
 	git add dir1/new &&
 	git add dir2/new &&
@@ -203,6 +210,7 @@ EOF
 # test that *only* files returned by the integration script get flagged as invalid
 test_expect_success '*only* files returned by the integration script get flagged as invalid' '
 	write_script .git/hooks/fsmonitor-test<<-\EOF &&
+	printf "last_update_token\0"
 	printf "dir1/modified\0"
 	EOF
 	clean_repo &&
@@ -218,11 +226,12 @@ test_expect_success '*only* files returned by the integration script get flagged
 # Ensure commands that call refresh_index() to move the index back in time
 # properly invalidate the fsmonitor cache
 test_expect_success 'refresh_index() invalidates fsmonitor cache' '
-	write_script .git/hooks/fsmonitor-test<<-\EOF &&
-	EOF
 	clean_repo &&
 	dirty_repo &&
+	write_integration_script &&
 	git add . &&
+	write_script .git/hooks/fsmonitor-test<<-\EOF &&
+	EOF
 	git commit -m "to reset" &&
 	git reset HEAD~1 &&
 	git status >actual &&
@@ -271,6 +280,7 @@ do
 		# (if enabled) files unless it is told about them.
 		test_expect_success "status doesn't detect unreported modifications" '
 			write_script .git/hooks/fsmonitor-test<<-\EOF &&
+			printf "last_update_token\0"
 			:>marker
 			EOF
 			clean_repo &&
@@ -294,7 +304,7 @@ do
 	done
 done
 
-# test that splitting the index dosn't interfere
+# test that splitting the index doesn't interfere
 test_expect_success 'splitting the index results in the same state' '
 	write_integration_script &&
 	dirty_repo &&
@@ -354,4 +364,23 @@ test_expect_success 'discard_index() also discards fsmonitor info' '
 	test_cmp expect actual
 '
 
+# Test unstaging entries that:
+#  - Are not flagged with CE_FSMONITOR_VALID
+#  - Have a position in the index >= the number of entries present in the index
+#    after unstaging.
+test_expect_success 'status succeeds after staging/unstaging' '
+	test_create_repo fsmonitor-stage-unstage &&
+	(
+		cd fsmonitor-stage-unstage &&
+		test_commit initial &&
+		git update-index --fsmonitor &&
+		removed=$(test_seq 1 100 | sed "s/^/z/") &&
+		touch $removed &&
+		git add $removed &&
+		git config core.fsmonitor "$TEST_DIRECTORY/t7519/fsmonitor-env" &&
+		FSMONITOR_LIST="$removed" git restore -S $removed &&
+		FSMONITOR_LIST="$removed" git status
+	)
+'
+
 test_done
diff --git a/third_party/git/t/t7519/fsmonitor-all b/third_party/git/t/t7519/fsmonitor-all
index 691bc94dc2..94ab66bd3d 100755
--- a/third_party/git/t/t7519/fsmonitor-all
+++ b/third_party/git/t/t7519/fsmonitor-all
@@ -17,7 +17,6 @@ fi
 
 if test "$1" != 1
 then
-	echo "Unsupported core.fsmonitor hook version." >&2
 	exit 1
 fi
 
diff --git a/third_party/git/t/t7519/fsmonitor-all-v2 b/third_party/git/t/t7519/fsmonitor-all-v2
new file mode 100755
index 0000000000..061907e88b
--- /dev/null
+++ b/third_party/git/t/t7519/fsmonitor-all-v2
@@ -0,0 +1,21 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+#
+# An test hook script to integrate with git to test fsmonitor.
+#
+# The hook is passed a version (currently 2) and since token
+# formatted as a string and outputs to stdout all files that have been
+# modified since the given time. Paths must be relative to the root of
+# the working tree and separated by a single NUL.
+#
+#echo "$0 $*" >&2
+my ($version, $last_update_token) = @ARGV;
+
+if ($version ne 2) {
+	print "Unsupported query-fsmonitor hook version '$version'.\n";
+	exit 1;
+}
+
+print "last_update_token\0/\0"
diff --git a/third_party/git/t/t7519/fsmonitor-env b/third_party/git/t/t7519/fsmonitor-env
new file mode 100755
index 0000000000..8f1f7ab164
--- /dev/null
+++ b/third_party/git/t/t7519/fsmonitor-env
@@ -0,0 +1,24 @@
+#!/bin/sh
+#
+# An test hook script to integrate with git to test fsmonitor.
+#
+# The hook is passed a version (currently 1) and a time in nanoseconds
+# formatted as a string and outputs to stdout all files that have been
+# modified since the given time. Paths must be relative to the root of
+# the working tree and separated by a single NUL.
+#
+#echo "$0 $*" >&2
+
+if test "$#" -ne 2
+then
+	echo "$0: exactly 2 arguments expected" >&2
+	exit 2
+fi
+
+if test "$1" != 1
+then
+	echo "Unsupported core.fsmonitor hook version." >&2
+	exit 1
+fi
+
+printf '%s\n' $FSMONITOR_LIST
diff --git a/third_party/git/t/t7519/fsmonitor-watchman b/third_party/git/t/t7519/fsmonitor-watchman
index 5514edcf68..264b9daf83 100755
--- a/third_party/git/t/t7519/fsmonitor-watchman
+++ b/third_party/git/t/t7519/fsmonitor-watchman
@@ -23,10 +23,10 @@ my ($version, $time) = @ARGV;
 
 if ($version == 1) {
 	# convert nanoseconds to seconds
-	$time = int $time / 1000000000;
+	# subtract one second to make sure watchman will return all changes
+	$time = int ($time / 1000000000) - 1;
 } else {
-	die "Unsupported query-fsmonitor hook version '$version'.\n" .
-	    "Falling back to scanning...\n";
+	exit 1;
 }
 
 my $git_work_tree;
@@ -54,18 +54,12 @@ sub launch_watchman {
 	#
 	# To accomplish this, we're using the "since" generator to use the
 	# recency index to select candidate nodes and "fields" to limit the
-	# output to file names only. Then we're using the "expression" term to
-	# further constrain the results.
-	#
-	# The category of transient files that we want to ignore will have a
-	# creation clock (cclock) newer than $time_t value and will also not
-	# currently exist.
+	# output to file names only.
 
 	my $query = <<"	END";
 		["query", "$git_work_tree", {
 			"since": $time,
-			"fields": ["name"],
-			"expression": ["not", ["allof", ["since", $time, "cclock"], ["not", "exists"]]]
+			"fields": ["name"]
 		}]
 	END
 	
diff --git a/third_party/git/t/t7519/fsmonitor-watchman-v2 b/third_party/git/t/t7519/fsmonitor-watchman-v2
new file mode 100755
index 0000000000..14ed0aa42d
--- /dev/null
+++ b/third_party/git/t/t7519/fsmonitor-watchman-v2
@@ -0,0 +1,173 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use IPC::Open2;
+
+# An example hook script to integrate Watchman
+# (https://facebook.github.io/watchman/) with git to speed up detecting
+# new and modified files.
+#
+# The hook is passed a version (currently 2) and last update token
+# formatted as a string and outputs to stdout a new update token and
+# all files that have been modified since the update token. Paths must
+# be relative to the root of the working tree and separated by a single NUL.
+#
+# To enable this hook, rename this file to "query-watchman" and set
+# 'git config core.fsmonitor .git/hooks/query-watchman'
+#
+my ($version, $last_update_token) = @ARGV;
+
+# Uncomment for debugging
+# print STDERR "$0 $version $last_update_token\n";
+
+# Check the hook interface version
+if ($version ne 2) {
+	die "Unsupported query-fsmonitor hook version '$version'.\n" .
+	    "Falling back to scanning...\n";
+}
+
+my $git_work_tree = get_working_dir();
+
+my $retry = 1;
+
+my $json_pkg;
+eval {
+	require JSON::XS;
+	$json_pkg = "JSON::XS";
+	1;
+} or do {
+	require JSON::PP;
+	$json_pkg = "JSON::PP";
+};
+
+launch_watchman();
+
+sub launch_watchman {
+	my $o = watchman_query();
+	if (is_work_tree_watched($o)) {
+		output_result($o->{clock}, @{$o->{files}});
+	}
+}
+
+sub output_result {
+	my ($clockid, @files) = @_;
+
+	# Uncomment for debugging watchman output
+	# open (my $fh, ">", ".git/watchman-output.out");
+	# binmode $fh, ":utf8";
+	# print $fh "$clockid\n@files\n";
+	# close $fh;
+
+	binmode STDOUT, ":utf8";
+	print $clockid;
+	print "\0";
+	local $, = "\0";
+	print @files;
+}
+
+sub watchman_clock {
+	my $response = qx/watchman clock "$git_work_tree"/;
+	die "Failed to get clock id on '$git_work_tree'.\n" .
+		"Falling back to scanning...\n" if $? != 0;
+
+	return $json_pkg->new->utf8->decode($response);
+}
+
+sub watchman_query {
+	my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty')
+	or die "open2() failed: $!\n" .
+	"Falling back to scanning...\n";
+
+	# In the query expression below we're asking for names of files that
+	# changed since $last_update_token but not from the .git folder.
+	#
+	# To accomplish this, we're using the "since" generator to use the
+	# recency index to select candidate nodes and "fields" to limit the
+	# output to file names only. Then we're using the "expression" term to
+	# further constrain the results.
+	if (substr($last_update_token, 0, 1) eq "c") {
+		$last_update_token = "\"$last_update_token\"";
+	}
+	my $query = <<"	END";
+		["query", "$git_work_tree", {
+			"since": $last_update_token,
+			"fields": ["name"],
+			"expression": ["not", ["dirname", ".git"]]
+		}]
+	END
+
+	# Uncomment for debugging the watchman query
+	# open (my $fh, ">", ".git/watchman-query.json");
+	# print $fh $query;
+	# close $fh;
+
+	print CHLD_IN $query;
+	close CHLD_IN;
+	my $response = do {local $/; <CHLD_OUT>};
+
+	# Uncomment for debugging the watch response
+	# open ($fh, ">", ".git/watchman-response.json");
+	# print $fh $response;
+	# close $fh;
+
+	die "Watchman: command returned no output.\n" .
+	"Falling back to scanning...\n" if $response eq "";
+	die "Watchman: command returned invalid output: $response\n" .
+	"Falling back to scanning...\n" unless $response =~ /^\{/;
+
+	return $json_pkg->new->utf8->decode($response);
+}
+
+sub is_work_tree_watched {
+	my ($output) = @_;
+	my $error = $output->{error};
+	if ($retry > 0 and $error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) {
+		$retry--;
+		my $response = qx/watchman watch "$git_work_tree"/;
+		die "Failed to make watchman watch '$git_work_tree'.\n" .
+		    "Falling back to scanning...\n" if $? != 0;
+		$output = $json_pkg->new->utf8->decode($response);
+		$error = $output->{error};
+		die "Watchman: $error.\n" .
+		"Falling back to scanning...\n" if $error;
+
+		# Uncomment for debugging watchman output
+		# open (my $fh, ">", ".git/watchman-output.out");
+		# close $fh;
+
+		# Watchman will always return all files on the first query so
+		# return the fast "everything is dirty" flag to git and do the
+		# Watchman query just to get it over with now so we won't pay
+		# the cost in git to look up each individual file.
+		my $o = watchman_clock();
+		$error = $output->{error};
+
+		die "Watchman: $error.\n" .
+		"Falling back to scanning...\n" if $error;
+
+		output_result($o->{clock}, ("/"));
+		$last_update_token = $o->{clock};
+
+		eval { launch_watchman() };
+		return 0;
+	}
+
+	die "Watchman: $error.\n" .
+	"Falling back to scanning...\n" if $error;
+
+	return 1;
+}
+
+sub get_working_dir {
+	my $working_dir;
+	if ($^O =~ 'msys' || $^O =~ 'cygwin') {
+		$working_dir = Win32::GetCwd();
+		$working_dir =~ tr/\\/\//;
+	} else {
+		require Cwd;
+		$working_dir = Cwd::cwd();
+	}
+
+	return $working_dir;
+}
diff --git a/third_party/git/t/t7526-commit-pathspec-file.sh b/third_party/git/t/t7526-commit-pathspec-file.sh
new file mode 100755
index 0000000000..5fbe47ebcd
--- /dev/null
+++ b/third_party/git/t/t7526-commit-pathspec-file.sh
@@ -0,0 +1,164 @@
+#!/bin/sh
+
+test_description='commit --pathspec-from-file'
+
+. ./test-lib.sh
+
+test_tick
+
+test_expect_success setup '
+	test_commit file0 &&
+	git tag checkpoint &&
+
+	echo A >fileA.t &&
+	echo B >fileB.t &&
+	echo C >fileC.t &&
+	echo D >fileD.t &&
+	git add fileA.t fileB.t fileC.t fileD.t
+'
+
+restore_checkpoint () {
+	git reset --soft checkpoint
+}
+
+verify_expect () {
+	git diff-tree --no-commit-id --name-status -r HEAD >actual &&
+	test_cmp expect actual
+}
+
+test_expect_success '--pathspec-from-file from stdin' '
+	restore_checkpoint &&
+
+	echo fileA.t | git commit --pathspec-from-file=- -m "Commit" &&
+
+	cat >expect <<-\EOF &&
+	A	fileA.t
+	EOF
+	verify_expect
+'
+
+test_expect_success '--pathspec-from-file from file' '
+	restore_checkpoint &&
+
+	echo fileA.t >list &&
+	git commit --pathspec-from-file=list -m "Commit" &&
+
+	cat >expect <<-\EOF &&
+	A	fileA.t
+	EOF
+	verify_expect
+'
+
+test_expect_success 'NUL delimiters' '
+	restore_checkpoint &&
+
+	printf "fileA.t\0fileB.t\0" | git commit --pathspec-from-file=- --pathspec-file-nul -m "Commit" &&
+
+	cat >expect <<-\EOF &&
+	A	fileA.t
+	A	fileB.t
+	EOF
+	verify_expect
+'
+
+test_expect_success 'LF delimiters' '
+	restore_checkpoint &&
+
+	printf "fileA.t\nfileB.t\n" | git commit --pathspec-from-file=- -m "Commit" &&
+
+	cat >expect <<-\EOF &&
+	A	fileA.t
+	A	fileB.t
+	EOF
+	verify_expect
+'
+
+test_expect_success 'no trailing delimiter' '
+	restore_checkpoint &&
+
+	printf "fileA.t\nfileB.t" | git commit --pathspec-from-file=- -m "Commit" &&
+
+	cat >expect <<-\EOF &&
+	A	fileA.t
+	A	fileB.t
+	EOF
+	verify_expect
+'
+
+test_expect_success 'CRLF delimiters' '
+	restore_checkpoint &&
+
+	printf "fileA.t\r\nfileB.t\r\n" | git commit --pathspec-from-file=- -m "Commit" &&
+
+	cat >expect <<-\EOF &&
+	A	fileA.t
+	A	fileB.t
+	EOF
+	verify_expect
+'
+
+test_expect_success 'quotes' '
+	restore_checkpoint &&
+
+	cat >list <<-\EOF &&
+	"file\101.t"
+	EOF
+
+	git commit --pathspec-from-file=list -m "Commit" &&
+
+	cat >expect <<-\EOF &&
+	A	fileA.t
+	EOF
+	verify_expect expect
+'
+
+test_expect_success 'quotes not compatible with --pathspec-file-nul' '
+	restore_checkpoint &&
+
+	cat >list <<-\EOF &&
+	"file\101.t"
+	EOF
+
+	test_must_fail git commit --pathspec-from-file=list --pathspec-file-nul -m "Commit"
+'
+
+test_expect_success 'only touches what was listed' '
+	restore_checkpoint &&
+
+	printf "fileB.t\nfileC.t\n" | git commit --pathspec-from-file=- -m "Commit" &&
+
+	cat >expect <<-\EOF &&
+	A	fileB.t
+	A	fileC.t
+	EOF
+	verify_expect
+'
+
+test_expect_success 'error conditions' '
+	restore_checkpoint &&
+	echo fileA.t >list &&
+	>empty_list &&
+
+	test_must_fail git commit --pathspec-from-file=list --interactive -m "Commit" 2>err &&
+	test_i18ngrep -e "--pathspec-from-file is incompatible with --interactive/--patch" err &&
+
+	test_must_fail git commit --pathspec-from-file=list --patch -m "Commit" 2>err &&
+	test_i18ngrep -e "--pathspec-from-file is incompatible with --interactive/--patch" err &&
+
+	test_must_fail git commit --pathspec-from-file=list --all -m "Commit" 2>err &&
+	test_i18ngrep -e "--pathspec-from-file with -a does not make sense" err &&
+
+	test_must_fail git commit --pathspec-from-file=list -m "Commit" -- fileA.t 2>err &&
+	test_i18ngrep -e "--pathspec-from-file is incompatible with pathspec arguments" err &&
+
+	test_must_fail git commit --pathspec-file-nul -m "Commit" 2>err &&
+	test_i18ngrep -e "--pathspec-file-nul requires --pathspec-from-file" err &&
+
+	test_must_fail git commit --pathspec-from-file=empty_list --include -m "Commit" 2>err &&
+	test_i18ngrep -e "No paths with --include/--only does not make sense." err &&
+
+	test_must_fail git commit --pathspec-from-file=empty_list --only -m "Commit" 2>err &&
+	test_i18ngrep -e "No paths with --include/--only does not make sense." err
+'
+
+test_done
diff --git a/third_party/git/t/t7600-merge.sh b/third_party/git/t/t7600-merge.sh
index 132608879a..1c85f75555 100755
--- a/third_party/git/t/t7600-merge.sh
+++ b/third_party/git/t/t7600-merge.sh
@@ -29,15 +29,19 @@ Testing basic merge operations/option parsing.
 . ./test-lib.sh
 . "$TEST_DIRECTORY"/lib-gpg.sh
 
-printf '%s\n' 1 2 3 4 5 6 7 8 9 >file
-printf '%s\n' '1 X' 2 3 4 5 6 7 8 9 >file.1
-printf '%s\n' 1 2 3 4 '5 X' 6 7 8 9 >file.5
-printf '%s\n' 1 2 3 4 5 6 7 8 '9 X' >file.9
-printf '%s\n' 1 2 3 4 5 6 7 8 '9 Y' >file.9y
-printf '%s\n' '1 X' 2 3 4 5 6 7 8 9 >result.1
-printf '%s\n' '1 X' 2 3 4 '5 X' 6 7 8 9 >result.1-5
-printf '%s\n' '1 X' 2 3 4 '5 X' 6 7 8 '9 X' >result.1-5-9
-printf '%s\n' 1 2 3 4 5 6 7 8 '9 Z' >result.9z
+test_write_lines 1 2 3 4 5 6 7 8 9 >file
+cp file file.orig
+test_write_lines '1 X' 2 3 4 5 6 7 8 9 >file.1
+test_write_lines 1 2 '3 X' 4 5 6 7 8 9 >file.3
+test_write_lines 1 2 3 4 '5 X' 6 7 8 9 >file.5
+test_write_lines 1 2 3 4 5 6 7 8 '9 X' >file.9
+test_write_lines 1 2 3 4 5 6 7 8 '9 Y' >file.9y
+test_write_lines '1 X' 2 3 4 5 6 7 8 9 >result.1
+test_write_lines '1 X' 2 3 4 '5 X' 6 7 8 9 >result.1-5
+test_write_lines '1 X' 2 3 4 5 6 7 8 '9 X' >result.1-9
+test_write_lines '1 X' 2 3 4 '5 X' 6 7 8 '9 X' >result.1-5-9
+test_write_lines '1 X' 2 '3 X' 4 '5 X' 6 7 8 '9 X' >result.1-3-5-9
+test_write_lines 1 2 3 4 5 6 7 8 '9 Z' >result.9z
 
 create_merge_msgs () {
 	echo "Merge tag 'c2'" >msg.1-5 &&
@@ -81,7 +85,7 @@ verify_head () {
 }
 
 verify_parents () {
-	printf '%s\n' "$@" >parents.expected &&
+	test_write_lines "$@" >parents.expected &&
 	>parents.actual &&
 	i=1 &&
 	while test $i -le $#
@@ -95,7 +99,7 @@ verify_parents () {
 }
 
 verify_mergeheads () {
-	printf '%s\n' "$@" >mergehead.expected &&
+	test_write_lines "$@" >mergehead.expected &&
 	while read sha1 rest
 	do
 		git rev-parse $sha1
@@ -242,7 +246,7 @@ test_expect_success 'merge --squash c3 with c7' '
 	#	file
 	EOF
 	git cat-file commit HEAD >raw &&
-	sed -e '1,/^$/d' raw >actual &&
+	sed -e "1,/^$/d" raw >actual &&
 	test_cmp expect actual
 '
 
@@ -264,7 +268,7 @@ test_expect_success 'merge c3 with c7 with commit.cleanup = scissors' '
 	#	file
 	EOF
 	git cat-file commit HEAD >raw &&
-	sed -e '1,/^$/d' raw >actual &&
+	sed -e "1,/^$/d" raw >actual &&
 	test_i18ncmp expect actual
 '
 
@@ -288,7 +292,7 @@ test_expect_success 'merge c3 with c7 with --squash commit.cleanup = scissors' '
 	#	file
 	EOF
 	git cat-file commit HEAD >raw &&
-	sed -e '1,/^$/d' raw >actual &&
+	sed -e "1,/^$/d" raw >actual &&
 	test_i18ncmp expect actual
 '
 
@@ -675,6 +679,134 @@ test_expect_success 'refresh the index before merging' '
 	git merge c3
 '
 
+test_expect_success 'merge with --autostash' '
+	git reset --hard c1 &&
+	git merge-file file file.orig file.9 &&
+	git merge --autostash c2 2>err &&
+	test_i18ngrep "Applied autostash." err &&
+	git show HEAD:file >merge-result &&
+	test_cmp result.1-5 merge-result &&
+	test_cmp result.1-5-9 file
+'
+
+test_expect_success 'merge with merge.autoStash' '
+	test_config merge.autoStash true &&
+	git reset --hard c1 &&
+	git merge-file file file.orig file.9 &&
+	git merge c2 2>err &&
+	test_i18ngrep "Applied autostash." err &&
+	git show HEAD:file >merge-result &&
+	test_cmp result.1-5 merge-result &&
+	test_cmp result.1-5-9 file
+'
+
+test_expect_success 'fast-forward merge with --autostash' '
+	git reset --hard c0 &&
+	git merge-file file file.orig file.5 &&
+	git merge --autostash c1 2>err &&
+	test_i18ngrep "Applied autostash." err &&
+	test_cmp result.1-5 file
+'
+
+test_expect_success 'octopus merge with --autostash' '
+	git reset --hard c1 &&
+	git merge-file file file.orig file.3 &&
+	git merge --autostash c2 c3 2>err &&
+	test_i18ngrep "Applied autostash." err &&
+	git show HEAD:file >merge-result &&
+	test_cmp result.1-5-9 merge-result &&
+	test_cmp result.1-3-5-9 file
+'
+
+test_expect_success 'conflicted merge with --autostash, --abort restores stash' '
+	git reset --hard c3 &&
+	cp file.1 file &&
+	test_must_fail git merge --autostash c7 &&
+	git merge --abort 2>err &&
+	test_i18ngrep "Applied autostash." err &&
+	test_cmp file.1 file
+'
+
+test_expect_success 'completed merge (git commit) with --no-commit and --autostash' '
+	git reset --hard c1 &&
+	git merge-file file file.orig file.9 &&
+	git diff >expect &&
+	git merge --no-commit --autostash c2 &&
+	git stash show -p MERGE_AUTOSTASH >actual &&
+	test_cmp expect actual &&
+	git commit 2>err &&
+	test_i18ngrep "Applied autostash." err &&
+	git show HEAD:file >merge-result &&
+	test_cmp result.1-5 merge-result &&
+	test_cmp result.1-5-9 file
+'
+
+test_expect_success 'completed merge (git merge --continue) with --no-commit and --autostash' '
+	git reset --hard c1 &&
+	git merge-file file file.orig file.9 &&
+	git diff >expect &&
+	git merge --no-commit --autostash c2 &&
+	git stash show -p MERGE_AUTOSTASH >actual &&
+	test_cmp expect actual &&
+	git merge --continue 2>err &&
+	test_i18ngrep "Applied autostash." err &&
+	git show HEAD:file >merge-result &&
+	test_cmp result.1-5 merge-result &&
+	test_cmp result.1-5-9 file
+'
+
+test_expect_success 'aborted merge (merge --abort) with --no-commit and --autostash' '
+	git reset --hard c1 &&
+	git merge-file file file.orig file.9 &&
+	git diff >expect &&
+	git merge --no-commit --autostash c2 &&
+	git stash show -p MERGE_AUTOSTASH >actual &&
+	test_cmp expect actual &&
+	git merge --abort 2>err &&
+	test_i18ngrep "Applied autostash." err &&
+	git diff >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'aborted merge (reset --hard) with --no-commit and --autostash' '
+	git reset --hard c1 &&
+	git merge-file file file.orig file.9 &&
+	git diff >expect &&
+	git merge --no-commit --autostash c2 &&
+	git stash show -p MERGE_AUTOSTASH >actual &&
+	test_cmp expect actual &&
+	git reset --hard 2>err &&
+	test_i18ngrep "Autostash exists; creating a new stash entry." err &&
+	git diff --exit-code
+'
+
+test_expect_success 'quit merge with --no-commit and --autostash' '
+	git reset --hard c1 &&
+	git merge-file file file.orig file.9 &&
+	git diff >expect &&
+	git merge --no-commit --autostash c2 &&
+	git stash show -p MERGE_AUTOSTASH >actual &&
+	test_cmp expect actual &&
+	git diff HEAD >expect &&
+	git merge --quit 2>err &&
+	test_i18ngrep "Autostash exists; creating a new stash entry." err &&
+	git diff HEAD >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'merge with conflicted --autostash changes' '
+	git reset --hard c1 &&
+	git merge-file file file.orig file.9y &&
+	git diff >expect &&
+	test_when_finished "test_might_fail git stash drop" &&
+	git merge --autostash c3 2>err &&
+	test_i18ngrep "Applying autostash resulted in conflicts." err &&
+	git show HEAD:file >merge-result &&
+	test_cmp result.1-9 merge-result &&
+	git stash show -p >actual &&
+	test_cmp expect actual
+'
+
 cat >expected.branch <<\EOF
 Merge branch 'c5-branch' (early part)
 EOF
diff --git a/third_party/git/t/t7601-merge-pull-config.sh b/third_party/git/t/t7601-merge-pull-config.sh
index c6c44ec570..c5c4ea5fc0 100755
--- a/third_party/git/t/t7601-merge-pull-config.sh
+++ b/third_party/git/t/t7601-merge-pull-config.sh
@@ -27,6 +27,63 @@ 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=true' '
+	git reset --hard c0 &&
+	test_config pull.ff true &&
+	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 given' '
+	git reset --hard c0 &&
+	git pull --ff . c1 2>err &&
+	test_i18ngrep ! "Pulling without specifying how to reconcile" err
+'
+
+test_expect_success 'pull.rebase not set and --no-ff given' '
+	git reset --hard c0 &&
+	git pull --no-ff . 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 &&
diff --git a/third_party/git/t/t7612-merge-verify-signatures.sh b/third_party/git/t/t7612-merge-verify-signatures.sh
index d99218a725..a426f3a89a 100755
--- a/third_party/git/t/t7612-merge-verify-signatures.sh
+++ b/third_party/git/t/t7612-merge-verify-signatures.sh
@@ -66,6 +66,20 @@ test_expect_success GPG 'merge commit with untrusted signature with verification
 	test_i18ngrep "has an untrusted GPG signature" mergeerror
 '
 
+test_expect_success GPG 'merge commit with untrusted signature with verification and high minTrustLevel' '
+	test_when_finished "git reset --hard && git checkout initial" &&
+	test_config gpg.minTrustLevel marginal &&
+	test_must_fail git merge --ff-only --verify-signatures side-untrusted 2>mergeerror &&
+	test_i18ngrep "has an untrusted GPG signature" mergeerror
+'
+
+test_expect_success GPG 'merge commit with untrusted signature with verification and low minTrustLevel' '
+	test_when_finished "git reset --hard && git checkout initial" &&
+	test_config gpg.minTrustLevel undefined &&
+	git merge --ff-only --verify-signatures side-untrusted >mergeoutput &&
+	test_i18ngrep "has a good GPG signature" mergeoutput
+'
+
 test_expect_success GPG 'merge commit with untrusted signature with merge.verifySignatures=true' '
 	test_when_finished "git reset --hard && git checkout initial" &&
 	test_config merge.verifySignatures true &&
@@ -73,6 +87,14 @@ test_expect_success GPG 'merge commit with untrusted signature with merge.verify
 	test_i18ngrep "has an untrusted GPG signature" mergeerror
 '
 
+test_expect_success GPG 'merge commit with untrusted signature with merge.verifySignatures=true and minTrustLevel' '
+	test_when_finished "git reset --hard && git checkout initial" &&
+	test_config merge.verifySignatures true &&
+	test_config gpg.minTrustLevel marginal &&
+	test_must_fail git merge --ff-only side-untrusted 2>mergeerror &&
+	test_i18ngrep "has an untrusted GPG signature" mergeerror
+'
+
 test_expect_success GPG 'merge signed commit with verification' '
 	test_when_finished "git reset --hard && git checkout initial" &&
 	git merge --verbose --ff-only --verify-signatures side-signed >mergeoutput &&
diff --git a/third_party/git/t/t7700-repack.sh b/third_party/git/t/t7700-repack.sh
index 4e855bc21b..25b235c063 100755
--- a/third_party/git/t/t7700-repack.sh
+++ b/third_party/git/t/t7700-repack.sh
@@ -4,129 +4,104 @@ test_description='git repack works correctly'
 
 . ./test-lib.sh
 
-commit_and_pack() {
-	test_commit "$@" >/dev/null &&
-	SHA1=$(git pack-objects --all --unpacked --incremental .git/objects/pack/pack </dev/null) &&
-	echo pack-${SHA1}.pack
+commit_and_pack () {
+	test_commit "$@" 1>&2 &&
+	incrpackid=$(git pack-objects --all --unpacked --incremental .git/objects/pack/pack </dev/null) &&
+	echo pack-${incrpackid}.pack
+}
+
+test_no_missing_in_packs () {
+	myidx=$(ls -1 .git/objects/pack/*.idx) &&
+	test_path_is_file "$myidx" &&
+	git verify-pack -v alt_objects/pack/*.idx >orig.raw &&
+	sed -n -e "s/^\($OID_REGEX\).*/\1/p" orig.raw | sort >orig &&
+	git verify-pack -v $myidx >dest.raw &&
+	cut -d" " -f1 dest.raw | sort >dest &&
+	comm -23 orig dest >missing &&
+	test_must_be_empty missing
+}
+
+# we expect $packid and $oid to be defined
+test_has_duplicate_object () {
+	want_duplicate_object="$1"
+	found_duplicate_object=false
+	for p in .git/objects/pack/*.idx
+	do
+		idx=$(basename $p)
+		test "pack-$packid.idx" = "$idx" && continue
+		git verify-pack -v $p >packlist || return $?
+		if grep "^$oid" packlist
+		then
+			found_duplicate_object=true
+			echo "DUPLICATE OBJECT FOUND"
+			break
+		fi
+	done &&
+	test "$want_duplicate_object" = "$found_duplicate_object"
 }
 
 test_expect_success 'objects in packs marked .keep are not repacked' '
-	echo content1 > file1 &&
-	echo content2 > file2 &&
+	echo content1 >file1 &&
+	echo content2 >file2 &&
 	git add . &&
 	test_tick &&
 	git commit -m initial_commit &&
 	# Create two packs
 	# The first pack will contain all of the objects except one
-	git rev-list --objects --all | grep -v file2 |
-		git pack-objects pack > /dev/null &&
+	git rev-list --objects --all >objs &&
+	grep -v file2 objs | git pack-objects pack &&
 	# The second pack will contain the excluded object
-	packsha1=$(git rev-list --objects --all | grep file2 |
-		git pack-objects pack) &&
-	>pack-$packsha1.keep &&
-	objsha1=$(git verify-pack -v pack-$packsha1.idx | head -n 1 |
-		sed -e "s/^\([0-9a-f]\{40\}\).*/\1/") &&
+	packid=$(grep file2 objs | git pack-objects pack) &&
+	>pack-$packid.keep &&
+	git verify-pack -v pack-$packid.idx >packlist &&
+	oid=$(head -n 1 packlist | sed -e "s/^\($OID_REGEX\).*/\1/") &&
 	mv pack-* .git/objects/pack/ &&
 	git repack -A -d -l &&
 	git prune-packed &&
-	for p in .git/objects/pack/*.idx; do
-		idx=$(basename $p)
-		test "pack-$packsha1.idx" = "$idx" && continue
-		if git verify-pack -v $p | egrep "^$objsha1"; then
-			found_duplicate_object=1
-			echo "DUPLICATE OBJECT FOUND"
-			break
-		fi
-	done &&
-	test -z "$found_duplicate_object"
+	test_has_duplicate_object false
 '
 
 test_expect_success 'writing bitmaps via command-line can duplicate .keep objects' '
-	# build on $objsha1, $packsha1, and .keep state from previous
+	# build on $oid, $packid, and .keep state from previous
 	git repack -Adbl &&
-	test_when_finished "found_duplicate_object=" &&
-	for p in .git/objects/pack/*.idx; do
-		idx=$(basename $p)
-		test "pack-$packsha1.idx" = "$idx" && continue
-		if git verify-pack -v $p | egrep "^$objsha1"; then
-			found_duplicate_object=1
-			echo "DUPLICATE OBJECT FOUND"
-			break
-		fi
-	done &&
-	test "$found_duplicate_object" = 1
+	test_has_duplicate_object true
 '
 
 test_expect_success 'writing bitmaps via config can duplicate .keep objects' '
-	# build on $objsha1, $packsha1, and .keep state from previous
+	# build on $oid, $packid, and .keep state from previous
 	git -c repack.writebitmaps=true repack -Adl &&
-	test_when_finished "found_duplicate_object=" &&
-	for p in .git/objects/pack/*.idx; do
-		idx=$(basename $p)
-		test "pack-$packsha1.idx" = "$idx" && continue
-		if git verify-pack -v $p | egrep "^$objsha1"; then
-			found_duplicate_object=1
-			echo "DUPLICATE OBJECT FOUND"
-			break
-		fi
-	done &&
-	test "$found_duplicate_object" = 1
+	test_has_duplicate_object true
 '
 
 test_expect_success 'loose objects in alternate ODB are not repacked' '
 	mkdir alt_objects &&
-	echo $(pwd)/alt_objects > .git/objects/info/alternates &&
-	echo content3 > file3 &&
-	objsha1=$(GIT_OBJECT_DIRECTORY=alt_objects git hash-object -w file3) &&
+	echo $(pwd)/alt_objects >.git/objects/info/alternates &&
+	echo content3 >file3 &&
+	oid=$(GIT_OBJECT_DIRECTORY=alt_objects git hash-object -w file3) &&
 	git add file3 &&
 	test_tick &&
 	git commit -m commit_file3 &&
 	git repack -a -d -l &&
 	git prune-packed &&
-	for p in .git/objects/pack/*.idx; do
-		if git verify-pack -v $p | egrep "^$objsha1"; then
-			found_duplicate_object=1
-			echo "DUPLICATE OBJECT FOUND"
-			break
-		fi
-	done &&
-	test -z "$found_duplicate_object"
+	test_has_duplicate_object false
 '
 
 test_expect_success 'packed obs in alt ODB are repacked even when local repo is packless' '
 	mkdir alt_objects/pack &&
 	mv .git/objects/pack/* alt_objects/pack &&
 	git repack -a &&
-	myidx=$(ls -1 .git/objects/pack/*.idx) &&
-	test -f "$myidx" &&
-	for p in alt_objects/pack/*.idx; do
-		git verify-pack -v $p | sed -n -e "/^[0-9a-f]\{40\}/p"
-	done | while read sha1 rest; do
-		if ! ( git verify-pack -v $myidx | grep "^$sha1" ); then
-			echo "Missing object in local pack: $sha1"
-			return 1
-		fi
-	done
+	test_no_missing_in_packs
 '
 
 test_expect_success 'packed obs in alt ODB are repacked when local repo has packs' '
 	rm -f .git/objects/pack/* &&
-	echo new_content >> file1 &&
+	echo new_content >>file1 &&
 	git add file1 &&
 	test_tick &&
 	git commit -m more_content &&
 	git repack &&
 	git repack -a -d &&
-	myidx=$(ls -1 .git/objects/pack/*.idx) &&
-	test -f "$myidx" &&
-	for p in alt_objects/pack/*.idx; do
-		git verify-pack -v $p | sed -n -e "/^[0-9a-f]\{40\}/p"
-	done | while read sha1 rest; do
-		if ! ( git verify-pack -v $myidx | grep "^$sha1" ); then
-			echo "Missing object in local pack: $sha1"
-			return 1
-		fi
-	done
+	test_no_missing_in_packs
 '
 
 test_expect_success 'packed obs in alternate ODB kept pack are repacked' '
@@ -134,7 +109,7 @@ test_expect_success 'packed obs in alternate ODB kept pack are repacked' '
 	for p in alt_objects/pack/*.pack
 	do
 		base_name=$(basename $p .pack) &&
-		if test -f alt_objects/pack/$base_name.keep
+		if test_path_is_file alt_objects/pack/$base_name.keep
 		then
 			rm alt_objects/pack/$base_name.keep
 		else
@@ -142,22 +117,13 @@ test_expect_success 'packed obs in alternate ODB kept pack are repacked' '
 		fi
 	done &&
 	git repack -a -d &&
-	myidx=$(ls -1 .git/objects/pack/*.idx) &&
-	test -f "$myidx" &&
-	for p in alt_objects/pack/*.idx; do
-		git verify-pack -v $p | sed -n -e "/^[0-9a-f]\{40\}/p"
-	done | while read sha1 rest; do
-		if ! ( git verify-pack -v $myidx | grep "^$sha1" ); then
-			echo "Missing object in local pack: $sha1"
-			return 1
-		fi
-	done
+	test_no_missing_in_packs
 '
 
 test_expect_success 'packed unreachable obs in alternate ODB are not loosened' '
 	rm -f alt_objects/pack/*.keep &&
 	mv .git/objects/pack/* alt_objects/pack/ &&
-	csha1=$(git rev-parse HEAD^{commit}) &&
+	coid=$(git rev-parse HEAD^{commit}) &&
 	git reset --hard HEAD^ &&
 	test_tick &&
 	git reflog expire --expire=$test_tick --expire-unreachable=$test_tick --all &&
@@ -167,15 +133,15 @@ test_expect_success 'packed unreachable obs in alternate ODB are not loosened' '
 	    --unpack-unreachable </dev/null pack &&
 	rm -f .git/objects/pack/* &&
 	mv pack-* .git/objects/pack/ &&
-	test 0 = $(git verify-pack -v -- .git/objects/pack/*.idx |
-		egrep "^$csha1 " | sort | uniq | wc -l) &&
-	echo > .git/objects/info/alternates &&
-	test_must_fail git show $csha1
+	git verify-pack -v -- .git/objects/pack/*.idx >packlist &&
+	! grep "^$coid " packlist &&
+	echo >.git/objects/info/alternates &&
+	test_must_fail git show $coid
 '
 
 test_expect_success 'local packed unreachable obs that exist in alternate ODB are not loosened' '
-	echo $(pwd)/alt_objects > .git/objects/info/alternates &&
-	echo "$csha1" | git pack-objects --non-empty --all --reflog pack &&
+	echo $(pwd)/alt_objects >.git/objects/info/alternates &&
+	echo "$coid" | git pack-objects --non-empty --all --reflog pack &&
 	rm -f .git/objects/pack/* &&
 	mv pack-* .git/objects/pack/ &&
 	# The pack-objects call on the next line is equivalent to
@@ -184,10 +150,10 @@ test_expect_success 'local packed unreachable obs that exist in alternate ODB ar
 	    --unpack-unreachable </dev/null pack &&
 	rm -f .git/objects/pack/* &&
 	mv pack-* .git/objects/pack/ &&
-	test 0 = $(git verify-pack -v -- .git/objects/pack/*.idx |
-		egrep "^$csha1 " | sort | uniq | wc -l) &&
-	echo > .git/objects/info/alternates &&
-	test_must_fail git show $csha1
+	git verify-pack -v -- .git/objects/pack/*.idx >packlist &&
+	! grep "^$coid " &&
+	echo >.git/objects/info/alternates &&
+	test_must_fail git show $coid
 '
 
 test_expect_success 'objects made unreachable by grafts only are kept' '
@@ -196,7 +162,7 @@ test_expect_success 'objects made unreachable by grafts only are kept' '
 	H0=$(git rev-parse HEAD) &&
 	H1=$(git rev-parse HEAD^) &&
 	H2=$(git rev-parse HEAD^^) &&
-	echo "$H0 $H2" > .git/info/grafts &&
+	echo "$H0 $H2" >.git/info/grafts &&
 	git reflog expire --expire=$test_tick --expire-unreachable=$test_tick --all &&
 	git repack -a -d &&
 	git cat-file -t $H1
@@ -235,7 +201,7 @@ test_expect_success 'incremental repack does not complain' '
 
 test_expect_success 'bitmaps can be disabled on bare repos' '
 	git -c repack.writeBitmaps=false -C bare.git repack -ad &&
-	bitmap=$(ls bare.git/objects/pack/*.bitmap 2>/dev/null || :) &&
+	bitmap=$(ls bare.git/objects/pack/*.bitmap || :) &&
 	test -z "$bitmap"
 '
 
diff --git a/third_party/git/t/t7800-difftool.sh b/third_party/git/t/t7800-difftool.sh
index 6bac9ed180..524f30f7dc 100755
--- a/third_party/git/t/t7800-difftool.sh
+++ b/third_party/git/t/t7800-difftool.sh
@@ -125,15 +125,14 @@ test_expect_success 'difftool stops on error with --trust-exit-code' '
 	test_when_finished "rm -f for-diff .git/fail-right-file" &&
 	test_when_finished "git reset -- for-diff" &&
 	write_script .git/fail-right-file <<-\EOF &&
-	echo "$2"
+	echo failed
 	exit 1
 	EOF
 	>for-diff &&
 	git add for-diff &&
-	echo file >expect &&
 	test_must_fail git difftool -y --trust-exit-code \
 		--extcmd .git/fail-right-file branch >actual &&
-	test_cmp expect actual
+	test_line_count = 1 actual
 '
 
 test_expect_success 'difftool honors exit status if command not found' '
@@ -721,6 +720,14 @@ test_expect_success SYMLINKS 'difftool --dir-diff handles modified symlinks' '
 	test_cmp expect actual
 '
 
+test_expect_success 'add -N and difftool -d' '
+	test_when_finished git reset --hard &&
+
+	test_write_lines A B C >intent-to-add &&
+	git add -N intent-to-add &&
+	git difftool --dir-diff --extcmd ls
+'
+
 test_expect_success 'outside worktree' '
 	echo 1 >1 &&
 	echo 2 >2 &&
diff --git a/third_party/git/t/t7810-grep.sh b/third_party/git/t/t7810-grep.sh
index 7d7b396c23..991d5bd9c0 100755
--- a/third_party/git/t/t7810-grep.sh
+++ b/third_party/git/t/t7810-grep.sh
@@ -72,6 +72,11 @@ test_expect_success setup '
 	# Still a no-op.
 	function dummy() {}
 	EOF
+	if test_have_prereq FUNNYNAMES
+	then
+		echo unusual >"\"unusual\" pathname" &&
+		echo unusual >"t/nested \"unusual\" pathname"
+	fi &&
 	git add . &&
 	test_tick &&
 	git commit -m initial
@@ -481,6 +486,48 @@ do
 		git grep --count -h -e b $H -- ab >actual &&
 		test_cmp expected actual
 	'
+
+	test_expect_success FUNNYNAMES "grep $L should quote unusual pathnames" '
+		cat >expected <<-EOF &&
+		${HC}"\"unusual\" pathname":unusual
+		${HC}"t/nested \"unusual\" pathname":unusual
+		EOF
+		git grep unusual $H >actual &&
+		test_cmp expected actual
+	'
+
+	test_expect_success FUNNYNAMES "grep $L in subdir should quote unusual relative pathnames" '
+		cat >expected <<-EOF &&
+		${HC}"nested \"unusual\" pathname":unusual
+		EOF
+		(
+			cd t &&
+			git grep unusual $H
+		) >actual &&
+		test_cmp expected actual
+	'
+
+	test_expect_success FUNNYNAMES "grep -z $L with unusual pathnames" '
+		cat >expected <<-EOF &&
+		${HC}"unusual" pathname:unusual
+		${HC}t/nested "unusual" pathname:unusual
+		EOF
+		git grep -z unusual $H >actual &&
+		tr "\0" ":" <actual >actual-replace-null &&
+		test_cmp expected actual-replace-null
+	'
+
+	test_expect_success FUNNYNAMES "grep -z $L in subdir with unusual relative pathnames" '
+		cat >expected <<-EOF &&
+		${HC}nested "unusual" pathname:unusual
+		EOF
+		(
+			cd t &&
+			git grep -z unusual $H
+		) >actual &&
+		tr "\0" ":" <actual >actual-replace-null &&
+		test_cmp expected actual-replace-null
+	'
 done
 
 cat >expected <<EOF
diff --git a/third_party/git/t/t7811-grep-open.sh b/third_party/git/t/t7811-grep-open.sh
index d1ebfd88c7..a98785da79 100755
--- a/third_party/git/t/t7811-grep-open.sh
+++ b/third_party/git/t/t7811-grep-open.sh
@@ -113,7 +113,6 @@ test_expect_success 'modified file' '
 	subdir/grep.c
 	unrelated
 	EOF
-	>empty &&
 
 	echo "enum grep_pat_token" >unrelated &&
 	test_when_finished "git checkout HEAD unrelated" &&
diff --git a/third_party/git/t/t7812-grep-icase-non-ascii.sh b/third_party/git/t/t7812-grep-icase-non-ascii.sh
index 0c685d3598..03dba6685a 100755
--- a/third_party/git/t/t7812-grep-icase-non-ascii.sh
+++ b/third_party/git/t/t7812-grep-icase-non-ascii.sh
@@ -53,4 +53,35 @@ test_expect_success REGEX_LOCALE 'pickaxe -i on non-ascii' '
 	test_cmp expected actual
 '
 
+test_expect_success GETTEXT_LOCALE,LIBPCRE2 'PCRE v2: setup invalid UTF-8 data' '
+	printf "\\200\\n" >invalid-0x80 &&
+	echo "รฆvar" >expected &&
+	cat expected >>invalid-0x80 &&
+	git add invalid-0x80
+'
+
+test_expect_success GETTEXT_LOCALE,LIBPCRE2 'PCRE v2: grep ASCII from invalid UTF-8 data' '
+	git grep -h "var" invalid-0x80 >actual &&
+	test_cmp expected actual &&
+	git grep -h "(*NO_JIT)var" invalid-0x80 >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success GETTEXT_LOCALE,LIBPCRE2 'PCRE v2: grep non-ASCII from invalid UTF-8 data' '
+	git grep -h "รฆ" invalid-0x80 >actual &&
+	test_cmp expected actual &&
+	git grep -h "(*NO_JIT)รฆ" invalid-0x80 >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success GETTEXT_LOCALE,LIBPCRE2 'PCRE v2: grep non-ASCII from invalid UTF-8 data with -i' '
+	test_might_fail git grep -hi "ร†" invalid-0x80 >actual &&
+	if test -s actual
+	then
+	    test_cmp expected actual
+	fi &&
+	test_must_fail git grep -hi "(*NO_JIT)ร†" invalid-0x80 >actual &&
+	! test_cmp expected actual
+'
+
 test_done
diff --git a/third_party/git/t/t7814-grep-recurse-submodules.sh b/third_party/git/t/t7814-grep-recurse-submodules.sh
index a11366b4ce..828cb3ba58 100755
--- a/third_party/git/t/t7814-grep-recurse-submodules.sh
+++ b/third_party/git/t/t7814-grep-recurse-submodules.sh
@@ -345,7 +345,16 @@ test_incompatible_with_recurse_submodules ()
 }
 
 test_incompatible_with_recurse_submodules --untracked
-test_incompatible_with_recurse_submodules --no-index
+
+test_expect_success 'grep --recurse-submodules --no-index ignores --recurse-submodules' '
+	git grep --recurse-submodules --no-index -e "^(.|.)[\d]" >actual &&
+	cat >expect <<-\EOF &&
+	a:(1|2)d(3|4)
+	submodule/a:(1|2)d(3|4)
+	submodule/sub/a:(1|2)d(3|4)
+	EOF
+	test_cmp expect actual
+'
 
 test_expect_success 'grep --recurse-submodules should pass the pattern type along' '
 	# Fixed
@@ -408,4 +417,25 @@ test_expect_success 'grep --recurse-submodules with submodules without .gitmodul
 	test_cmp expect actual
 '
 
+reset_and_clean () {
+	git reset --hard &&
+	git clean -fd &&
+	git submodule foreach --recursive 'git reset --hard' &&
+	git submodule foreach --recursive 'git clean -fd'
+}
+
+test_expect_success 'grep --recurse-submodules without --cached considers worktree modifications' '
+	reset_and_clean &&
+	echo "A modified line in submodule" >>submodule/a &&
+	echo "submodule/a:A modified line in submodule" >expect &&
+	git grep --recurse-submodules "A modified line in submodule" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'grep --recurse-submodules with --cached ignores worktree modifications' '
+	reset_and_clean &&
+	echo "A modified line in submodule" >>submodule/a &&
+	test_must_fail git grep --recurse-submodules --cached "A modified line in submodule" >actual 2>&1 &&
+	test_must_be_empty actual
+'
 test_done
diff --git a/third_party/git/t/t7008-grep-binary.sh b/third_party/git/t/t7815-grep-binary.sh
index 2d87c49b75..90ebb64f46 100755
--- a/third_party/git/t/t7008-grep-binary.sh
+++ b/third_party/git/t/t7815-grep-binary.sh
@@ -4,41 +4,6 @@ test_description='git grep in binary files'
 
 . ./test-lib.sh
 
-nul_match () {
-	matches=$1
-	flags=$2
-	pattern=$3
-	pattern_human=$(echo "$pattern" | sed 's/Q/<NUL>/g')
-
-	if test "$matches" = 1
-	then
-		test_expect_success "git grep -f f $flags '$pattern_human' a" "
-			printf '$pattern' | q_to_nul >f &&
-			git grep -f f $flags a
-		"
-	elif test "$matches" = 0
-	then
-		test_expect_success "git grep -f f $flags '$pattern_human' a" "
-			printf '$pattern' | q_to_nul >f &&
-			test_must_fail git grep -f f $flags a
-		"
-	elif test "$matches" = T1
-	then
-		test_expect_failure "git grep -f f $flags '$pattern_human' a" "
-			printf '$pattern' | q_to_nul >f &&
-			git grep -f f $flags a
-		"
-	elif test "$matches" = T0
-	then
-		test_expect_failure "git grep -f f $flags '$pattern_human' a" "
-			printf '$pattern' | q_to_nul >f &&
-			test_must_fail git grep -f f $flags a
-		"
-	else
-		test_expect_success "PANIC: Test framework error. Unknown matches value $matches" 'false'
-	fi
-}
-
 test_expect_success 'setup' "
 	echo 'binaryQfileQm[*]cQ*รฆQรฐ' | q_to_nul >a &&
 	git add a &&
@@ -102,72 +67,6 @@ test_expect_failure 'git grep .fi a' '
 	git grep .fi a
 '
 
-nul_match 1 '-F' 'yQf'
-nul_match 0 '-F' 'yQx'
-nul_match 1 '-Fi' 'YQf'
-nul_match 0 '-Fi' 'YQx'
-nul_match 1 '' 'yQf'
-nul_match 0 '' 'yQx'
-nul_match 1 '' 'รฆQรฐ'
-nul_match 1 '-F' 'eQm[*]c'
-nul_match 1 '-Fi' 'EQM[*]C'
-
-# Regex patterns that would match but shouldn't with -F
-nul_match 0 '-F' 'yQ[f]'
-nul_match 0 '-F' '[y]Qf'
-nul_match 0 '-Fi' 'YQ[F]'
-nul_match 0 '-Fi' '[Y]QF'
-nul_match 0 '-F' 'รฆQ[รฐ]'
-nul_match 0 '-F' '[รฆ]Qรฐ'
-nul_match 0 '-Fi' 'ร†Q[ร]'
-nul_match 0 '-Fi' '[ร†]Qร'
-
-# kwset is disabled on -i & non-ASCII. No way to match non-ASCII \0
-# patterns case-insensitively.
-nul_match T1 '-i' 'ร†Qร'
-
-# \0 implicitly disables regexes. This is an undocumented internal
-# limitation.
-nul_match T1 '' 'yQ[f]'
-nul_match T1 '' '[y]Qf'
-nul_match T1 '-i' 'YQ[F]'
-nul_match T1 '-i' '[Y]Qf'
-nul_match T1 '' 'รฆQ[รฐ]'
-nul_match T1 '' '[รฆ]Qรฐ'
-nul_match T1 '-i' 'ร†Q[ร]'
-
-# ... because of \0 implicitly disabling regexes regexes that
-# should/shouldn't match don't do the right thing.
-nul_match T1 '' 'eQm.*cQ'
-nul_match T1 '-i' 'EQM.*cQ'
-nul_match T0 '' 'eQm[*]c'
-nul_match T0 '-i' 'EQM[*]C'
-
-# Due to the REG_STARTEND extension when kwset() is disabled on -i &
-# non-ASCII the string will be matched in its entirety, but the
-# pattern will be cut off at the first \0.
-nul_match 0 '-i' 'NOMATCHQรฐ'
-nul_match T0 '-i' '[ร†]QNOMATCH'
-nul_match T0 '-i' '[รฆ]QNOMATCH'
-# Matches, but for the wrong reasons, just stops at [รฆ]
-nul_match 1 '-i' '[ร†]Qรฐ'
-nul_match 1 '-i' '[รฆ]Qรฐ'
-
-# Ensure that the matcher doesn't regress to something that stops at
-# \0
-nul_match 0 '-F' 'yQ[f]'
-nul_match 0 '-Fi' 'YQ[F]'
-nul_match 0 '' 'yQNOMATCH'
-nul_match 0 '' 'QNOMATCH'
-nul_match 0 '-i' 'YQNOMATCH'
-nul_match 0 '-i' 'QNOMATCH'
-nul_match 0 '-F' 'รฆQ[รฐ]'
-nul_match 0 '-Fi' 'ร†Q[ร]'
-nul_match 0 '' 'yQNร“MATCH'
-nul_match 0 '' 'QNร“MATCH'
-nul_match 0 '-i' 'YQNร“MATCH'
-nul_match 0 '-i' 'QNร“MATCH'
-
 test_expect_success 'grep respects binary diff attribute' '
 	echo text >t &&
 	git add t &&
diff --git a/third_party/git/t/t7816-grep-binary-pattern.sh b/third_party/git/t/t7816-grep-binary-pattern.sh
new file mode 100755
index 0000000000..60bab291e4
--- /dev/null
+++ b/third_party/git/t/t7816-grep-binary-pattern.sh
@@ -0,0 +1,127 @@
+#!/bin/sh
+
+test_description='git grep with a binary pattern files'
+
+. ./lib-gettext.sh
+
+nul_match_internal () {
+	matches=$1
+	prereqs=$2
+	lc_all=$3
+	extra_flags=$4
+	flags=$5
+	pattern=$6
+	pattern_human=$(echo "$pattern" | sed 's/Q/<NUL>/g')
+
+	if test "$matches" = 1
+	then
+		test_expect_success $prereqs "LC_ALL='$lc_all' git grep $extra_flags -f f $flags '$pattern_human' a" "
+			printf '$pattern' | q_to_nul >f &&
+			LC_ALL='$lc_all' git grep $extra_flags -f f $flags a
+		"
+	elif test "$matches" = 0
+	then
+		test_expect_success $prereqs "LC_ALL='$lc_all' git grep $extra_flags -f f $flags '$pattern_human' a" "
+			>stderr &&
+			printf '$pattern' | q_to_nul >f &&
+			test_must_fail env LC_ALL=\"$lc_all\" git grep $extra_flags -f f $flags a 2>stderr &&
+			test_i18ngrep ! 'This is only supported with -P under PCRE v2' stderr
+		"
+	elif test "$matches" = P
+	then
+		test_expect_success $prereqs "error, PCRE v2 only: LC_ALL='$lc_all' git grep -f f $flags '$pattern_human' a" "
+			>stderr &&
+			printf '$pattern' | q_to_nul >f &&
+			test_must_fail env LC_ALL=\"$lc_all\" git grep -f f $flags a 2>stderr &&
+			test_i18ngrep 'This is only supported with -P under PCRE v2' stderr
+		"
+	else
+		test_expect_success "PANIC: Test framework error. Unknown matches value $matches" 'false'
+	fi
+}
+
+nul_match () {
+	matches=$1
+	matches_pcre2=$2
+	matches_pcre2_locale=$3
+	flags=$4
+	pattern=$5
+	pattern_human=$(echo "$pattern" | sed 's/Q/<NUL>/g')
+
+	nul_match_internal "$matches" "" "C" "" "$flags" "$pattern"
+	nul_match_internal "$matches_pcre2" "LIBPCRE2" "C" "-P" "$flags" "$pattern"
+	nul_match_internal "$matches_pcre2_locale" "LIBPCRE2,GETTEXT_LOCALE" "$is_IS_locale" "-P" "$flags" "$pattern"
+}
+
+test_expect_success 'setup' "
+	echo 'binaryQfileQm[*]cQ*รฆQรฐ' | q_to_nul >a &&
+	git add a &&
+	git commit -m.
+"
+
+# Simple fixed-string matching that can use kwset (no -i && non-ASCII)
+nul_match P P P '-F' 'yQf'
+nul_match P P P '-F' 'yQx'
+nul_match P P P '-Fi' 'YQf'
+nul_match P P P '-Fi' 'YQx'
+nul_match P P 1 '' 'yQf'
+nul_match P P 0 '' 'yQx'
+nul_match P P 1 '' 'รฆQรฐ'
+nul_match P P P '-F' 'eQm[*]c'
+nul_match P P P '-Fi' 'EQM[*]C'
+
+# Regex patterns that would match but shouldn't with -F
+nul_match P P P '-F' 'yQ[f]'
+nul_match P P P '-F' '[y]Qf'
+nul_match P P P '-Fi' 'YQ[F]'
+nul_match P P P '-Fi' '[Y]QF'
+nul_match P P P '-F' 'รฆQ[รฐ]'
+nul_match P P P '-F' '[รฆ]Qรฐ'
+
+# The -F kwset codepath can't handle -i && non-ASCII...
+nul_match P 1 1 '-i' '[รฆ]Qรฐ'
+
+# ...PCRE v2 only matches non-ASCII with -i casefolding under UTF-8
+# semantics
+nul_match P P P '-Fi' 'ร†Q[ร]'
+nul_match P 0 1 '-i'  'ร†Q[ร]'
+nul_match P 0 1 '-i'  '[ร†]Qร'
+nul_match P 0 1 '-i' '[ร†]Qรฐ'
+nul_match P 0 1 '-i' 'ร†Qร'
+
+# \0 in regexes can only work with -P & PCRE v2
+nul_match P P 1 '' 'yQ[f]'
+nul_match P P 1 '' '[y]Qf'
+nul_match P P 1 '-i' 'YQ[F]'
+nul_match P P 1 '-i' '[Y]Qf'
+nul_match P P 1 '' 'รฆQ[รฐ]'
+nul_match P P 1 '' '[รฆ]Qรฐ'
+nul_match P P 1 '-i' 'ร†Q[ร]'
+nul_match P P 1 '' 'eQm.*cQ'
+nul_match P P 1 '-i' 'EQM.*cQ'
+nul_match P P 0 '' 'eQm[*]c'
+nul_match P P 0 '-i' 'EQM[*]C'
+
+# Assert that we're using REG_STARTEND and the pattern doesn't match
+# just because it's cut off at the first \0.
+nul_match P P 0 '-i' 'NOMATCHQรฐ'
+nul_match P P 0 '-i' '[ร†]QNOMATCH'
+nul_match P P 0 '-i' '[รฆ]QNOMATCH'
+
+# Ensure that the matcher doesn't regress to something that stops at
+# \0
+nul_match P P P '-F' 'yQ[f]'
+nul_match P P P '-Fi' 'YQ[F]'
+nul_match P P 0 '' 'yQNOMATCH'
+nul_match P P 0 '' 'QNOMATCH'
+nul_match P P 0 '-i' 'YQNOMATCH'
+nul_match P P 0 '-i' 'QNOMATCH'
+nul_match P P P '-F' 'รฆQ[รฐ]'
+nul_match P P P '-Fi' 'ร†Q[ร]'
+nul_match P P 1 '-i' 'ร†Q[ร]'
+nul_match P P 0 '' 'yQNร“MATCH'
+nul_match P P 0 '' 'QNร“MATCH'
+nul_match P P 0 '-i' 'YQNร“MATCH'
+nul_match P P 0 '-i' 'QNร“MATCH'
+
+test_done
diff --git a/third_party/git/t/t7900-maintenance.sh b/third_party/git/t/t7900-maintenance.sh
new file mode 100755
index 0000000000..53c883531e
--- /dev/null
+++ b/third_party/git/t/t7900-maintenance.sh
@@ -0,0 +1,65 @@
+#!/bin/sh
+
+test_description='git maintenance builtin'
+
+. ./test-lib.sh
+
+GIT_TEST_COMMIT_GRAPH=0
+
+test_expect_success 'help text' '
+	test_expect_code 129 git maintenance -h 2>err &&
+	test_i18ngrep "usage: git maintenance run" err &&
+	test_expect_code 128 git maintenance barf 2>err &&
+	test_i18ngrep "invalid subcommand: barf" err &&
+	test_expect_code 129 git maintenance 2>err &&
+	test_i18ngrep "usage: git maintenance" err
+'
+
+test_expect_success 'run [--auto|--quiet]' '
+	GIT_TRACE2_EVENT="$(pwd)/run-no-auto.txt" \
+		git maintenance run 2>/dev/null &&
+	GIT_TRACE2_EVENT="$(pwd)/run-auto.txt" \
+		git maintenance run --auto 2>/dev/null &&
+	GIT_TRACE2_EVENT="$(pwd)/run-no-quiet.txt" \
+		git maintenance run --no-quiet 2>/dev/null &&
+	test_subcommand git gc --quiet <run-no-auto.txt &&
+	test_subcommand ! git gc --auto --quiet <run-auto.txt &&
+	test_subcommand git gc --no-quiet <run-no-quiet.txt
+'
+
+test_expect_success 'maintenance.<task>.enabled' '
+	git config maintenance.gc.enabled false &&
+	git config maintenance.commit-graph.enabled true &&
+	GIT_TRACE2_EVENT="$(pwd)/run-config.txt" git maintenance run 2>err &&
+	test_subcommand ! git gc --quiet <run-config.txt &&
+	test_subcommand git commit-graph write --split --reachable --no-progress <run-config.txt
+'
+
+test_expect_success 'run --task=<task>' '
+	GIT_TRACE2_EVENT="$(pwd)/run-commit-graph.txt" \
+		git maintenance run --task=commit-graph 2>/dev/null &&
+	GIT_TRACE2_EVENT="$(pwd)/run-gc.txt" \
+		git maintenance run --task=gc 2>/dev/null &&
+	GIT_TRACE2_EVENT="$(pwd)/run-commit-graph.txt" \
+		git maintenance run --task=commit-graph 2>/dev/null &&
+	GIT_TRACE2_EVENT="$(pwd)/run-both.txt" \
+		git maintenance run --task=commit-graph --task=gc 2>/dev/null &&
+	test_subcommand ! git gc --quiet <run-commit-graph.txt &&
+	test_subcommand git gc --quiet <run-gc.txt &&
+	test_subcommand git gc --quiet <run-both.txt &&
+	test_subcommand git commit-graph write --split --reachable --no-progress <run-commit-graph.txt &&
+	test_subcommand ! git commit-graph write --split --reachable --no-progress <run-gc.txt &&
+	test_subcommand git commit-graph write --split --reachable --no-progress <run-both.txt
+'
+
+test_expect_success 'run --task=bogus' '
+	test_must_fail git maintenance run --task=bogus 2>err &&
+	test_i18ngrep "is not a valid task" err
+'
+
+test_expect_success 'run --task duplicate' '
+	test_must_fail git maintenance run --task=gc --task=gc 2>err &&
+	test_i18ngrep "cannot be selected multiple times" err
+'
+
+test_done
diff --git a/third_party/git/t/t8002-blame.sh b/third_party/git/t/t8002-blame.sh
index eea048e52c..015973e8fe 100755
--- a/third_party/git/t/t8002-blame.sh
+++ b/third_party/git/t/t8002-blame.sh
@@ -6,6 +6,10 @@ test_description='git blame'
 PROG='git blame -c'
 . "$TEST_DIRECTORY"/annotate-tests.sh
 
+test_expect_success 'setup' '
+	hexsz=$(test_oid hexsz)
+'
+
 test_expect_success 'blame untracked file in empty repo' '
 	>untracked &&
 	test_must_fail git blame untracked
@@ -105,21 +109,32 @@ test_expect_success 'blame --abbrev=<n> works' '
 '
 
 test_expect_success 'blame -l aligns regular and boundary commits' '
-	check_abbrev 40 -l HEAD &&
-	check_abbrev 39 -l ^HEAD
+	check_abbrev $hexsz         -l HEAD &&
+	check_abbrev $((hexsz - 1)) -l ^HEAD
 '
 
-test_expect_success 'blame --abbrev=40 behaves like -l' '
-	check_abbrev 40 --abbrev=40 HEAD &&
-	check_abbrev 39 --abbrev=40 ^HEAD
+test_expect_success 'blame --abbrev with full length behaves like -l' '
+	check_abbrev $hexsz         --abbrev=$hexsz HEAD &&
+	check_abbrev $((hexsz - 1)) --abbrev=$hexsz ^HEAD
 '
 
-test_expect_success '--no-abbrev works like --abbrev=40' '
-	check_abbrev 40 --no-abbrev
+test_expect_success '--no-abbrev works like --abbrev with full length' '
+	check_abbrev $hexsz --no-abbrev
 '
 
 test_expect_success '--exclude-promisor-objects does not BUG-crash' '
 	test_must_fail git blame --exclude-promisor-objects one
 '
 
+test_expect_success 'blame with uncommitted edits in partial clone does not crash' '
+	git init server &&
+	echo foo >server/file.txt &&
+	git -C server add file.txt &&
+	git -C server commit -m file &&
+
+	git clone --filter=blob:none "file://$(pwd)/server" client &&
+	echo bar >>client/file.txt &&
+	git -C client blame file.txt
+'
+
 test_done
diff --git a/third_party/git/t/t8003-blame-corner-cases.sh b/third_party/git/t/t8003-blame-corner-cases.sh
index 1c5fb1d1f8..ba8013b002 100755
--- a/third_party/git/t/t8003-blame-corner-cases.sh
+++ b/third_party/git/t/t8003-blame-corner-cases.sh
@@ -6,7 +6,6 @@ test_description='git blame corner cases'
 pick_fc='s/^[0-9a-f^]* *\([^ ]*\) *(\([^ ]*\) .*/\1-\2/'
 
 test_expect_success setup '
-
 	echo A A A A A >one &&
 	echo B B B B B >two &&
 	echo C C C C C >tres &&
@@ -173,7 +172,6 @@ test_expect_success 'blame during cherry-pick with file rename conflict' '
 	git show HEAD@{1}:rodent > rodent &&
 	git add rodent &&
 	git blame -f -C -C1 rodent | sed -e "$pick_fc" >current &&
-	cat current &&
 	cat >expected <<-\EOF &&
 	mouse-Initial
 	mouse-Second
@@ -275,18 +273,14 @@ test_expect_success 'blame file with CRLF core.autocrlf=true' '
 	grep "A U Thor" actual
 '
 
-# Tests the splitting and merging of blame entries in blame_coalesce().
-# The output of blame is the same, regardless of whether blame_coalesce() runs
-# or not, so we'd likely only notice a problem if blame crashes or assigned
-# blame to the "splitting" commit ('SPLIT' below).
-test_expect_success 'blame coalesce' '
+test_expect_success 'setup coalesce tests' '
 	cat >giraffe <<-\EOF &&
 	ABC
 	DEF
 	EOF
 	git add giraffe &&
 	git commit -m "original file" &&
-	oid=$(git rev-parse HEAD) &&
+	orig=$(git rev-parse HEAD) &&
 
 	cat >giraffe <<-\EOF &&
 	ABC
@@ -295,6 +289,7 @@ test_expect_success 'blame coalesce' '
 	EOF
 	git add giraffe &&
 	git commit -m "interior SPLIT line" &&
+	split=$(git rev-parse HEAD) &&
 
 	cat >giraffe <<-\EOF &&
 	ABC
@@ -302,12 +297,25 @@ test_expect_success 'blame coalesce' '
 	EOF
 	git add giraffe &&
 	git commit -m "same contents as original" &&
+	final=$(git rev-parse HEAD)
+'
+
+test_expect_success 'blame coalesce' '
+	cat >expect <<-EOF &&
+	$orig 1 1 2
+	$orig 2 2
+	EOF
+	git blame --porcelain $final giraffe >actual.raw &&
+	grep "^$orig" actual.raw >actual &&
+	test_cmp expect actual
+'
 
+test_expect_success 'blame does not coalesce non-adjacent result lines' '
 	cat >expect <<-EOF &&
-	$oid 1) ABC
-	$oid 2) DEF
+	$orig 1) ABC
+	$orig 3) DEF
 	EOF
-	git -c core.abbrev=40 blame -s giraffe >actual &&
+	git blame --no-abbrev -s -L1,1 -L3,3 $split giraffe >actual &&
 	test_cmp expect actual
 '
 
diff --git a/third_party/git/t/t8011-blame-split-file.sh b/third_party/git/t/t8011-blame-split-file.sh
index 831125047b..bdda0c03fe 100755
--- a/third_party/git/t/t8011-blame-split-file.sh
+++ b/third_party/git/t/t8011-blame-split-file.sh
@@ -54,7 +54,7 @@ test_expect_success 'setup simulated porcelain' '
 	cat >read-porcelain.pl <<-\EOF
 	my $field = shift;
 	while (<>) {
-		if (/^[0-9a-f]{40} /) {
+		if (/^[0-9a-f]{40,} /) {
 			flush();
 			$hash = $&;
 		} elsif (/^$field (.*)/) {
diff --git a/third_party/git/t/t8013-blame-ignore-revs.sh b/third_party/git/t/t8013-blame-ignore-revs.sh
index 36dc31eb39..24ae5018e8 100755
--- a/third_party/git/t/t8013-blame-ignore-revs.sh
+++ b/third_party/git/t/t8013-blame-ignore-revs.sh
@@ -21,6 +21,7 @@ test_expect_success setup '
 	test_tick &&
 	git commit -m X &&
 	git tag X &&
+	git tag -a -m "X (annotated)" XT &&
 
 	git blame --line-porcelain file >blame_raw &&
 
@@ -31,20 +32,36 @@ test_expect_success setup '
 	grep -E "^[0-9a-f]+ [0-9]+ 2" blame_raw | sed -e "s/ .*//" >actual &&
 	git rev-parse X >expect &&
 	test_cmp expect actual
+'
+
+# Ensure bogus --ignore-rev requests are caught
+test_expect_success 'validate --ignore-rev' '
+	test_must_fail git blame --ignore-rev X^{tree} file
+'
+
+# Ensure bogus --ignore-revs-file requests are caught
+test_expect_success 'validate --ignore-revs-file' '
+	git rev-parse X^{tree} >ignore_x &&
+	test_must_fail git blame --ignore-revs-file ignore_x file
+'
+
+for I in X XT
+do
+	# Ignore X (or XT), make sure A is blamed for line 1 and B for line 2.
+	# Giving X (i.e. commit) and XT (i.e. annotated tag to commit) should
+	# produce the same result.
+	test_expect_success "ignore_rev_changing_lines ($I)" '
+		git blame --line-porcelain --ignore-rev $I file >blame_raw &&
+
+		grep -E "^[0-9a-f]+ [0-9]+ 1" blame_raw | sed -e "s/ .*//" >actual &&
+		git rev-parse A >expect &&
+		test_cmp expect actual &&
+
+		grep -E "^[0-9a-f]+ [0-9]+ 2" blame_raw | sed -e "s/ .*//" >actual &&
+		git rev-parse B >expect &&
+		test_cmp expect actual
 	'
-
-# Ignore X, make sure A is blamed for line 1 and B for line 2.
-test_expect_success ignore_rev_changing_lines '
-	git blame --line-porcelain --ignore-rev X file >blame_raw &&
-
-	grep -E "^[0-9a-f]+ [0-9]+ 1" blame_raw | sed -e "s/ .*//" >actual &&
-	git rev-parse A >expect &&
-	test_cmp expect actual &&
-
-	grep -E "^[0-9a-f]+ [0-9]+ 2" blame_raw | sed -e "s/ .*//" >actual &&
-	git rev-parse B >expect &&
-	test_cmp expect actual
-	'
+done
 
 # For ignored revs that have added 'unblamable' lines, attribute those to the
 # ignored commit.
@@ -67,7 +84,7 @@ test_expect_success ignore_rev_adding_unblamable_lines '
 
 	grep -E "^[0-9a-f]+ [0-9]+ 4" blame_raw | sed -e "s/ .*//" >actual &&
 	test_cmp expect actual
-	'
+'
 
 # Ignore X and Y, both in separate files.  Lines 1 == A, 2 == B.
 test_expect_success ignore_revs_from_files '
@@ -82,7 +99,7 @@ test_expect_success ignore_revs_from_files '
 	grep -E "^[0-9a-f]+ [0-9]+ 2" blame_raw | sed -e "s/ .*//" >actual &&
 	git rev-parse B >expect &&
 	test_cmp expect actual
-	'
+'
 
 # Ignore X from the config option, Y from a file.
 test_expect_success ignore_revs_from_configs_and_files '
@@ -96,7 +113,7 @@ test_expect_success ignore_revs_from_configs_and_files '
 	grep -E "^[0-9a-f]+ [0-9]+ 2" blame_raw | sed -e "s/ .*//" >actual &&
 	git rev-parse B >expect &&
 	test_cmp expect actual
-	'
+'
 
 # Override blame.ignoreRevsFile (ignore_x) with an empty string.  X should be
 # blamed now for lines 1 and 2, since we are no longer ignoring X.
@@ -120,7 +137,7 @@ test_expect_success bad_files_and_revs '
 	echo NOREV >ignore_norev &&
 	test_must_fail git blame file --ignore-revs-file ignore_norev 2>err &&
 	test_i18ngrep "invalid object name: NOREV" err
-	'
+'
 
 # For ignored revs that have added 'unblamable' lines, mark those lines with a
 # '*'
@@ -138,7 +155,7 @@ test_expect_success mark_unblamable_lines '
 
 	sed -n "4p" blame_raw | cut -c1 >actual &&
 	test_cmp expect actual
-	'
+'
 
 # Commit Z will touch the first two lines.  Y touched all four.
 # 	A--B--X--Y--Z
@@ -171,7 +188,7 @@ test_expect_success mark_ignored_lines '
 
 	sed -n "4p" blame_raw | cut -c1 >actual &&
 	! test_cmp expect actual
-	'
+'
 
 # For ignored revs that added 'unblamable' lines and more recent commits changed
 # the blamable lines, mark the unblamable lines with a
@@ -190,7 +207,7 @@ test_expect_success mark_unblamable_lines_intermediate '
 
 	sed -n "4p" blame_raw | cut -c1 >actual &&
 	test_cmp expect actual
-	'
+'
 
 # The heuristic called by guess_line_blames() tries to find the size of a
 # blame_entry 'e' in the parent's address space.  Those calculations need to
@@ -227,7 +244,7 @@ test_expect_success ignored_chunk_negative_parent_size '
 	git tag C &&
 
 	git blame file --ignore-rev B >blame_raw
-	'
+'
 
 # Resetting the repo and creating:
 #
@@ -269,6 +286,6 @@ test_expect_success ignore_merge '
 	grep -E "^[0-9a-f]+ [0-9]+ 9" blame_raw | sed -e "s/ .*//" >actual &&
 	git rev-parse C >expect &&
 	test_cmp expect actual
-	'
+'
 
 test_done
diff --git a/third_party/git/t/t8014-blame-ignore-fuzzy.sh b/third_party/git/t/t8014-blame-ignore-fuzzy.sh
index 6e61882b6f..e68e6115a6 100755
--- a/third_party/git/t/t8014-blame-ignore-fuzzy.sh
+++ b/third_party/git/t/t8014-blame-ignore-fuzzy.sh
@@ -248,7 +248,7 @@ Final
 EOF
 
 # The first line of b matches best with the last line of a, but the overall
-# match is better if we match it with the the first line of a.
+# match is better if we match it with the first line of a.
 title11="Piggy in the middle"
 cat <<EOF >a11
 abcdefg
diff --git a/third_party/git/t/t9001-send-email.sh b/third_party/git/t/t9001-send-email.sh
index 997f90b42b..a08f72596a 100755
--- a/third_party/git/t/t9001-send-email.sh
+++ b/third_party/git/t/t9001-send-email.sh
@@ -42,7 +42,8 @@ clean_fake_sendmail () {
 }
 
 test_expect_success $PREREQ 'Extract patches' '
-	patches=$(git format-patch -s --cc="One <one@example.com>" --cc=two@example.com -n HEAD^1)
+	patches=$(git format-patch -s --cc="One <one@example.com>" --cc=two@example.com -n HEAD^1) &&
+	threaded_patches=$(git format-patch -o threaded -s --in-reply-to="format" HEAD^1)
 '
 
 # Test no confirm early to ensure remaining tests will not hang
@@ -1194,8 +1195,8 @@ test_expect_success $PREREQ 'in-reply-to but no threading' '
 		--to=nobody@example.com \
 		--in-reply-to="<in-reply-id@example.com>" \
 		--no-thread \
-		$patches |
-	grep "In-Reply-To: <in-reply-id@example.com>"
+		$patches >out &&
+	grep "In-Reply-To: <in-reply-id@example.com>" out
 '
 
 test_expect_success $PREREQ 'no in-reply-to and no threading' '
@@ -1219,6 +1220,17 @@ test_expect_success $PREREQ 'threading but no chain-reply-to' '
 	grep "In-Reply-To: " stdout
 '
 
+test_expect_success $PREREQ 'override in-reply-to if no threading' '
+	git send-email \
+		--dry-run \
+		--from="Example <nobody@example.com>" \
+		--to=nobody@example.com \
+		--no-thread \
+		--in-reply-to="override" \
+		$threaded_patches >stdout &&
+	grep "In-Reply-To: <override>" stdout
+'
+
 test_expect_success $PREREQ 'sendemail.to works' '
 	git config --replace-all sendemail.to "Somebody <somebody@ex.com>" &&
 	git send-email \
@@ -1260,7 +1272,7 @@ test_expect_success $PREREQ 'sendemail.identity: --no-identity clears previous i
 	grep "To: default@example.com" stdout
 '
 
-test_expect_success $PREREQ 'sendemail.identity: bool identity variable existance overrides' '
+test_expect_success $PREREQ 'sendemail.identity: bool identity variable existence overrides' '
 	git -c sendemail.identity=cloud \
 		-c sendemail.xmailer=true \
 		-c sendemail.cloud.xmailer=false \
@@ -1539,7 +1551,7 @@ test_expect_success $PREREQ '8-bit and sendemail.transferencoding=quoted-printab
 		--smtp-server="$(pwd)/fake.sendmail" \
 		email-using-8bit \
 		2>errors >out &&
-	sed '1,/^$/d' msgtxt1 >actual &&
+	sed "1,/^$/d" msgtxt1 >actual &&
 	test_cmp expected actual
 '
 
@@ -1556,7 +1568,7 @@ test_expect_success $PREREQ '8-bit and sendemail.transferencoding=base64' '
 		--smtp-server="$(pwd)/fake.sendmail" \
 		email-using-8bit \
 		2>errors >out &&
-	sed '1,/^$/d' msgtxt1 >actual &&
+	sed "1,/^$/d" msgtxt1 >actual &&
 	test_cmp expected actual
 '
 
@@ -1582,7 +1594,7 @@ test_expect_success $PREREQ 'convert from quoted-printable to base64' '
 		--smtp-server="$(pwd)/fake.sendmail" \
 		email-using-qp \
 		2>errors >out &&
-	sed '1,/^$/d' msgtxt1 >actual &&
+	sed "1,/^$/d" msgtxt1 >actual &&
 	test_cmp expected actual
 '
 
@@ -1612,7 +1624,7 @@ test_expect_success $PREREQ 'CRLF and sendemail.transferencoding=quoted-printabl
 		--smtp-server="$(pwd)/fake.sendmail" \
 		email-using-crlf \
 		2>errors >out &&
-	sed '1,/^$/d' msgtxt1 >actual &&
+	sed "1,/^$/d" msgtxt1 >actual &&
 	test_cmp expected actual
 '
 
@@ -1629,7 +1641,7 @@ test_expect_success $PREREQ 'CRLF and sendemail.transferencoding=base64' '
 		--smtp-server="$(pwd)/fake.sendmail" \
 		email-using-crlf \
 		2>errors >out &&
-	sed '1,/^$/d' msgtxt1 >actual &&
+	sed "1,/^$/d" msgtxt1 >actual &&
 	test_cmp expected actual
 '
 
@@ -2066,7 +2078,7 @@ test_expect_success $PREREQ 'leading and trailing whitespaces are removed' '
 	TO1=$(echo "QTo 1 <to1@example.com>" | q_to_tab) &&
 	TO2=$(echo "QZto2" | qz_to_tab_space) &&
 	CC1=$(echo "cc1" | append_cr) &&
-	BCC1=$(echo "Q bcc1@example.com Q" | q_to_nul) &&
+	BCC1=$(echo " bcc1@example.com Q" | q_to_nul) &&
 	git send-email \
 	--dry-run \
 	--from="	Example <from@example.com>" \
@@ -2130,4 +2142,33 @@ test_expect_success $PREREQ 'test that send-email works outside a repo' '
 		"$(pwd)/0001-add-master.patch"
 '
 
+test_expect_success $PREREQ 'test that sendmail config is rejected' '
+	test_config sendmail.program sendmail &&
+	test_must_fail git send-email \
+		--from="Example <nobody@example.com>" \
+		--to=nobody@example.com \
+		--smtp-server="$(pwd)/fake.sendmail" \
+		HEAD^ 2>err &&
+	test_i18ngrep "found configuration options for '"'"sendmail"'"'" err
+'
+
+test_expect_success $PREREQ 'test that sendmail config rejection is specific' '
+	test_config resendmail.program sendmail &&
+	git send-email \
+		--from="Example <nobody@example.com>" \
+		--to=nobody@example.com \
+		--smtp-server="$(pwd)/fake.sendmail" \
+		HEAD^
+'
+
+test_expect_success $PREREQ 'test forbidSendmailVariables behavior override' '
+	test_config sendmail.program sendmail &&
+	test_config sendemail.forbidSendmailVariables false &&
+	git send-email \
+		--from="Example <nobody@example.com>" \
+		--to=nobody@example.com \
+		--smtp-server="$(pwd)/fake.sendmail" \
+		HEAD^
+'
+
 test_done
diff --git a/third_party/git/t/t9010-svn-fe.sh b/third_party/git/t/t9010-svn-fe.sh
deleted file mode 100755
index 0b20b07e68..0000000000
--- a/third_party/git/t/t9010-svn-fe.sh
+++ /dev/null
@@ -1,1107 +0,0 @@
-#!/bin/sh
-
-test_description='check svn dumpfile importer'
-
-. ./test-lib.sh
-
-if test_have_prereq !PIPE
-then
-	skip_all="svn dumpfile importer testing requires the PIPE prerequisite"
-	test_done
-fi
-
-reinit_git () {
-	rm -fr .git &&
-	rm -f stream backflow &&
-	git init &&
-	mkfifo stream backflow
-}
-
-try_dump () {
-	input=$1 &&
-	maybe_fail_svnfe=${2:+test_$2} &&
-	maybe_fail_fi=${3:+test_$3} &&
-
-	{
-		$maybe_fail_svnfe test-svn-fe "$input" >stream 3<backflow &
-	} &&
-	$maybe_fail_fi git fast-import --cat-blob-fd=3 <stream 3>backflow &&
-	wait $!
-}
-
-properties () {
-	while test "$#" -ne 0
-	do
-		property="$1" &&
-		value="$2" &&
-		printf "%s\n" "K ${#property}" &&
-		printf "%s\n" "$property" &&
-		printf "%s\n" "V ${#value}" &&
-		printf "%s\n" "$value" &&
-		shift 2 ||
-		return 1
-	done
-}
-
-text_no_props () {
-	text="$1
-" &&
-	printf "%s\n" "Prop-content-length: 10" &&
-	printf "%s\n" "Text-content-length: ${#text}" &&
-	printf "%s\n" "Content-length: $((${#text} + 10))" &&
-	printf "%s\n" "" "PROPS-END" &&
-	printf "%s\n" "$text"
-}
-
->empty
-
-test_expect_success 'empty dump' '
-	reinit_git &&
-	echo "SVN-fs-dump-format-version: 2" >input &&
-	try_dump input
-'
-
-test_expect_success 'v4 dumps not supported' '
-	reinit_git &&
-	echo "SVN-fs-dump-format-version: 4" >v4.dump &&
-	try_dump v4.dump must_fail
-'
-
-test_expect_failure 'empty revision' '
-	reinit_git &&
-	printf "rev <nobody, nobody@local>: %s\n" "" "" >expect &&
-	cat >emptyrev.dump <<-\EOF &&
-	SVN-fs-dump-format-version: 3
-
-	Revision-number: 1
-	Prop-content-length: 0
-	Content-length: 0
-
-	Revision-number: 2
-	Prop-content-length: 0
-	Content-length: 0
-
-	EOF
-	try_dump emptyrev.dump &&
-	git log -p --format="rev <%an, %ae>: %s" HEAD >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'empty properties' '
-	reinit_git &&
-	printf "rev <nobody, nobody@local>: %s\n" "" "" >expect &&
-	cat >emptyprop.dump <<-\EOF &&
-	SVN-fs-dump-format-version: 3
-
-	Revision-number: 1
-	Prop-content-length: 10
-	Content-length: 10
-
-	PROPS-END
-
-	Revision-number: 2
-	Prop-content-length: 10
-	Content-length: 10
-
-	PROPS-END
-	EOF
-	try_dump emptyprop.dump &&
-	git log -p --format="rev <%an, %ae>: %s" HEAD >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'author name and commit message' '
-	reinit_git &&
-	echo "<author@example.com, author@example.com@local>" >expect.author &&
-	cat >message <<-\EOF &&
-	A concise summary of the change
-
-	A detailed description of the change, why it is needed, what
-	was broken and why applying this is the best course of action.
-
-	* file.c
-	    Details pertaining to an individual file.
-	EOF
-	{
-		properties \
-			svn:author author@example.com \
-			svn:log "$(cat message)" &&
-		echo PROPS-END
-	} >props &&
-	{
-		echo "SVN-fs-dump-format-version: 3" &&
-		echo &&
-		echo "Revision-number: 1" &&
-		echo Prop-content-length: $(wc -c <props) &&
-		echo Content-length: $(wc -c <props) &&
-		echo &&
-		cat props
-	} >log.dump &&
-	try_dump log.dump &&
-	git log -p --format="%B" HEAD >actual.log &&
-	git log --format="<%an, %ae>" >actual.author &&
-	test_cmp message actual.log &&
-	test_cmp expect.author actual.author
-'
-
-test_expect_success 'unsupported properties are ignored' '
-	reinit_git &&
-	echo author >expect &&
-	cat >extraprop.dump <<-\EOF &&
-	SVN-fs-dump-format-version: 3
-
-	Revision-number: 1
-	Prop-content-length: 56
-	Content-length: 56
-
-	K 8
-	nonsense
-	V 1
-	y
-	K 10
-	svn:author
-	V 6
-	author
-	PROPS-END
-	EOF
-	try_dump extraprop.dump &&
-	git log -p --format=%an HEAD >actual &&
-	test_cmp expect actual
-'
-
-test_expect_failure 'timestamp and empty file' '
-	echo author@example.com >expect.author &&
-	echo 1999-01-01 >expect.date &&
-	echo file >expect.files &&
-	reinit_git &&
-	{
-		properties \
-			svn:author author@example.com \
-			svn:date "1999-01-01T00:01:002.000000Z" \
-			svn:log "add empty file" &&
-		echo PROPS-END
-	} >props &&
-	{
-		cat <<-EOF &&
-		SVN-fs-dump-format-version: 3
-
-		Revision-number: 1
-		EOF
-		echo Prop-content-length: $(wc -c <props) &&
-		echo Content-length: $(wc -c <props) &&
-		echo &&
-		cat props &&
-		cat <<-\EOF
-
-		Node-path: empty-file
-		Node-kind: file
-		Node-action: add
-		Content-length: 0
-
-		EOF
-	} >emptyfile.dump &&
-	try_dump emptyfile.dump &&
-	git log --format=%an HEAD >actual.author &&
-	git log --date=short --format=%ad HEAD >actual.date &&
-	git ls-tree -r --name-only HEAD >actual.files &&
-	test_cmp expect.author actual.author &&
-	test_cmp expect.date actual.date &&
-	test_cmp expect.files actual.files &&
-	git checkout HEAD empty-file &&
-	test_cmp empty file
-'
-
-test_expect_success 'directory with files' '
-	reinit_git &&
-	printf "%s\n" directory/file1 directory/file2 >expect.files &&
-	echo hi >hi &&
-	echo hello >hello &&
-	{
-		properties \
-			svn:author author@example.com \
-			svn:date "1999-02-01T00:01:002.000000Z" \
-			svn:log "add directory with some files in it" &&
-		echo PROPS-END
-	} >props &&
-	{
-		cat <<-EOF &&
-		SVN-fs-dump-format-version: 3
-
-		Revision-number: 1
-		EOF
-		echo Prop-content-length: $(wc -c <props) &&
-		echo Content-length: $(wc -c <props) &&
-		echo &&
-		cat props &&
-		cat <<-\EOF &&
-
-		Node-path: directory
-		Node-kind: dir
-		Node-action: add
-		Prop-content-length: 10
-		Content-length: 10
-
-		PROPS-END
-
-		Node-path: directory/file1
-		Node-kind: file
-		Node-action: add
-		EOF
-		text_no_props hello &&
-		cat <<-\EOF &&
-		Node-path: directory/file2
-		Node-kind: file
-		Node-action: add
-		EOF
-		text_no_props hi
-	} >directory.dump &&
-	try_dump directory.dump &&
-
-	git ls-tree -r --name-only HEAD >actual.files &&
-	git checkout HEAD directory &&
-	test_cmp expect.files actual.files &&
-	test_cmp hello directory/file1 &&
-	test_cmp hi directory/file2
-'
-
-test_expect_success 'branch name with backslash' '
-	reinit_git &&
-	sort <<-\EOF >expect.branch-files &&
-	trunk/file1
-	trunk/file2
-	"branches/UpdateFOPto094\\/file1"
-	"branches/UpdateFOPto094\\/file2"
-	EOF
-
-	echo hi >hi &&
-	echo hello >hello &&
-	{
-		properties \
-			svn:author author@example.com \
-			svn:date "1999-02-02T00:01:02.000000Z" \
-			svn:log "add directory with some files in it" &&
-		echo PROPS-END
-	} >props.setup &&
-	{
-		properties \
-			svn:author brancher@example.com \
-			svn:date "2007-12-06T21:38:34.000000Z" \
-			svn:log "Updating fop to .94 and adjust fo-stylesheets" &&
-		echo PROPS-END
-	} >props.branch &&
-	{
-		cat <<-EOF &&
-		SVN-fs-dump-format-version: 3
-
-		Revision-number: 1
-		EOF
-		echo Prop-content-length: $(wc -c <props.setup) &&
-		echo Content-length: $(wc -c <props.setup) &&
-		echo &&
-		cat props.setup &&
-		cat <<-\EOF &&
-
-		Node-path: trunk
-		Node-kind: dir
-		Node-action: add
-		Prop-content-length: 10
-		Content-length: 10
-
-		PROPS-END
-
-		Node-path: branches
-		Node-kind: dir
-		Node-action: add
-		Prop-content-length: 10
-		Content-length: 10
-
-		PROPS-END
-
-		Node-path: trunk/file1
-		Node-kind: file
-		Node-action: add
-		EOF
-		text_no_props hello &&
-		cat <<-\EOF &&
-		Node-path: trunk/file2
-		Node-kind: file
-		Node-action: add
-		EOF
-		text_no_props hi &&
-		cat <<-\EOF &&
-
-		Revision-number: 2
-		EOF
-		echo Prop-content-length: $(wc -c <props.branch) &&
-		echo Content-length: $(wc -c <props.branch) &&
-		echo &&
-		cat props.branch &&
-		cat <<-\EOF
-
-		Node-path: branches/UpdateFOPto094\
-		Node-kind: dir
-		Node-action: add
-		Node-copyfrom-rev: 1
-		Node-copyfrom-path: trunk
-
-		Node-kind: dir
-		Node-action: add
-		Prop-content-length: 34
-		Content-length: 34
-
-		K 13
-		svn:mergeinfo
-		V 0
-
-		PROPS-END
-		EOF
-	} >branch.dump &&
-	try_dump branch.dump &&
-
-	git ls-tree -r --name-only HEAD |
-	sort >actual.branch-files &&
-	test_cmp expect.branch-files actual.branch-files
-'
-
-test_expect_success 'node without action' '
-	reinit_git &&
-	cat >inaction.dump <<-\EOF &&
-	SVN-fs-dump-format-version: 3
-
-	Revision-number: 1
-	Prop-content-length: 10
-	Content-length: 10
-
-	PROPS-END
-
-	Node-path: directory
-	Node-kind: dir
-	Prop-content-length: 10
-	Content-length: 10
-
-	PROPS-END
-	EOF
-	try_dump inaction.dump must_fail
-'
-
-test_expect_success 'action: add node without text' '
-	reinit_git &&
-	cat >textless.dump <<-\EOF &&
-	SVN-fs-dump-format-version: 3
-
-	Revision-number: 1
-	Prop-content-length: 10
-	Content-length: 10
-
-	PROPS-END
-
-	Node-path: textless
-	Node-kind: file
-	Node-action: add
-	Prop-content-length: 10
-	Content-length: 10
-
-	PROPS-END
-	EOF
-	try_dump textless.dump must_fail
-'
-
-test_expect_failure 'change file mode but keep old content' '
-	reinit_git &&
-	cat >expect <<-\EOF &&
-	OBJID
-	:120000 100644 OBJID OBJID T	greeting
-	OBJID
-	:100644 120000 OBJID OBJID T	greeting
-	OBJID
-	:000000 100644 OBJID OBJID A	greeting
-	EOF
-	echo "link hello" >expect.blob &&
-	echo hello >hello &&
-	cat >filemode.dump <<-\EOF &&
-	SVN-fs-dump-format-version: 3
-
-	Revision-number: 1
-	Prop-content-length: 10
-	Content-length: 10
-
-	PROPS-END
-
-	Node-path: greeting
-	Node-kind: file
-	Node-action: add
-	Prop-content-length: 10
-	Text-content-length: 11
-	Content-length: 21
-
-	PROPS-END
-	link hello
-
-	Revision-number: 2
-	Prop-content-length: 10
-	Content-length: 10
-
-	PROPS-END
-
-	Node-path: greeting
-	Node-kind: file
-	Node-action: change
-	Prop-content-length: 33
-	Content-length: 33
-
-	K 11
-	svn:special
-	V 1
-	*
-	PROPS-END
-
-	Revision-number: 3
-	Prop-content-length: 10
-	Content-length: 10
-
-	PROPS-END
-
-	Node-path: greeting
-	Node-kind: file
-	Node-action: change
-	Prop-content-length: 10
-	Content-length: 10
-
-	PROPS-END
-	EOF
-	try_dump filemode.dump &&
-	{
-		git rev-list HEAD |
-		git diff-tree --root --stdin |
-		sed "s/$OID_REGEX/OBJID/g"
-	} >actual &&
-	git show HEAD:greeting >actual.blob &&
-	git show HEAD^:greeting >actual.target &&
-	test_cmp expect actual &&
-	test_cmp expect.blob actual.blob &&
-	test_cmp hello actual.target
-'
-
-test_expect_success 'NUL in property value' '
-	reinit_git &&
-	echo "commit message" >expect.message &&
-	{
-		properties \
-			unimportant "something with a NUL (Q)" \
-			svn:log "commit message"&&
-		echo PROPS-END
-	} |
-	q_to_nul >props &&
-	{
-		cat <<-\EOF &&
-		SVN-fs-dump-format-version: 3
-
-		Revision-number: 1
-		EOF
-		echo Prop-content-length: $(wc -c <props) &&
-		echo Content-length: $(wc -c <props) &&
-		echo &&
-		cat props
-	} >nulprop.dump &&
-	try_dump nulprop.dump &&
-	git diff-tree --always -s --format=%s HEAD >actual.message &&
-	test_cmp expect.message actual.message
-'
-
-test_expect_success 'NUL in log message, file content, and property name' '
-	# Caveat: svnadmin 1.6.16 (r1073529) truncates at \0 in the
-	# svn:specialQnotreally example.
-	reinit_git &&
-	cat >expect <<-\EOF &&
-	OBJID
-	:100644 100644 OBJID OBJID M	greeting
-	OBJID
-	:000000 100644 OBJID OBJID A	greeting
-	EOF
-	printf "\n%s\n" "something with an ASCII NUL (Q)" >expect.message &&
-	printf "%s\n" "helQo" >expect.hello1 &&
-	printf "%s\n" "link hello" >expect.hello2 &&
-	{
-		properties svn:log "something with an ASCII NUL (Q)" &&
-		echo PROPS-END
-	} |
-	q_to_nul >props &&
-	{
-		q_to_nul <<-\EOF &&
-		SVN-fs-dump-format-version: 3
-
-		Revision-number: 1
-		Prop-content-length: 10
-		Content-length: 10
-
-		PROPS-END
-
-		Node-path: greeting
-		Node-kind: file
-		Node-action: add
-		Prop-content-length: 10
-		Text-content-length: 6
-		Content-length: 16
-
-		PROPS-END
-		helQo
-
-		Revision-number: 2
-		EOF
-		echo Prop-content-length: $(wc -c <props) &&
-		echo Content-length: $(wc -c <props) &&
-		echo &&
-		cat props &&
-		q_to_nul <<-\EOF
-
-		Node-path: greeting
-		Node-kind: file
-		Node-action: change
-		Prop-content-length: 43
-		Text-content-length: 11
-		Content-length: 54
-
-		K 21
-		svn:specialQnotreally
-		V 1
-		*
-		PROPS-END
-		link hello
-		EOF
-	} >8bitclean.dump &&
-	try_dump 8bitclean.dump &&
-	{
-		git rev-list HEAD |
-		git diff-tree --root --stdin |
-		sed "s/$OID_REGEX/OBJID/g"
-	} >actual &&
-	{
-		git cat-file commit HEAD | nul_to_q &&
-		echo
-	} |
-	sed -ne "/^\$/,\$ p" >actual.message &&
-	git cat-file blob HEAD^:greeting | nul_to_q >actual.hello1 &&
-	git cat-file blob HEAD:greeting | nul_to_q >actual.hello2 &&
-	test_cmp expect actual &&
-	test_cmp expect.message actual.message &&
-	test_cmp expect.hello1 actual.hello1 &&
-	test_cmp expect.hello2 actual.hello2
-'
-
-test_expect_success 'change file mode and reiterate content' '
-	reinit_git &&
-	cat >expect <<-\EOF &&
-	OBJID
-	:120000 100644 OBJID OBJID T	greeting
-	OBJID
-	:100644 120000 OBJID OBJID T	greeting
-	OBJID
-	:000000 100644 OBJID OBJID A	greeting
-	EOF
-	echo "link hello" >expect.blob &&
-	echo hello >hello &&
-	cat >filemode2.dump <<-\EOF &&
-	SVN-fs-dump-format-version: 3
-
-	Revision-number: 1
-	Prop-content-length: 10
-	Content-length: 10
-
-	PROPS-END
-
-	Node-path: greeting
-	Node-kind: file
-	Node-action: add
-	Prop-content-length: 10
-	Text-content-length: 11
-	Content-length: 21
-
-	PROPS-END
-	link hello
-
-	Revision-number: 2
-	Prop-content-length: 10
-	Content-length: 10
-
-	PROPS-END
-
-	Node-path: greeting
-	Node-kind: file
-	Node-action: change
-	Prop-content-length: 33
-	Text-content-length: 11
-	Content-length: 44
-
-	K 11
-	svn:special
-	V 1
-	*
-	PROPS-END
-	link hello
-
-	Revision-number: 3
-	Prop-content-length: 10
-	Content-length: 10
-
-	PROPS-END
-
-	Node-path: greeting
-	Node-kind: file
-	Node-action: change
-	Prop-content-length: 10
-	Text-content-length: 11
-	Content-length: 21
-
-	PROPS-END
-	link hello
-	EOF
-	try_dump filemode2.dump &&
-	{
-		git rev-list HEAD |
-		git diff-tree --root --stdin |
-		sed "s/$OID_REGEX/OBJID/g"
-	} >actual &&
-	git show HEAD:greeting >actual.blob &&
-	git show HEAD^:greeting >actual.target &&
-	test_cmp expect actual &&
-	test_cmp expect.blob actual.blob &&
-	test_cmp hello actual.target
-'
-
-test_expect_success 'deltas supported' '
-	reinit_git &&
-	{
-		# (old) h + (inline) ello + (old) \n
-		printf "SVNQ%b%b%s" "Q\003\006\005\004" "\001Q\0204\001\002" "ello" |
-		q_to_nul
-	} >delta &&
-	{
-		properties \
-			svn:author author@example.com \
-			svn:date "1999-01-05T00:01:002.000000Z" \
-			svn:log "add greeting" &&
-		echo PROPS-END
-	} >props &&
-	{
-		properties \
-			svn:author author@example.com \
-			svn:date "1999-01-06T00:01:002.000000Z" \
-			svn:log "change it" &&
-		echo PROPS-END
-	} >props2 &&
-	{
-		echo SVN-fs-dump-format-version: 3 &&
-		echo &&
-		echo Revision-number: 1 &&
-		echo Prop-content-length: $(wc -c <props) &&
-		echo Content-length: $(wc -c <props) &&
-		echo &&
-		cat props &&
-		cat <<-\EOF &&
-
-		Node-path: hello
-		Node-kind: file
-		Node-action: add
-		Prop-content-length: 10
-		Text-content-length: 3
-		Content-length: 13
-
-		PROPS-END
-		hi
-
-		EOF
-		echo Revision-number: 2 &&
-		echo Prop-content-length: $(wc -c <props2) &&
-		echo Content-length: $(wc -c <props2) &&
-		echo &&
-		cat props2 &&
-		cat <<-\EOF &&
-
-		Node-path: hello
-		Node-kind: file
-		Node-action: change
-		Text-delta: true
-		Prop-content-length: 10
-		EOF
-		echo Text-content-length: $(wc -c <delta) &&
-		echo Content-length: $((10 + $(wc -c <delta))) &&
-		echo &&
-		echo PROPS-END &&
-		cat delta
-	} >delta.dump &&
-	try_dump delta.dump
-'
-
-test_expect_success 'property deltas supported' '
-	reinit_git &&
-	cat >expect <<-\EOF &&
-	OBJID
-	:100755 100644 OBJID OBJID M	script.sh
-	EOF
-	{
-		properties \
-			svn:author author@example.com \
-			svn:date "1999-03-06T00:01:002.000000Z" \
-			svn:log "make an executable, or chmod -x it" &&
-		echo PROPS-END
-	} >revprops &&
-	{
-		echo SVN-fs-dump-format-version: 3 &&
-		echo &&
-		echo Revision-number: 1 &&
-		echo Prop-content-length: $(wc -c <revprops) &&
-		echo Content-length: $(wc -c <revprops) &&
-		echo &&
-		cat revprops &&
-		echo &&
-		cat <<-\EOF &&
-		Node-path: script.sh
-		Node-kind: file
-		Node-action: add
-		Text-content-length: 0
-		Prop-content-length: 39
-		Content-length: 39
-
-		K 14
-		svn:executable
-		V 4
-		true
-		PROPS-END
-
-		EOF
-		echo Revision-number: 2 &&
-		echo Prop-content-length: $(wc -c <revprops) &&
-		echo Content-length: $(wc -c <revprops) &&
-		echo &&
-		cat revprops &&
-		echo &&
-		cat <<-\EOF
-		Node-path: script.sh
-		Node-kind: file
-		Node-action: change
-		Prop-delta: true
-		Prop-content-length: 30
-		Content-length: 30
-
-		D 14
-		svn:executable
-		PROPS-END
-		EOF
-	} >propdelta.dump &&
-	try_dump propdelta.dump &&
-	{
-		git rev-list HEAD |
-		git diff-tree --stdin |
-		sed "s/$OID_REGEX/OBJID/g"
-	} >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'properties on /' '
-	reinit_git &&
-	cat <<-\EOF >expect &&
-	OBJID
-	OBJID
-	:000000 100644 OBJID OBJID A	greeting
-	EOF
-	sed -e "s/X$//" <<-\EOF >changeroot.dump &&
-	SVN-fs-dump-format-version: 3
-
-	Revision-number: 1
-	Prop-content-length: 10
-	Content-length: 10
-
-	PROPS-END
-
-	Node-path: greeting
-	Node-kind: file
-	Node-action: add
-	Text-content-length: 0
-	Prop-content-length: 10
-	Content-length: 10
-
-	PROPS-END
-
-	Revision-number: 2
-	Prop-content-length: 10
-	Content-length: 10
-
-	PROPS-END
-
-	Node-path: X
-	Node-kind: dir
-	Node-action: change
-	Prop-delta: true
-	Prop-content-length: 43
-	Content-length: 43
-
-	K 10
-	svn:ignore
-	V 11
-	build-area
-
-	PROPS-END
-	EOF
-	try_dump changeroot.dump &&
-	{
-		git rev-list HEAD |
-		git diff-tree --root --always --stdin |
-		sed "s/$OID_REGEX/OBJID/g"
-	} >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'deltas for typechange' '
-	reinit_git &&
-	cat >expect <<-\EOF &&
-	OBJID
-	:120000 100644 OBJID OBJID T	test-file
-	OBJID
-	:100755 120000 OBJID OBJID T	test-file
-	OBJID
-	:000000 100755 OBJID OBJID A	test-file
-	EOF
-	cat >deleteprop.dump <<-\EOF &&
-	SVN-fs-dump-format-version: 3
-
-	Revision-number: 1
-	Prop-content-length: 10
-	Content-length: 10
-
-	PROPS-END
-
-	Node-path: test-file
-	Node-kind: file
-	Node-action: add
-	Prop-delta: true
-	Prop-content-length: 35
-	Text-content-length: 17
-	Content-length: 52
-
-	K 14
-	svn:executable
-	V 0
-
-	PROPS-END
-	link testing 123
-
-	Revision-number: 2
-	Prop-content-length: 10
-	Content-length: 10
-
-	PROPS-END
-
-	Node-path: test-file
-	Node-kind: file
-	Node-action: change
-	Prop-delta: true
-	Prop-content-length: 53
-	Text-content-length: 17
-	Content-length: 70
-
-	K 11
-	svn:special
-	V 1
-	*
-	D 14
-	svn:executable
-	PROPS-END
-	link testing 231
-
-	Revision-number: 3
-	Prop-content-length: 10
-	Content-length: 10
-
-	PROPS-END
-
-	Node-path: test-file
-	Node-kind: file
-	Node-action: change
-	Prop-delta: true
-	Prop-content-length: 27
-	Text-content-length: 17
-	Content-length: 44
-
-	D 11
-	svn:special
-	PROPS-END
-	link testing 321
-	EOF
-	try_dump deleteprop.dump &&
-	{
-		git rev-list HEAD |
-		git diff-tree --root --stdin |
-		sed "s/$OID_REGEX/OBJID/g"
-	} >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'deltas need not consume the whole preimage' '
-	reinit_git &&
-	cat >expect <<-\EOF &&
-	OBJID
-	:120000 100644 OBJID OBJID T	postimage
-	OBJID
-	:100644 120000 OBJID OBJID T	postimage
-	OBJID
-	:000000 100644 OBJID OBJID A	postimage
-	EOF
-	echo "first preimage" >expect.1 &&
-	printf target >expect.2 &&
-	printf lnk >expect.3 &&
-	{
-		printf "SVNQ%b%b%b" "QQ\017\001\017" "\0217" "first preimage\n" |
-		q_to_nul
-	} >delta.1 &&
-	{
-		properties svn:special "*" &&
-		echo PROPS-END
-	} >symlink.props &&
-	{
-		printf "SVNQ%b%b%b" "Q\002\013\004\012" "\0201\001\001\0211" "lnk target" |
-		q_to_nul
-	} >delta.2 &&
-	{
-		printf "SVNQ%b%b" "Q\004\003\004Q" "\001Q\002\002" |
-		q_to_nul
-	} >delta.3 &&
-	{
-		cat <<-\EOF &&
-		SVN-fs-dump-format-version: 3
-
-		Revision-number: 1
-		Prop-content-length: 10
-		Content-length: 10
-
-		PROPS-END
-
-		Node-path: postimage
-		Node-kind: file
-		Node-action: add
-		Text-delta: true
-		Prop-content-length: 10
-		EOF
-		echo Text-content-length: $(wc -c <delta.1) &&
-		echo Content-length: $((10 + $(wc -c <delta.1))) &&
-		echo &&
-		echo PROPS-END &&
-		cat delta.1 &&
-		cat <<-\EOF &&
-
-		Revision-number: 2
-		Prop-content-length: 10
-		Content-length: 10
-
-		PROPS-END
-
-		Node-path: postimage
-		Node-kind: file
-		Node-action: change
-		Text-delta: true
-		EOF
-		echo Prop-content-length: $(wc -c <symlink.props) &&
-		echo Text-content-length: $(wc -c <delta.2) &&
-		echo Content-length: $(($(wc -c <symlink.props) + $(wc -c <delta.2))) &&
-		echo &&
-		cat symlink.props &&
-		cat delta.2 &&
-		cat <<-\EOF &&
-
-		Revision-number: 3
-		Prop-content-length: 10
-		Content-length: 10
-
-		PROPS-END
-
-		Node-path: postimage
-		Node-kind: file
-		Node-action: change
-		Text-delta: true
-		Prop-content-length: 10
-		EOF
-		echo Text-content-length: $(wc -c <delta.3) &&
-		echo Content-length: $((10 + $(wc -c <delta.3))) &&
-		echo &&
-		echo PROPS-END &&
-		cat delta.3 &&
-		echo
-	} >deltapartial.dump &&
-	try_dump deltapartial.dump &&
-	{
-		git rev-list HEAD |
-		git diff-tree --root --stdin |
-		sed "s/$OID_REGEX/OBJID/g"
-	} >actual &&
-	test_cmp expect actual &&
-	git show HEAD:postimage >actual.3 &&
-	git show HEAD^:postimage >actual.2 &&
-	git show HEAD^^:postimage >actual.1 &&
-	test_cmp expect.1 actual.1 &&
-	test_cmp expect.2 actual.2 &&
-	test_cmp expect.3 actual.3
-'
-
-test_expect_success 'no hang for delta trying to read past end of preimage' '
-	reinit_git &&
-	{
-		# COPY 1
-		printf "SVNQ%b%b" "Q\001\001\002Q" "\001Q" |
-		q_to_nul
-	} >greedy.delta &&
-	{
-		cat <<-\EOF &&
-		SVN-fs-dump-format-version: 3
-
-		Revision-number: 1
-		Prop-content-length: 10
-		Content-length: 10
-
-		PROPS-END
-
-		Node-path: bootstrap
-		Node-kind: file
-		Node-action: add
-		Text-delta: true
-		Prop-content-length: 10
-		EOF
-		echo Text-content-length: $(wc -c <greedy.delta) &&
-		echo Content-length: $((10 + $(wc -c <greedy.delta))) &&
-		echo &&
-		echo PROPS-END &&
-		cat greedy.delta &&
-		echo
-	} >greedydelta.dump &&
-	try_dump greedydelta.dump must_fail might_fail
-'
-
-test_expect_success 'set up svn repo' '
-	svnconf=$PWD/svnconf &&
-	mkdir -p "$svnconf" &&
-
-	if
-		svnadmin -h >/dev/null 2>&1 &&
-		svnadmin create simple-svn &&
-		svnadmin load simple-svn <"$TEST_DIRECTORY/t9135/svn.dump" &&
-		svn export --config-dir "$svnconf" "file://$PWD/simple-svn" simple-svnco
-	then
-		test_set_prereq SVNREPO
-	fi
-'
-
-test_expect_success SVNREPO 't9135/svn.dump' '
-	mkdir -p simple-git &&
-	(
-		cd simple-git &&
-		reinit_git &&
-		try_dump "$TEST_DIRECTORY/t9135/svn.dump"
-	) &&
-	(
-		cd simple-svnco &&
-		git init &&
-		git add . &&
-		git fetch ../simple-git master &&
-		git diff --exit-code FETCH_HEAD
-	)
-'
-
-test_done
diff --git a/third_party/git/t/t9011-svn-da.sh b/third_party/git/t/t9011-svn-da.sh
deleted file mode 100755
index ab1ef28fd9..0000000000
--- a/third_party/git/t/t9011-svn-da.sh
+++ /dev/null
@@ -1,248 +0,0 @@
-#!/bin/sh
-
-test_description='test parsing of svndiff0 files
-
-Using the "test-svn-fe -d" helper, check that svn-fe correctly
-interprets deltas using various facilities (some from the spec,
-some only learned from practice).
-'
-. ./test-lib.sh
-
->empty
-printf foo >preimage
-
-test_expect_success 'reject empty delta' '
-	test_must_fail test-svn-fe -d preimage empty 0
-'
-
-test_expect_success 'delta can empty file' '
-	printf "SVNQ" | q_to_nul >clear.delta &&
-	test-svn-fe -d preimage clear.delta 4 >actual &&
-	test_must_be_empty actual
-'
-
-test_expect_success 'reject svndiff2' '
-	printf "SVN\002" >bad.filetype &&
-	test_must_fail test-svn-fe -d preimage bad.filetype 4
-'
-
-test_expect_success 'one-window empty delta' '
-	printf "SVNQ%s" "QQQQQ" | q_to_nul >clear.onewindow &&
-	test-svn-fe -d preimage clear.onewindow 9 >actual &&
-	test_must_be_empty actual
-'
-
-test_expect_success 'reject incomplete window header' '
-	printf "SVNQ%s" "QQQQQ" | q_to_nul >clear.onewindow &&
-	printf "SVNQ%s" "QQ" | q_to_nul >clear.partialwindow &&
-	test_must_fail test-svn-fe -d preimage clear.onewindow 6 &&
-	test_must_fail test-svn-fe -d preimage clear.partialwindow 6
-'
-
-test_expect_success 'reject declared delta longer than actual delta' '
-	printf "SVNQ%s" "QQQQQ" | q_to_nul >clear.onewindow &&
-	printf "SVNQ%s" "QQ" | q_to_nul >clear.partialwindow &&
-	test_must_fail test-svn-fe -d preimage clear.onewindow 14 &&
-	test_must_fail test-svn-fe -d preimage clear.partialwindow 9
-'
-
-test_expect_success 'two-window empty delta' '
-	printf "SVNQ%s%s" "QQQQQ" "QQQQQ" | q_to_nul >clear.twowindow &&
-	test-svn-fe -d preimage clear.twowindow 14 >actual &&
-	test_must_fail test-svn-fe -d preimage clear.twowindow 13 &&
-	test_must_be_empty actual
-'
-
-test_expect_success 'noisy zeroes' '
-	printf "SVNQ%s" \
-		"RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQ" |
-		tr R "\200" |
-		q_to_nul >clear.noisy &&
-	len=$(wc -c <clear.noisy) &&
-	test-svn-fe -d preimage clear.noisy $len &&
-	test_must_be_empty actual
-'
-
-test_expect_success 'reject variable-length int in magic' '
-	printf "SVNRQ" | tr R "\200" | q_to_nul >clear.badmagic &&
-	test_must_fail test-svn-fe -d preimage clear.badmagic 5
-'
-
-test_expect_success 'reject truncated integer' '
-	printf "SVNQ%s%s" "QQQQQ" "QQQQRRQ" |
-		tr R "\200" |
-		q_to_nul >clear.fullint &&
-	printf "SVNQ%s%s" "QQQQQ" "QQQQRR" |
-		tr RT "\201" |
-		q_to_nul >clear.partialint &&
-	test_must_fail test-svn-fe -d preimage clear.fullint 15 &&
-	test-svn-fe -d preimage clear.fullint 16 &&
-	test_must_fail test-svn-fe -d preimage clear.partialint 15
-'
-
-test_expect_success 'nonempty (but unused) preimage view' '
-	printf "SVNQ%b" "Q\003QQQ" | q_to_nul >clear.readpreimage &&
-	test-svn-fe -d preimage clear.readpreimage 9 >actual &&
-	test_must_be_empty actual
-'
-
-test_expect_success 'preimage view: right endpoint cannot backtrack' '
-	printf "SVNQ%b%b" "Q\003QQQ" "Q\002QQQ" |
-		q_to_nul >clear.backtrack &&
-	test_must_fail test-svn-fe -d preimage clear.backtrack 14
-'
-
-test_expect_success 'preimage view: left endpoint can advance' '
-	printf "SVNQ%b%b" "Q\003QQQ" "\001\002QQQ" |
-		q_to_nul >clear.preshrink &&
-	printf "SVNQ%b%b" "Q\003QQQ" "\001\001QQQ" |
-		q_to_nul >clear.shrinkbacktrack &&
-	test-svn-fe -d preimage clear.preshrink 14 >actual &&
-	test_must_fail test-svn-fe -d preimage clear.shrinkbacktrack 14 &&
-	test_must_be_empty actual
-'
-
-test_expect_success 'preimage view: offsets compared by value' '
-	printf "SVNQ%b%b" "\001\001QQQ" "\0200Q\003QQQ" |
-		q_to_nul >clear.noisybacktrack &&
-	printf "SVNQ%b%b" "\001\001QQQ" "\0200\001\002QQQ" |
-		q_to_nul >clear.noisyadvance &&
-	test_must_fail test-svn-fe -d preimage clear.noisybacktrack 15 &&
-	test-svn-fe -d preimage clear.noisyadvance 15 &&
-	test_must_be_empty actual
-'
-
-test_expect_success 'preimage view: reject truncated preimage' '
-	printf "SVNQ%b" "\010QQQQ" | q_to_nul >clear.lateemptyread &&
-	printf "SVNQ%b" "\010\001QQQ" | q_to_nul >clear.latenonemptyread &&
-	printf "SVNQ%b" "\001\010QQQ" | q_to_nul >clear.longread &&
-	test_must_fail test-svn-fe -d preimage clear.lateemptyread 9 &&
-	test_must_fail test-svn-fe -d preimage clear.latenonemptyread 9 &&
-	test_must_fail test-svn-fe -d preimage clear.longread 9
-'
-
-test_expect_success 'forbid unconsumed inline data' '
-	printf "SVNQ%b%s%b%s" "QQQQ\003" "bar" "QQQQ\001" "x" |
-		q_to_nul >inline.clear &&
-	test_must_fail test-svn-fe -d preimage inline.clear 18 >actual
-'
-
-test_expect_success 'reject truncated inline data' '
-	printf "SVNQ%b%s" "QQQQ\003" "b" | q_to_nul >inline.trunc &&
-	test_must_fail test-svn-fe -d preimage inline.trunc 10
-'
-
-test_expect_success 'reject truncated inline data (after instruction section)' '
-	printf "SVNQ%b%b%s" "QQ\001\001\003" "\0201" "b" | q_to_nul >insn.trunc &&
-	test_must_fail test-svn-fe -d preimage insn.trunc 11
-'
-
-test_expect_success 'copyfrom_data' '
-	echo hi >expect &&
-	printf "SVNQ%b%b%b" "QQ\003\001\003" "\0203" "hi\n" | q_to_nul >copydat &&
-	test-svn-fe -d preimage copydat 13 >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'multiple copyfrom_data' '
-	echo hi >expect &&
-	printf "SVNQ%b%b%b%b%b" "QQ\003\002\003" "\0201\0202" "hi\n" \
-		"QQQ\002Q" "\0200Q" | q_to_nul >copy.multi &&
-	len=$(wc -c <copy.multi) &&
-	test-svn-fe -d preimage copy.multi $len >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'incomplete multiple insn' '
-	printf "SVNQ%b%b%b" "QQ\003\002\003" "\0203\0200" "hi\n" |
-		q_to_nul >copy.partial &&
-	len=$(wc -c <copy.partial) &&
-	test_must_fail test-svn-fe -d preimage copy.partial $len
-'
-
-test_expect_success 'catch attempt to copy missing data' '
-	printf "SVNQ%b%b%s%b%s" "QQ\002\002\001" "\0201\0201" "X" \
-			"QQQQ\002" "YZ" |
-		q_to_nul >copy.incomplete &&
-	len=$(wc -c <copy.incomplete) &&
-	test_must_fail test-svn-fe -d preimage copy.incomplete $len
-'
-
-test_expect_success 'copyfrom target to repeat data' '
-	printf foofoo >expect &&
-	printf "SVNQ%b%b%s" "QQ\006\004\003" "\0203\0100\003Q" "foo" |
-		q_to_nul >copytarget.repeat &&
-	len=$(wc -c <copytarget.repeat) &&
-	test-svn-fe -d preimage copytarget.repeat $len >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'copyfrom target out of order' '
-	printf foooof >expect &&
-	printf "SVNQ%b%b%s" \
-		"QQ\006\007\003" "\0203\0101\002\0101\001\0101Q" "foo" |
-		q_to_nul >copytarget.reverse &&
-	len=$(wc -c <copytarget.reverse) &&
-	test-svn-fe -d preimage copytarget.reverse $len >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'catch copyfrom future' '
-	printf "SVNQ%b%b%s" "QQ\004\004\003" "\0202\0101\002\0201" "XYZ" |
-		q_to_nul >copytarget.infuture &&
-	len=$(wc -c <copytarget.infuture) &&
-	test_must_fail test-svn-fe -d preimage copytarget.infuture $len
-'
-
-test_expect_success 'copy to sustain' '
-	printf XYXYXYXYXYXZ >expect &&
-	printf "SVNQ%b%b%s" "QQ\014\004\003" "\0202\0111Q\0201" "XYZ" |
-		q_to_nul >copytarget.sustain &&
-	len=$(wc -c <copytarget.sustain) &&
-	test-svn-fe -d preimage copytarget.sustain $len >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'catch copy that overflows' '
-	printf "SVNQ%b%b%s" "QQ\003\003\001" "\0201\0177Q" X |
-		q_to_nul >copytarget.overflow &&
-	len=$(wc -c <copytarget.overflow) &&
-	test_must_fail test-svn-fe -d preimage copytarget.overflow $len
-'
-
-test_expect_success 'copyfrom source' '
-	printf foo >expect &&
-	printf "SVNQ%b%b" "Q\003\003\002Q" "\003Q" | q_to_nul >copysource.all &&
-	test-svn-fe -d preimage copysource.all 11 >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'copy backwards' '
-	printf oof >expect &&
-	printf "SVNQ%b%b" "Q\003\003\006Q" "\001\002\001\001\001Q" |
-		q_to_nul >copysource.rev &&
-	test-svn-fe -d preimage copysource.rev 15 >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'offsets are relative to window' '
-	printf fo >expect &&
-	printf "SVNQ%b%b%b%b" "Q\003\001\002Q" "\001Q" \
-		"\002\001\001\002Q" "\001Q" |
-		q_to_nul >copysource.two &&
-	test-svn-fe -d preimage copysource.two 18 >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'example from notes/svndiff' '
-	printf aaaaccccdddddddd >expect &&
-	printf aaaabbbbcccc >source &&
-	printf "SVNQ%b%b%s" "Q\014\020\007\001" \
-		"\004Q\004\010\0201\0107\010" d |
-		q_to_nul >delta.example &&
-	len=$(wc -c <delta.example) &&
-	test-svn-fe -d source delta.example $len >actual &&
-	test_cmp expect actual
-'
-
-test_done
diff --git a/third_party/git/t/t9020-remote-svn.sh b/third_party/git/t/t9020-remote-svn.sh
deleted file mode 100755
index 6fca08e5e3..0000000000
--- a/third_party/git/t/t9020-remote-svn.sh
+++ /dev/null
@@ -1,89 +0,0 @@
-#!/bin/sh
-
-test_description='tests remote-svn'
-
-. ./test-lib.sh
-
-MARKSPATH=.git/info/fast-import/remote-svn
-
-if ! test_have_prereq PYTHON
-then
-	skip_all='skipping remote-svn tests, python not available'
-	test_done
-fi
-
-# Override svnrdump with our simulator
-PATH="$HOME:$PATH"
-export PATH PYTHON_PATH GIT_BUILD_DIR
-
-write_script "$HOME/svnrdump" <<\EOF
-exec "$PYTHON_PATH" "$GIT_BUILD_DIR/contrib/svn-fe/svnrdump_sim.py" "$@"
-EOF
-
-init_git () {
-	rm -fr .git &&
-	git init &&
-	#git remote add svnsim testsvn::sim:///$TEST_DIRECTORY/t9020/example.svnrdump
-	# let's reuse an existing dump file!?
-	git remote add svnsim "testsvn::sim://$TEST_DIRECTORY/t9154/svn.dump"
-	git remote add svnfile "testsvn::file://$TEST_DIRECTORY/t9154/svn.dump"
-}
-
-if test -e "$GIT_BUILD_DIR/git-remote-testsvn"
-then
-	test_set_prereq REMOTE_SVN
-fi
-
-test_debug '
-	git --version
-	type git
-	type svnrdump
-'
-
-test_expect_success REMOTE_SVN 'simple fetch' '
-	init_git &&
-	git fetch svnsim &&
-	test_cmp .git/refs/svn/svnsim/master .git/refs/remotes/svnsim/master  &&
-	cp .git/refs/remotes/svnsim/master master.good
-'
-
-test_debug '
-	cat .git/refs/svn/svnsim/master
-	cat .git/refs/remotes/svnsim/master
-'
-
-test_expect_success REMOTE_SVN 'repeated fetch, nothing shall change' '
-	git fetch svnsim &&
-	test_cmp master.good .git/refs/remotes/svnsim/master
-'
-
-test_expect_success REMOTE_SVN 'fetch from a file:// url gives the same result' '
-	git fetch svnfile
-'
-
-test_expect_failure REMOTE_SVN 'the sha1 differ because the git-svn-id line in the commit msg contains the url' '
-	test_cmp .git/refs/remotes/svnfile/master .git/refs/remotes/svnsim/master
-'
-
-test_expect_success REMOTE_SVN 'mark-file regeneration' '
-	# filter out any other marks, that can not be regenerated. Only up to 3 digit revisions are allowed here
-	grep ":[0-9]\{1,3\} " $MARKSPATH/svnsim.marks > $MARKSPATH/svnsim.marks.old &&
-	rm $MARKSPATH/svnsim.marks &&
-	git fetch svnsim &&
-	test_cmp $MARKSPATH/svnsim.marks.old $MARKSPATH/svnsim.marks
-'
-
-test_expect_success REMOTE_SVN 'incremental imports must lead to the same head' '
-	SVNRMAX=3 &&
-	export SVNRMAX &&
-	init_git &&
-	git fetch svnsim &&
-	test_cmp .git/refs/svn/svnsim/master .git/refs/remotes/svnsim/master  &&
-	unset SVNRMAX &&
-	git fetch svnsim &&
-	test_cmp master.good .git/refs/remotes/svnsim/master
-'
-
-test_debug 'git branch -a'
-
-test_done
diff --git a/third_party/git/t/t9100-git-svn-basic.sh b/third_party/git/t/t9100-git-svn-basic.sh
index 2c309a57d9..e4bb22034e 100755
--- a/third_party/git/t/t9100-git-svn-basic.sh
+++ b/third_party/git/t/t9100-git-svn-basic.sh
@@ -63,16 +63,16 @@ test_expect_success "$name" '
 
 
 name='detect node change from file to directory #1'
-test_expect_success "$name" "
+test_expect_success "$name" '
 	mkdir dir/new_file &&
 	mv dir/file dir/new_file/file &&
 	mv dir/new_file dir/file &&
 	git update-index --remove dir/file &&
 	git update-index --add dir/file/file &&
-	git commit -m '$name' &&
+	git commit -m "$name" &&
 	test_must_fail git svn set-tree --find-copies-harder --rmdir \
 		remotes/git-svn..mybranch
-"
+'
 
 
 name='detect node change from directory to file #1'
@@ -200,17 +200,19 @@ GIT_SVN_ID=alt
 export GIT_SVN_ID
 test_expect_success "$name" \
     'git svn init "$svnrepo" && git svn fetch &&
-     git rev-list --pretty=raw remotes/git-svn | grep ^tree | uniq > a &&
-     git rev-list --pretty=raw remotes/alt | grep ^tree | uniq > b &&
+     git log --format="tree %T %s" remotes/git-svn |
+	awk "!seen[\$0]++ { print \$1, \$2 }" >a &&
+     git log --format="tree %T" alt >b &&
      test_cmp a b'
 
 name='check imported tree checksums expected tree checksums'
 rm -f expected
 if test_have_prereq UTF8
 then
-	echo tree dc68b14b733e4ec85b04ab6f712340edc5dc936e > expected
+	echo tree dc68b14b733e4ec85b04ab6f712340edc5dc936e > expected.sha1
+	echo tree b95b55b29d771f5eb73aa9b9d52d02fe11a2538c2feb0829f754ce20a91d98eb > expected.sha256
 fi
-cat >> expected <<\EOF
+cat >> expected.sha1 <<\EOF
 tree c3322890dcf74901f32d216f05c5044f670ce632
 tree d3ccd5035feafd17b030c5732e7808cc49122853
 tree d03e1630363d4881e68929d532746b20b0986b83
@@ -220,8 +222,20 @@ tree 149d63cd5878155c846e8c55d7d8487de283f89e
 tree d667270a1f7b109f5eb3aaea21ede14b56bfdd6e
 tree 8f51f74cf0163afc9ad68a4b1537288c4558b5a4
 EOF
+cat >> expected.sha256 <<\EOF
+tree 8d12756699d0b5b110514240a0ff141f6cbf8891fd69ab05e5594196fb437c9f
+tree 8187168d33f7d4ccb8c1cc6e99532810aaccb47658f35d19b3803072d1128d7a
+tree 74e535d85da8ee25eb23d7b506790c5ab3ccdb1ba0826bd57625ed44ef361650
+tree 6fd7dd963e3cdca0cbd6368ed3cfcc8037cc154d2e7719d9d369a0952364fd95
+tree 1fd6cec6aa95102d69266e20419bb62ec2a06372d614b9850ef23ff204103bb4
+tree 6fd7dd963e3cdca0cbd6368ed3cfcc8037cc154d2e7719d9d369a0952364fd95
+tree deb2b7ac79cd8ce6f52af6a5a0a08691e94ba74a2ed55966bb27dbec551730eb
+tree 59e2e936761188476a7752034e8aa0a822b34050c8504b0dfd946407f4bc9215
+EOF
 
-test_expect_success POSIXPERM,SYMLINKS "$name" "test_cmp expected a"
+test_expect_success POSIXPERM,SYMLINKS "$name" '
+	test_cmp expected.$(test_oid algo) a
+'
 
 test_expect_success 'exit if remote refs are ambigious' '
         git config --add svn-remote.svn.fetch \
diff --git a/third_party/git/t/t9101-git-svn-props.sh b/third_party/git/t/t9101-git-svn-props.sh
index c26c4b0927..8b5681dd68 100755
--- a/third_party/git/t/t9101-git-svn-props.sh
+++ b/third_party/git/t/t9101-git-svn-props.sh
@@ -160,11 +160,13 @@ cat >create-ignore.expect <<\EOF
 /no-such-file*
 EOF
 
-cat >create-ignore-index.expect <<\EOF
-100644 8c52e5dfcd0a8b6b6bcfe6b41b89bcbf493718a5 0	.gitignore
-100644 8c52e5dfcd0a8b6b6bcfe6b41b89bcbf493718a5 0	deeply/.gitignore
-100644 8c52e5dfcd0a8b6b6bcfe6b41b89bcbf493718a5 0	deeply/nested/.gitignore
-100644 8c52e5dfcd0a8b6b6bcfe6b41b89bcbf493718a5 0	deeply/nested/directory/.gitignore
+expectoid=$(git hash-object create-ignore.expect)
+
+cat >create-ignore-index.expect <<EOF
+100644 $expectoid 0	.gitignore
+100644 $expectoid 0	deeply/.gitignore
+100644 $expectoid 0	deeply/nested/.gitignore
+100644 $expectoid 0	deeply/nested/directory/.gitignore
 EOF
 
 test_expect_success 'test create-ignore' "
diff --git a/third_party/git/t/t9104-git-svn-follow-parent.sh b/third_party/git/t/t9104-git-svn-follow-parent.sh
index 5e0ad19177..67eed2fefc 100755
--- a/third_party/git/t/t9104-git-svn-follow-parent.sh
+++ b/third_party/git/t/t9104-git-svn-follow-parent.sh
@@ -161,6 +161,7 @@ test_expect_success "track initial change if it was only made to parent" '
 	'
 
 test_expect_success "follow-parent is atomic" '
+	record_size=$(($(test_oid rawsz) + 4)) &&
 	(
 		cd wc &&
 		svn_cmd up &&
@@ -186,7 +187,7 @@ test_expect_success "follow-parent is atomic" '
 	mkdir -p "$GIT_DIR"/svn/refs/remotes/flunk@18 &&
 	rev_map=$(cd "$GIT_DIR"/svn/refs/remotes/stunk && ls .rev_map*) &&
 	dd if="$GIT_DIR"/svn/refs/remotes/stunk/$rev_map \
-	   of="$GIT_DIR"/svn/refs/remotes/flunk@18/$rev_map bs=24 count=1 &&
+	   of="$GIT_DIR"/svn/refs/remotes/flunk@18/$rev_map bs=$record_size count=1 &&
 	rm -rf "$GIT_DIR"/svn/refs/remotes/stunk &&
 	git svn init --minimize-url -i flunk "$svnrepo"/flunk &&
 	git svn fetch -i flunk &&
diff --git a/third_party/git/t/t9106-git-svn-commit-diff-clobber.sh b/third_party/git/t/t9106-git-svn-commit-diff-clobber.sh
index dbe8deac0d..aec45bca3b 100755
--- a/third_party/git/t/t9106-git-svn-commit-diff-clobber.sh
+++ b/third_party/git/t/t9106-git-svn-commit-diff-clobber.sh
@@ -92,7 +92,8 @@ test_expect_success 'multiple dcommit from git svn will not clobber svn' "
 
 
 test_expect_success 'check that rebase really failed' '
-	test -d .git/rebase-apply
+	git status >output &&
+	grep currently.rebasing output
 '
 
 test_expect_success 'resolve, continue the rebase and dcommit' "
diff --git a/third_party/git/t/t9108-git-svn-glob.sh b/third_party/git/t/t9108-git-svn-glob.sh
index 6990f64364..d5939d4753 100755
--- a/third_party/git/t/t9108-git-svn-glob.sh
+++ b/third_party/git/t/t9108-git-svn-glob.sh
@@ -48,7 +48,7 @@ test_expect_success 'test refspec globbing' '
 	                 "tags/*/src/a:refs/remotes/tags/*" &&
 	git svn multi-fetch &&
 	git log --pretty=oneline refs/remotes/tags/end >actual &&
-	sed -e "s/^.\{41\}//" actual >output.end &&
+	cut -d" " -f2- actual >output.end &&
 	test_cmp expect.end output.end &&
 	test "$(git rev-parse refs/remotes/tags/end~1)" = \
 		"$(git rev-parse refs/remotes/branches/start)" &&
@@ -84,7 +84,7 @@ test_expect_success 'test left-hand-side only globbing' '
 	test $(git rev-parse refs/remotes/two/tags/end~3) = \
 	     $(git rev-parse refs/remotes/two/branches/start) &&
 	git log --pretty=oneline refs/remotes/two/tags/end >actual &&
-	sed -e "s/^.\{41\}//" actual >output.two &&
+	cut -d" " -f2- actual >output.two &&
 	test_cmp expect.two output.two
 	'
 
diff --git a/third_party/git/t/t9109-git-svn-multi-glob.sh b/third_party/git/t/t9109-git-svn-multi-glob.sh
index c1e7542a37..648dcee1ea 100755
--- a/third_party/git/t/t9109-git-svn-multi-glob.sh
+++ b/third_party/git/t/t9109-git-svn-multi-glob.sh
@@ -48,7 +48,7 @@ test_expect_success 'test refspec globbing' '
 	                 "tags/*/src/a:refs/remotes/tags/*" &&
 	git svn multi-fetch &&
 	git log --pretty=oneline refs/remotes/tags/end >actual &&
-	sed -e "s/^.\{41\}//" actual >output.end &&
+	cut -d" " -f2- actual >output.end &&
 	test_cmp expect.end output.end &&
 	test "$(git rev-parse refs/remotes/tags/end~1)" = \
 		"$(git rev-parse refs/remotes/branches/v1/start)" &&
@@ -84,7 +84,7 @@ test_expect_success 'test left-hand-side only globbing' '
 	test $(git rev-parse refs/remotes/two/tags/end~3) = \
 	     $(git rev-parse refs/remotes/two/branches/v1/start) &&
 	git log --pretty=oneline refs/remotes/two/tags/end >actual &&
-	sed -e "s/^.\{41\}//" actual >output.two &&
+	cut -d" " -f2- actual >output.two &&
 	test_cmp expect.two output.two
 	'
 cat > expect.four <<EOF
@@ -135,7 +135,7 @@ test_expect_success 'test another branch' '
 	test $(git rev-parse refs/remotes/four/tags/next~2) = \
 	     $(git rev-parse refs/remotes/four/branches/v2/start) &&
 	git log --pretty=oneline refs/remotes/four/tags/next >actual &&
-	sed -e "s/^.\{41\}//" actual >output.four &&
+	cut -d" " -f2- actual >output.four &&
 	test_cmp expect.four output.four
 	'
 
diff --git a/third_party/git/t/t9116-git-svn-log.sh b/third_party/git/t/t9116-git-svn-log.sh
index 45773ee560..0a9f1ef366 100755
--- a/third_party/git/t/t9116-git-svn-log.sh
+++ b/third_party/git/t/t9116-git-svn-log.sh
@@ -43,14 +43,18 @@ test_expect_success 'setup repository and import' '
 
 test_expect_success 'run log' "
 	git reset --hard origin/a &&
-	git svn log -r2 origin/trunk | grep ^r2 &&
-	git svn log -r4 origin/trunk | grep ^r4 &&
-	git svn log -r3 | grep ^r3
+	git svn log -r2 origin/trunk >out &&
+	grep ^r2 out &&
+	git svn log -r4 origin/trunk >out &&
+	grep ^r4 out &&
+	git svn log -r3 >out &&
+	grep ^r3 out
 	"
 
 test_expect_success 'run log against a from trunk' "
 	git reset --hard origin/trunk &&
-	git svn log -r3 origin/a | grep ^r3
+	git svn log -r3 origin/a >out &&
+	grep ^r3 out
 	"
 
 printf 'r1 \nr2 \nr4 \n' > expected-range-r1-r2-r4
diff --git a/third_party/git/t/t9141-git-svn-multiple-branches.sh b/third_party/git/t/t9141-git-svn-multiple-branches.sh
index 8e7f7d68b7..bf168a3645 100755
--- a/third_party/git/t/t9141-git-svn-multiple-branches.sh
+++ b/third_party/git/t/t9141-git-svn-multiple-branches.sh
@@ -90,10 +90,10 @@ test_expect_success 'Multiple branch or tag paths require -d' '
 	) &&
 	( cd svn_project &&
 		svn_cmd up &&
-		test_must_fail test -d b_one/Nope &&
-		test_must_fail test -d b_two/Nope &&
-		test_must_fail test -d tags_A/Tagless &&
-		test_must_fail test -d tags_B/Tagless
+		test_path_is_missing b_one/Nope &&
+		test_path_is_missing b_two/Nope &&
+		test_path_is_missing tags_A/Tagless &&
+		test_path_is_missing tags_B/Tagless
 	)
 '
 
diff --git a/third_party/git/t/t9160-git-svn-preserve-empty-dirs.sh b/third_party/git/t/t9160-git-svn-preserve-empty-dirs.sh
index 0ede3cfedb..36c6b1a12f 100755
--- a/third_party/git/t/t9160-git-svn-preserve-empty-dirs.sh
+++ b/third_party/git/t/t9160-git-svn-preserve-empty-dirs.sh
@@ -86,8 +86,8 @@ test_expect_success 'remove non-last entry from directory' '
 		cd "$GIT_REPO" &&
 		git checkout HEAD~2
 	) &&
-	test_must_fail test -f "$GIT_REPO"/2/.gitignore &&
-	test_must_fail test -f "$GIT_REPO"/3/.gitignore
+	test_path_is_missing "$GIT_REPO"/2/.gitignore &&
+	test_path_is_missing "$GIT_REPO"/3/.gitignore
 '
 
 # After re-cloning the repository with --placeholder-file specified, there
diff --git a/third_party/git/t/t9164-git-svn-dcommit-concurrent.sh b/third_party/git/t/t9164-git-svn-dcommit-concurrent.sh
index 90346ff4e9..8466269bf5 100755
--- a/third_party/git/t/t9164-git-svn-dcommit-concurrent.sh
+++ b/third_party/git/t/t9164-git-svn-dcommit-concurrent.sh
@@ -92,7 +92,7 @@ test_expect_success 'check if post-commit hook creates a concurrent commit' '
 		echo 1 >> file &&
 		svn_cmd commit -m "changing file" &&
 		svn_cmd up &&
-		test_must_fail test_cmp auto_updated_file au_file_saved
+		! test_cmp auto_updated_file au_file_saved
 	)
 '
 
@@ -103,7 +103,7 @@ test_expect_success 'check if pre-commit hook fails' '
 		echo 2 >> file &&
 		svn_cmd commit -m "changing file once again" &&
 		echo 3 >> file &&
-		test_must_fail svn_cmd commit -m "this commit should fail" &&
+		! svn_cmd commit -m "this commit should fail" &&
 		svn_cmd revert file
 	)
 '
diff --git a/third_party/git/t/t9168-git-svn-partially-globbed-names.sh b/third_party/git/t/t9168-git-svn-partially-globbed-names.sh
index bdf6e84999..854b3419b2 100755
--- a/third_party/git/t/t9168-git-svn-partially-globbed-names.sh
+++ b/third_party/git/t/t9168-git-svn-partially-globbed-names.sh
@@ -49,7 +49,7 @@ test_expect_success 'test refspec prefixed globbing' '
 			 "tags/t_*/src/a:refs/remotes/tags/t_*" &&
 	git svn multi-fetch &&
 	git log --pretty=oneline refs/remotes/tags/t_end >actual &&
-	sed -e "s/^.\{41\}//" actual >output.end &&
+	cut -d" " -f2- actual >output.end &&
 	test_cmp expect.end output.end &&
 	test "$(git rev-parse refs/remotes/tags/t_end~1)" = \
 		"$(git rev-parse refs/remotes/branches/b_start)" &&
@@ -87,7 +87,7 @@ test_expect_success 'test left-hand-side only prefixed globbing' '
 	test $(git rev-parse refs/remotes/two/tags/t_end~3) = \
 	     $(git rev-parse refs/remotes/two/branches/b_start) &&
 	git log --pretty=oneline refs/remotes/two/tags/t_end >actual &&
-	sed -e "s/^.\{41\}//" actual >output.two &&
+	cut -d" " -f2- actual >output.two &&
 	test_cmp expect.two output.two
 	'
 
@@ -129,7 +129,7 @@ test_expect_success 'test prefixed globs match just prefix' '
 	test $(git rev-parse refs/remotes/three/tags/t_~1) = \
 	     $(git rev-parse refs/remotes/three/branches/b_) &&
 	git log --pretty=oneline refs/remotes/three/tags/t_ >actual &&
-	sed -e "s/^.\{41\}//" actual >output.three &&
+	cut -d" " -f2- actual >output.three &&
 	test_cmp expect.three output.three
 	'
 
@@ -199,7 +199,7 @@ test_expect_success 'test globbing in the middle of the word' '
 	test $(git rev-parse refs/remotes/five/tags/fghij~1) = \
 	     $(git rev-parse refs/remotes/five/branches/abcde) &&
 	git log --pretty=oneline refs/remotes/five/tags/fghij >actual &&
-	sed -e "s/^.\{41\}//" actual >output.five &&
+	cut -d" " -f2- actual >output.five &&
 	test_cmp expect.five output.five
 	'
 
diff --git a/third_party/git/t/t9300-fast-import.sh b/third_party/git/t/t9300-fast-import.sh
index 141b7fa35e..308c1ef42c 100755
--- a/third_party/git/t/t9300-fast-import.sh
+++ b/third_party/git/t/t9300-fast-import.sh
@@ -85,6 +85,36 @@ test_expect_success 'A: create pack from stdin' '
 	An annotated tag that annotates a blob.
 	EOF
 
+	tag to-be-deleted
+	from :3
+	data <<EOF
+	Another annotated tag that annotates a blob.
+	EOF
+
+	reset refs/tags/to-be-deleted
+	from $ZERO_OID
+
+	tag nested
+	mark :6
+	from :4
+	data <<EOF
+	Tag of our lovely commit
+	EOF
+
+	reset refs/tags/nested
+	from $ZERO_OID
+
+	tag nested
+	mark :7
+	from :6
+	data <<EOF
+	Tag of tag of our lovely commit
+	EOF
+
+	alias
+	mark :8
+	to :5
+
 	INPUT_END
 	git fast-import --export-marks=marks.out <input &&
 	git whatchanged master
@@ -157,12 +187,19 @@ test_expect_success 'A: verify tag/series-A-blob' '
 	test_cmp expect actual
 '
 
+test_expect_success 'A: verify tag deletion is successful' '
+	test_must_fail git rev-parse --verify refs/tags/to-be-deleted
+'
+
 test_expect_success 'A: verify marks output' '
 	cat >expect <<-EOF &&
 	:2 $(git rev-parse --verify master:file2)
 	:3 $(git rev-parse --verify master:file3)
 	:4 $(git rev-parse --verify master:file4)
 	:5 $(git rev-parse --verify master^0)
+	:6 $(git cat-file tag nested | grep object | cut -d" " -f 2)
+	:7 $(git rev-parse --verify nested)
+	:8 $(git rev-parse --verify master^0)
 	EOF
 	test_cmp expect marks.out
 '
@@ -247,8 +284,9 @@ test_expect_success 'A: verify pack' '
 '
 
 test_expect_success 'A: verify diff' '
+	copy=$(git rev-parse --verify master:file2) &&
 	cat >expect <<-EOF &&
-	:000000 100755 0000000000000000000000000000000000000000 7123f7f44e39be127c5eb701e5968176ee9d78b1 A	copy-of-file2
+	:000000 100755 $ZERO_OID $copy A	copy-of-file2
 	EOF
 	git diff-tree -M -r master verify--import-marks >actual &&
 	compare_diff_raw expect actual &&
@@ -327,7 +365,7 @@ test_expect_success 'B: fail on invalid blob sha1' '
 	COMMIT
 
 	from refs/heads/master
-	M 755 0000000000000000000000000000000000000001 zero1
+	M 755 $(echo $ZERO_OID | sed -e "s/0$/1/") zero1
 
 	INPUT_END
 
@@ -373,6 +411,34 @@ test_expect_success 'B: accept empty committer' '
 	test -z "$out"
 '
 
+test_expect_success 'B: reject invalid timezone' '
+	cat >input <<-INPUT_END &&
+	commit refs/heads/invalid-timezone
+	committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1234567890 +051800
+	data <<COMMIT
+	empty commit
+	COMMIT
+	INPUT_END
+
+	test_when_finished "git update-ref -d refs/heads/invalid-timezone" &&
+	test_must_fail git fast-import <input
+'
+
+test_expect_success 'B: accept invalid timezone with raw-permissive' '
+	cat >input <<-INPUT_END &&
+	commit refs/heads/invalid-timezone
+	committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1234567890 +051800
+	data <<COMMIT
+	empty commit
+	COMMIT
+	INPUT_END
+
+	git init invalid-timezone &&
+	git -C invalid-timezone fast-import --date-format=raw-permissive <input &&
+	git -C invalid-timezone cat-file -p invalid-timezone >out &&
+	grep "1234567890 [+]051800" out
+'
+
 test_expect_success 'B: accept and fixup committer with no name' '
 	cat >input <<-INPUT_END &&
 	commit refs/heads/empty-committer-2
@@ -463,6 +529,7 @@ test_expect_success 'B: fail on invalid committer (5)' '
 test_expect_success 'C: incremental import create pack from stdin' '
 	newf=$(echo hi newf | git hash-object -w --stdin) &&
 	oldf=$(git rev-parse --verify master:file2) &&
+	thrf=$(git rev-parse --verify master:file3) &&
 	test_tick &&
 	cat >input <<-INPUT_END &&
 	commit refs/heads/branch
@@ -505,10 +572,11 @@ test_expect_success 'C: verify commit' '
 '
 
 test_expect_success 'C: validate rename result' '
+	zero=$ZERO_OID &&
 	cat >expect <<-EOF &&
-	:000000 100755 0000000000000000000000000000000000000000 f1fb5da718392694d0076d677d6d0e364c79b0bc A	file2/newf
-	:100644 100644 7123f7f44e39be127c5eb701e5968176ee9d78b1 7123f7f44e39be127c5eb701e5968176ee9d78b1 R100	file2	file2/oldf
-	:100644 000000 0d92e9f3374ae2947c23aa477cbc68ce598135f1 0000000000000000000000000000000000000000 D	file3
+	:000000 100755 $zero $newf A	file2/newf
+	:100644 100644 $oldf $oldf R100	file2	file2/oldf
+	:100644 000000 $thrf $zero D	file3
 	EOF
 	git diff-tree -M -r master branch >actual &&
 	compare_diff_raw expect actual
@@ -549,9 +617,11 @@ test_expect_success 'D: verify pack' '
 '
 
 test_expect_success 'D: validate new files added' '
+	f5id=$(echo "$file5_data" | git hash-object --stdin) &&
+	f6id=$(echo "$file6_data" | git hash-object --stdin) &&
 	cat >expect <<-EOF &&
-	:000000 100755 0000000000000000000000000000000000000000 e74b7d465e52746be2b4bae983670711e6e66657 A	newdir/exec.sh
-	:000000 100644 0000000000000000000000000000000000000000 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 A	newdir/interesting
+	:000000 100755 $ZERO_OID $f6id A	newdir/exec.sh
+	:000000 100644 $ZERO_OID $f5id A	newdir/interesting
 	EOF
 	git diff-tree -M -r branch^ branch >actual &&
 	compare_diff_raw expect actual
@@ -714,12 +784,13 @@ test_expect_success 'H: verify pack' '
 '
 
 test_expect_success 'H: validate old files removed, new files added' '
+	f4id=$(git rev-parse HEAD:file4) &&
 	cat >expect <<-EOF &&
-	:100755 000000 f1fb5da718392694d0076d677d6d0e364c79b0bc 0000000000000000000000000000000000000000 D	file2/newf
-	:100644 000000 7123f7f44e39be127c5eb701e5968176ee9d78b1 0000000000000000000000000000000000000000 D	file2/oldf
-	:100755 000000 85df50785d62d3b05ab03d9cbf7e4a0b49449730 0000000000000000000000000000000000000000 D	file4
-	:100644 100644 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 R100	newdir/interesting	h/e/l/lo
-	:100755 000000 e74b7d465e52746be2b4bae983670711e6e66657 0000000000000000000000000000000000000000 D	newdir/exec.sh
+	:100755 000000 $newf $zero D	file2/newf
+	:100644 000000 $oldf $zero D	file2/oldf
+	:100755 000000 $f4id $zero D	file4
+	:100644 100644 $f5id $f5id R100	newdir/interesting	h/e/l/lo
+	:100755 000000 $f6id $zero D	newdir/exec.sh
 	EOF
 	git diff-tree -M -r H^ H >actual &&
 	compare_diff_raw expect actual
@@ -870,14 +941,15 @@ test_expect_success 'L: verify internal tree sorting' '
 	INPUT_END
 
 	cat >expect <<-EXPECT_END &&
-	:100644 100644 4268632... 55d3a52... M	b.
-	:040000 040000 0ae5cac... 443c768... M	b
-	:100644 100644 4268632... 55d3a52... M	ba
+	:100644 100644 M	b.
+	:040000 040000 M	b
+	:100644 100644 M	ba
 	EXPECT_END
 
 	git fast-import <input &&
 	GIT_PRINT_SHA1_ELLIPSIS="yes" git diff-tree --abbrev --raw L^ L >output &&
-	test_cmp expect output
+	cut -d" " -f1,2,5 output >actual &&
+	test_cmp expect actual
 '
 
 test_expect_success 'L: nested tree copy does not corrupt deltas' '
@@ -939,7 +1011,7 @@ test_expect_success 'M: rename file in same subdirectory' '
 	INPUT_END
 
 	cat >expect <<-EOF &&
-	:100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc R100	file2/newf	file2/n.e.w.f
+	:100755 100755 $newf $newf R100	file2/newf	file2/n.e.w.f
 	EOF
 	git fast-import <input &&
 	git diff-tree -M -r M1^ M1 >actual &&
@@ -960,7 +1032,7 @@ test_expect_success 'M: rename file to new subdirectory' '
 	INPUT_END
 
 	cat >expect <<-EOF &&
-	:100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc R100	file2/newf	i/am/new/to/you
+	:100755 100755 $newf $newf R100	file2/newf	i/am/new/to/you
 	EOF
 	git fast-import <input &&
 	git diff-tree -M -r M2^ M2 >actual &&
@@ -981,7 +1053,7 @@ test_expect_success 'M: rename subdirectory to new subdirectory' '
 	INPUT_END
 
 	cat >expect <<-EOF &&
-	:100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc R100	i/am/new/to/you	other/sub/am/new/to/you
+	:100755 100755 $newf $newf R100	i/am/new/to/you	other/sub/am/new/to/you
 	EOF
 	git fast-import <input &&
 	git diff-tree -M -r M3^ M3 >actual &&
@@ -1002,15 +1074,14 @@ test_expect_success 'M: rename root to subdirectory' '
 	INPUT_END
 
 	cat >expect <<-EOF &&
-	:100644 100644 7123f7f44e39be127c5eb701e5968176ee9d78b1 7123f7f44e39be127c5eb701e5968176ee9d78b1 R100	file2/oldf	sub/file2/oldf
-	:100755 100755 85df50785d62d3b05ab03d9cbf7e4a0b49449730 85df50785d62d3b05ab03d9cbf7e4a0b49449730 R100	file4	sub/file4
-	:100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc R100	i/am/new/to/you	sub/i/am/new/to/you
-	:100755 100755 e74b7d465e52746be2b4bae983670711e6e66657 e74b7d465e52746be2b4bae983670711e6e66657 R100	newdir/exec.sh	sub/newdir/exec.sh
-	:100644 100644 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 R100	newdir/interesting	sub/newdir/interesting
+	:100644 100644 $oldf $oldf R100	file2/oldf	sub/file2/oldf
+	:100755 100755 $f4id $f4id R100	file4	sub/file4
+	:100755 100755 $newf $newf R100	i/am/new/to/you	sub/i/am/new/to/you
+	:100755 100755 $f6id $f6id R100	newdir/exec.sh	sub/newdir/exec.sh
+	:100644 100644 $f5id $f5id R100	newdir/interesting	sub/newdir/interesting
 	EOF
 	git fast-import <input &&
 	git diff-tree -M -r M4^ M4 >actual &&
-	cat actual &&
 	compare_diff_raw expect actual
 '
 
@@ -1033,7 +1104,7 @@ test_expect_success 'N: copy file in same subdirectory' '
 	INPUT_END
 
 	cat >expect <<-EOF &&
-	:100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc C100	file2/newf	file2/n.e.w.f
+	:100755 100755 $newf $newf C100	file2/newf	file2/n.e.w.f
 	EOF
 	git fast-import <input &&
 	git diff-tree -C --find-copies-harder -r N1^ N1 >actual &&
@@ -1065,9 +1136,9 @@ test_expect_success 'N: copy then modify subdirectory' '
 	INPUT_END
 
 	cat >expect <<-EOF &&
-	:100644 100644 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 C100	newdir/interesting	file3/file5
-	:100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc C100	file2/newf	file3/newf
-	:100644 100644 7123f7f44e39be127c5eb701e5968176ee9d78b1 7123f7f44e39be127c5eb701e5968176ee9d78b1 C100	file2/oldf	file3/oldf
+	:100644 100644 $f5id $f5id C100	newdir/interesting	file3/file5
+	:100755 100755 $newf $newf C100	file2/newf	file3/newf
+	:100644 100644 $oldf $oldf C100	file2/oldf	file3/oldf
 	EOF
 	git fast-import <input &&
 	git diff-tree -C --find-copies-harder -r N2^^ N2 >actual &&
@@ -1098,9 +1169,9 @@ test_expect_success 'N: copy dirty subdirectory' '
 '
 
 test_expect_success 'N: copy directory by id' '
-	cat >expect <<-\EOF &&
-	:100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc C100	file2/newf	file3/newf
-	:100644 100644 7123f7f44e39be127c5eb701e5968176ee9d78b1 7123f7f44e39be127c5eb701e5968176ee9d78b1 C100	file2/oldf	file3/oldf
+	cat >expect <<-EOF &&
+	:100755 100755 $newf $newf C100	file2/newf	file3/newf
+	:100644 100644 $oldf $oldf C100	file2/oldf	file3/oldf
 	EOF
 	subdir=$(git rev-parse refs/heads/branch^0:file2) &&
 	cat >input <<-INPUT_END &&
@@ -1119,9 +1190,9 @@ test_expect_success 'N: copy directory by id' '
 '
 
 test_expect_success PIPE 'N: read and copy directory' '
-	cat >expect <<-\EOF &&
-	:100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc C100	file2/newf	file3/newf
-	:100644 100644 7123f7f44e39be127c5eb701e5968176ee9d78b1 7123f7f44e39be127c5eb701e5968176ee9d78b1 C100	file2/oldf	file3/oldf
+	cat >expect <<-EOF &&
+	:100755 100755 $newf $newf C100	file2/newf	file3/newf
+	:100644 100644 $oldf $oldf C100	file2/oldf	file3/oldf
 	EOF
 	git update-ref -d refs/heads/N4 &&
 	rm -f backflow &&
@@ -1190,9 +1261,9 @@ test_expect_success PIPE 'N: empty directory reads as missing' '
 '
 
 test_expect_success 'N: copy root directory by tree hash' '
-	cat >expect <<-\EOF &&
-	:100755 000000 f1fb5da718392694d0076d677d6d0e364c79b0bc 0000000000000000000000000000000000000000 D	file3/newf
-	:100644 000000 7123f7f44e39be127c5eb701e5968176ee9d78b1 0000000000000000000000000000000000000000 D	file3/oldf
+	cat >expect <<-EOF &&
+	:100755 000000 $newf $zero D	file3/newf
+	:100644 000000 $oldf $zero D	file3/oldf
 	EOF
 	root=$(git rev-parse refs/heads/branch^0^{tree}) &&
 	cat >input <<-INPUT_END &&
@@ -1211,12 +1282,12 @@ test_expect_success 'N: copy root directory by tree hash' '
 '
 
 test_expect_success 'N: copy root by path' '
-	cat >expect <<-\EOF &&
-	:100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc C100	file2/newf	oldroot/file2/newf
-	:100644 100644 7123f7f44e39be127c5eb701e5968176ee9d78b1 7123f7f44e39be127c5eb701e5968176ee9d78b1 C100	file2/oldf	oldroot/file2/oldf
-	:100755 100755 85df50785d62d3b05ab03d9cbf7e4a0b49449730 85df50785d62d3b05ab03d9cbf7e4a0b49449730 C100	file4	oldroot/file4
-	:100755 100755 e74b7d465e52746be2b4bae983670711e6e66657 e74b7d465e52746be2b4bae983670711e6e66657 C100	newdir/exec.sh	oldroot/newdir/exec.sh
-	:100644 100644 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 C100	newdir/interesting	oldroot/newdir/interesting
+	cat >expect <<-EOF &&
+	:100755 100755 $newf $newf C100	file2/newf	oldroot/file2/newf
+	:100644 100644 $oldf $oldf C100	file2/oldf	oldroot/file2/oldf
+	:100755 100755 $f4id $f4id C100	file4	oldroot/file4
+	:100755 100755 $f6id $f6id C100	newdir/exec.sh	oldroot/newdir/exec.sh
+	:100644 100644 $f5id $f5id C100	newdir/interesting	oldroot/newdir/interesting
 	EOF
 	cat >input <<-INPUT_END &&
 	commit refs/heads/N-copy-root-path
@@ -1276,10 +1347,10 @@ test_expect_success 'N: delete directory by copying' '
 '
 
 test_expect_success 'N: modify copied tree' '
-	cat >expect <<-\EOF &&
-	:100644 100644 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 C100	newdir/interesting	file3/file5
-	:100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc C100	file2/newf	file3/newf
-	:100644 100644 7123f7f44e39be127c5eb701e5968176ee9d78b1 7123f7f44e39be127c5eb701e5968176ee9d78b1 C100	file2/oldf	file3/oldf
+	cat >expect <<-EOF &&
+	:100644 100644 $f5id $f5id C100	newdir/interesting	file3/file5
+	:100755 100755 $newf $newf C100	file2/newf	file3/newf
+	:100644 100644 $oldf $oldf C100	file2/oldf	file3/oldf
 	EOF
 	subdir=$(git rev-parse refs/heads/branch^0:file2) &&
 	cat >input <<-INPUT_END &&
@@ -2106,12 +2177,27 @@ test_expect_success 'R: abort on receiving feature after data command' '
 	test_must_fail git fast-import <input
 '
 
+test_expect_success 'R: import-marks features forbidden by default' '
+	>git.marks &&
+	echo "feature import-marks=git.marks" >input &&
+	test_must_fail git fast-import <input &&
+	echo "feature import-marks-if-exists=git.marks" >input &&
+	test_must_fail git fast-import <input
+'
+
 test_expect_success 'R: only one import-marks feature allowed per stream' '
+	>git.marks &&
+	>git2.marks &&
 	cat >input <<-EOF &&
 	feature import-marks=git.marks
 	feature import-marks=git2.marks
 	EOF
 
+	test_must_fail git fast-import --allow-unsafe-features <input
+'
+
+test_expect_success 'R: export-marks feature forbidden by default' '
+	echo "feature export-marks=git.marks" >input &&
 	test_must_fail git fast-import <input
 '
 
@@ -2125,19 +2211,29 @@ test_expect_success 'R: export-marks feature results in a marks file being creat
 
 	EOF
 
-	cat input | git fast-import &&
+	git fast-import --allow-unsafe-features <input &&
 	grep :1 git.marks
 '
 
 test_expect_success 'R: export-marks options can be overridden by commandline options' '
-	cat input | git fast-import --export-marks=other.marks &&
-	grep :1 other.marks
+	cat >input <<-\EOF &&
+	feature export-marks=feature-sub/git.marks
+	blob
+	mark :1
+	data 3
+	hi
+
+	EOF
+	git fast-import --allow-unsafe-features \
+			--export-marks=cmdline-sub/other.marks <input &&
+	grep :1 cmdline-sub/other.marks &&
+	test_path_is_missing feature-sub
 '
 
 test_expect_success 'R: catch typo in marks file name' '
 	test_must_fail git fast-import --import-marks=nonexistent.marks </dev/null &&
 	echo "feature import-marks=nonexistent.marks" |
-	test_must_fail git fast-import
+	test_must_fail git fast-import --allow-unsafe-features
 '
 
 test_expect_success 'R: import and output marks can be the same file' '
@@ -2192,7 +2288,8 @@ test_expect_success 'R: --import-marks-if-exists' '
 test_expect_success 'R: feature import-marks-if-exists' '
 	rm -f io.marks &&
 
-	git fast-import --export-marks=io.marks <<-\EOF &&
+	git fast-import --export-marks=io.marks \
+			--allow-unsafe-features <<-\EOF &&
 	feature import-marks-if-exists=not_io.marks
 	EOF
 	test_must_be_empty io.marks &&
@@ -2203,7 +2300,8 @@ test_expect_success 'R: feature import-marks-if-exists' '
 	echo ":1 $blob" >expect &&
 	echo ":2 $blob" >>expect &&
 
-	git fast-import --export-marks=io.marks <<-\EOF &&
+	git fast-import --export-marks=io.marks \
+			--allow-unsafe-features <<-\EOF &&
 	feature import-marks-if-exists=io.marks
 	blob
 	mark :2
@@ -2216,7 +2314,8 @@ test_expect_success 'R: feature import-marks-if-exists' '
 	echo ":3 $blob" >>expect &&
 
 	git fast-import --import-marks=io.marks \
-			--export-marks=io.marks <<-\EOF &&
+			--export-marks=io.marks \
+			--allow-unsafe-features <<-\EOF &&
 	feature import-marks-if-exists=not_io.marks
 	blob
 	mark :3
@@ -2227,7 +2326,8 @@ test_expect_success 'R: feature import-marks-if-exists' '
 	test_cmp expect io.marks &&
 
 	git fast-import --import-marks-if-exists=not_io.marks \
-			--export-marks=io.marks <<-\EOF &&
+			--export-marks=io.marks \
+			--allow-unsafe-features <<-\EOF &&
 	feature import-marks-if-exists=io.marks
 	EOF
 	test_must_be_empty io.marks
@@ -2239,7 +2339,7 @@ test_expect_success 'R: import to output marks works without any content' '
 	feature export-marks=marks.new
 	EOF
 
-	cat input | git fast-import &&
+	git fast-import --allow-unsafe-features <input &&
 	test_cmp marks.out marks.new
 '
 
@@ -2249,7 +2349,7 @@ test_expect_success 'R: import marks prefers commandline marks file over the str
 	feature export-marks=marks.new
 	EOF
 
-	cat input | git fast-import --import-marks=marks.out &&
+	git fast-import --import-marks=marks.out --allow-unsafe-features <input &&
 	test_cmp marks.out marks.new
 '
 
@@ -2262,7 +2362,8 @@ test_expect_success 'R: multiple --import-marks= should be honoured' '
 
 	head -n2 marks.out > one.marks &&
 	tail -n +3 marks.out > two.marks &&
-	git fast-import --import-marks=one.marks --import-marks=two.marks <input &&
+	git fast-import --import-marks=one.marks --import-marks=two.marks \
+		--allow-unsafe-features <input &&
 	test_cmp marks.out combined.marks
 '
 
@@ -2275,7 +2376,7 @@ test_expect_success 'R: feature relative-marks should be honoured' '
 
 	mkdir -p .git/info/fast-import/ &&
 	cp marks.new .git/info/fast-import/relative.in &&
-	git fast-import <input &&
+	git fast-import --allow-unsafe-features <input &&
 	test_cmp marks.new .git/info/fast-import/relative.out
 '
 
@@ -2287,7 +2388,7 @@ test_expect_success 'R: feature no-relative-marks should be honoured' '
 	feature export-marks=non-relative.out
 	EOF
 
-	git fast-import <input &&
+	git fast-import --allow-unsafe-features <input &&
 	test_cmp marks.new non-relative.out
 '
 
@@ -2440,9 +2541,6 @@ test_expect_success PIPE 'R: copy using cat-file' '
 	echo $expect_id blob $expect_len >expect.response &&
 
 	rm -f blobs &&
-	cat >frontend <<-\FRONTEND_END &&
-	#!/bin/sh
-	FRONTEND_END
 
 	mkfifo blobs &&
 	(
@@ -2557,7 +2655,7 @@ test_expect_success 'R: quiet option results in no stats being output' '
 
 	EOF
 
-	cat input | git fast-import 2> output &&
+	git fast-import 2>output <input &&
 	test_must_be_empty output
 '
 
@@ -2635,7 +2733,7 @@ test_expect_success 'R: corrupt lines do not mess marks file' '
 	rm -f io.marks &&
 	blob=$(echo hi | git hash-object --stdin) &&
 	cat >expect <<-EOF &&
-	:3 0000000000000000000000000000000000000000
+	:3 $ZERO_OID
 	:1 $blob
 	:2 $blob
 	EOF
@@ -2781,7 +2879,6 @@ test_expect_success 'S: filemodify with garbage after mark must fail' '
 	COMMIT
 	M 100644 :403x hello.c
 	EOF
-	cat err &&
 	test_i18ngrep "space after mark" err
 '
 
@@ -2798,7 +2895,6 @@ test_expect_success 'S: filemodify with garbage after inline must fail' '
 	inline
 	BLOB
 	EOF
-	cat err &&
 	test_i18ngrep "nvalid dataref" err
 '
 
@@ -2812,7 +2908,6 @@ test_expect_success 'S: filemodify with garbage after sha1 must fail' '
 	COMMIT
 	M 100644 ${sha1}x hello.c
 	EOF
-	cat err &&
 	test_i18ngrep "space after SHA1" err
 '
 
@@ -2828,7 +2923,6 @@ test_expect_success 'S: notemodify with garbage after mark dataref must fail' '
 	COMMIT
 	N :202x :302
 	EOF
-	cat err &&
 	test_i18ngrep "space after mark" err
 '
 
@@ -2844,7 +2938,6 @@ test_expect_success 'S: notemodify with garbage after inline dataref must fail'
 	note blob
 	BLOB
 	EOF
-	cat err &&
 	test_i18ngrep "nvalid dataref" err
 '
 
@@ -2858,7 +2951,6 @@ test_expect_success 'S: notemodify with garbage after sha1 dataref must fail' '
 	COMMIT
 	N ${sha1}x :302
 	EOF
-	cat err &&
 	test_i18ngrep "space after SHA1" err
 '
 
@@ -2874,7 +2966,6 @@ test_expect_success 'S: notemodify with garbage after mark commit-ish must fail'
 	COMMIT
 	N :202 :302x
 	EOF
-	cat err &&
 	test_i18ngrep "after mark" err
 '
 
@@ -2908,7 +2999,6 @@ test_expect_success 'S: from with garbage after mark must fail' '
 	EOF
 
 	# now evaluate the error
-	cat err &&
 	test_i18ngrep "after mark" err
 '
 
@@ -2928,7 +3018,6 @@ test_expect_success 'S: merge with garbage after mark must fail' '
 	merge :303x
 	M 100644 :403 hello.c
 	EOF
-	cat err &&
 	test_i18ngrep "after mark" err
 '
 
@@ -2944,7 +3033,6 @@ test_expect_success 'S: tag with garbage after mark must fail' '
 	tag S
 	TAG
 	EOF
-	cat err &&
 	test_i18ngrep "after mark" err
 '
 
@@ -2955,7 +3043,6 @@ test_expect_success 'S: cat-blob with garbage after mark must fail' '
 	test_must_fail git fast-import --import-marks=marks <<-EOF 2>err &&
 	cat-blob :403x
 	EOF
-	cat err &&
 	test_i18ngrep "after mark" err
 '
 
@@ -2966,7 +3053,6 @@ test_expect_success 'S: ls with garbage after mark must fail' '
 	test_must_fail git fast-import --import-marks=marks <<-EOF 2>err &&
 	ls :302x hello.c
 	EOF
-	cat err &&
 	test_i18ngrep "space after mark" err
 '
 
@@ -2975,7 +3061,6 @@ test_expect_success 'S: ls with garbage after sha1 must fail' '
 	test_must_fail git fast-import --import-marks=marks <<-EOF 2>err &&
 	ls ${sha1}x hello.c
 	EOF
-	cat err &&
 	test_i18ngrep "space after tree-ish" err
 '
 
@@ -2999,7 +3084,7 @@ test_expect_success 'T: delete branch' '
 	git branch to-delete &&
 	git fast-import <<-EOF &&
 	reset refs/heads/to-delete
-	from 0000000000000000000000000000000000000000
+	from $ZERO_OID
 	EOF
 	test_must_fail git rev-parse --verify refs/heads/to-delete
 '
@@ -3039,6 +3124,9 @@ test_expect_success 'U: initialize for U tests' '
 
 	INPUT_END
 
+	f7id=$(echo "blob 1" | git hash-object --stdin) &&
+	f8id=$(echo "sleep well" | git hash-object --stdin) &&
+	f9id=$(echo "au revoir" | git hash-object --stdin) &&
 	git fast-import <input
 '
 
@@ -3059,7 +3147,7 @@ test_expect_success 'U: filedelete file succeeds' '
 
 test_expect_success 'U: validate file delete result' '
 	cat >expect <<-EOF &&
-	:100644 000000 2907ebb4bf85d91bf0716bb3bd8a68ef48d6da76 0000000000000000000000000000000000000000 D	good/night.txt
+	:100644 000000 $f8id $ZERO_OID D	good/night.txt
 	EOF
 
 	git diff-tree -M -r U^1 U >actual &&
@@ -3084,7 +3172,7 @@ test_expect_success 'U: filedelete directory succeeds' '
 
 test_expect_success 'U: validate directory delete result' '
 	cat >expect <<-EOF &&
-	:100644 000000 69cb75792f55123d8389c156b0b41c2ff00ed507 0000000000000000000000000000000000000000 D	good/bye.txt
+	:100644 000000 $f9id $ZERO_OID D	good/bye.txt
 	EOF
 
 	git diff-tree -M -r U^1 U >actual &&
@@ -3109,7 +3197,7 @@ test_expect_success 'U: filedelete root succeeds' '
 
 test_expect_success 'U: validate root delete result' '
 	cat >expect <<-EOF &&
-	:100644 000000 c18147dc648481eeb65dc5e66628429a64843327 0000000000000000000000000000000000000000 D	hello.c
+	:100644 000000 $f7id $ZERO_OID D	hello.c
 	EOF
 
 	git diff-tree -M -r U^1 U >actual &&
@@ -3140,13 +3228,22 @@ background_import_then_checkpoint () {
 	exec 9<>V.output
 	rm V.output
 
-	git fast-import $options <&8 >&9 &
-	echo $! >V.pid
+	(
+		git fast-import $options <&8 >&9 &
+		echo $! >&9
+		wait $!
+		echo >&2 "background fast-import terminated too early with exit code $?"
+		# Un-block the read loop in the main shell process.
+		echo >&9 UNEXPECTED
+	) &
+	sh_pid=$!
+	read fi_pid <&9
 	# We don't mind if fast-import has already died by the time the test
 	# ends.
 	test_when_finished "
 		exec 8>&-; exec 9>&-;
-		kill $(cat V.pid) && wait $(cat V.pid)
+		kill $sh_pid && wait $sh_pid
+		kill $fi_pid && wait $fi_pid
 		true"
 
 	# Start in the background to ensure we adhere strictly to (blocking)
@@ -3166,6 +3263,9 @@ background_import_then_checkpoint () {
 		then
 			error=0
 			break
+		elif test "$output" = "UNEXPECTED"
+		then
+			break
 		fi
 		# otherwise ignore cruft
 		echo >&2 "cruft: $output"
@@ -3178,7 +3278,7 @@ background_import_then_checkpoint () {
 }
 
 background_import_still_running () {
-	if ! kill -0 "$(cat V.pid)"
+	if ! kill -0 "$fi_pid"
 	then
 		echo >&2 "background fast-import terminated too early"
 		false
@@ -3319,4 +3419,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
diff --git a/third_party/git/t/t9301-fast-import-notes.sh b/third_party/git/t/t9301-fast-import-notes.sh
index dadc70b7d5..14c1baa739 100755
--- a/third_party/git/t/t9301-fast-import-notes.sh
+++ b/third_party/git/t/t9301-fast-import-notes.sh
@@ -275,7 +275,7 @@ $whitespace
     third note for first commit
 EXPECT_END
 
-test_expect_success 'add concatentation notes with M command' '
+test_expect_success 'add concatenation notes with M command' '
 
 	git fast-import <input &&
 	GIT_NOTES_REF=refs/notes/test git log | grep "^    " > actual &&
@@ -470,12 +470,13 @@ test_expect_success 'add lots of commits and notes' '
 '
 
 test_expect_success 'verify that lots of notes trigger a fanout scheme' '
+	hexsz=$(test_oid hexsz) &&
 
 	# None of the entries in the top-level notes tree should be a full SHA1
 	git ls-tree --name-only refs/notes/many_notes |
 	while read path
 	do
-		if test $(expr length "$path") -ge 40
+		if test $(expr length "$path") -ge $hexsz
 		then
 			return 1
 		fi
@@ -518,7 +519,7 @@ test_expect_success 'verify that importing a notes tree respects the fanout sche
 	git ls-tree --name-only refs/notes/other_notes |
 	while read path
 	do
-		if test $(expr length "$path") -ge 40
+		if test $(expr length "$path") -ge $hexsz
 		then
 			return 1
 		fi
@@ -593,7 +594,7 @@ test_expect_success 'verify that changing notes respect existing fanout' '
 	git ls-tree --name-only refs/notes/many_notes |
 	while read path
 	do
-		if test $(expr length "$path") -ge 40
+		if test $(expr length "$path") -ge $hexsz
 		then
 			return 1
 		fi
@@ -616,7 +617,7 @@ i=$(($num_commits - $remaining_notes))
 for sha1 in $(git rev-list -n $i refs/heads/many_commits)
 do
 	cat >>input <<INPUT_END
-N 0000000000000000000000000000000000000000 $sha1
+N $ZERO_OID $sha1
 INPUT_END
 done
 
@@ -646,7 +647,6 @@ test_expect_success 'remove lots of notes' '
 '
 
 test_expect_success 'verify that removing notes trigger fanout consolidation' '
-
 	# All entries in the top-level notes tree should be a full SHA1
 	git ls-tree --name-only -r refs/notes/many_notes |
 	while read path
@@ -656,7 +656,7 @@ test_expect_success 'verify that removing notes trigger fanout consolidation' '
 		test "$path" = "deadbeef" && continue
 		test "$path" = "de/adbeef" && continue
 
-		if test $(expr length "$path") -ne 40
+		if test $(expr length "$path") -ne $hexsz
 		then
 			return 1
 		fi
diff --git a/third_party/git/t/t9350-fast-export.sh b/third_party/git/t/t9350-fast-export.sh
index b4004e05c2..1372842559 100755
--- a/third_party/git/t/t9350-fast-export.sh
+++ b/third_party/git/t/t9350-fast-export.sh
@@ -53,6 +53,33 @@ test_expect_success 'fast-export | fast-import' '
 
 '
 
+test_expect_success 'fast-export ^muss^{commit} muss' '
+	git fast-export --tag-of-filtered-object=rewrite ^muss^{commit} muss >actual &&
+	cat >expected <<-EOF &&
+	tag muss
+	from $(git rev-parse --verify muss^{commit})
+	$(git cat-file tag muss | grep tagger)
+	data 9
+	valentin
+
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'fast-export --mark-tags ^muss^{commit} muss' '
+	git fast-export --mark-tags --tag-of-filtered-object=rewrite ^muss^{commit} muss >actual &&
+	cat >expected <<-EOF &&
+	tag muss
+	mark :1
+	from $(git rev-parse --verify muss^{commit})
+	$(git cat-file tag muss | grep tagger)
+	data 9
+	valentin
+
+	EOF
+	test_cmp expected actual
+'
+
 test_expect_success 'fast-export master~2..master' '
 
 	git fast-export master~2..master >actual &&
@@ -105,12 +132,12 @@ test_expect_success 'reencoding iso-8859-7' '
 	sed "s/wer/i18n/" iso-8859-7.fi |
 		(cd new &&
 		 git fast-import &&
-		 # The commit object, if not re-encoded, would be 240 bytes.
+		 # The commit object, if not re-encoded, would be 200 bytes plus hash.
 		 # Removing the "encoding iso-8859-7\n" header drops 20 bytes.
 		 # Re-encoding the Pi character from \xF0 (\360) in iso-8859-7
 		 # to \xCF\x80 (\317\200) in UTF-8 adds a byte.  Check for
 		 # the expected size.
-		 test 221 -eq "$(git cat-file -s i18n)" &&
+		 test $(($(test_oid hexsz) + 181)) -eq "$(git cat-file -s i18n)" &&
 		 # ...and for the expected translation of bytes.
 		 git cat-file commit i18n >actual &&
 		 grep $(printf "\317\200") actual &&
@@ -137,12 +164,12 @@ test_expect_success 'preserving iso-8859-7' '
 	sed "s/wer/i18n-no-recoding/" iso-8859-7.fi |
 		(cd new &&
 		 git fast-import &&
-		 # The commit object, if not re-encoded, is 240 bytes.
+		 # The commit object, if not re-encoded, is 200 bytes plus hash.
 		 # Removing the "encoding iso-8859-7\n" header would drops 20
 		 # bytes.  Re-encoding the Pi character from \xF0 (\360) in
 		 # iso-8859-7 to \xCF\x80 (\317\200) in UTF-8 adds a byte.
 		 # Check for the expected size...
-		 test 240 -eq "$(git cat-file -s i18n-no-recoding)" &&
+		 test $(($(test_oid hexsz) + 200)) -eq "$(git cat-file -s i18n-no-recoding)" &&
 		 # ...as well as the expected byte.
 		 git cat-file commit i18n-no-recoding >actual &&
 		 grep $(printf "\360") actual &&
@@ -165,7 +192,7 @@ test_expect_success 'encoding preserved if reencoding fails' '
 		 grep ^encoding actual &&
 		 # Verify that the commit has the expected size; i.e.
 		 # that no bytes were re-encoded to a different encoding.
-		 test 252 -eq "$(git cat-file -s i18n-invalid)" &&
+		 test $(($(test_oid hexsz) + 212)) -eq "$(git cat-file -s i18n-invalid)" &&
 		 # ...and check for the original special bytes
 		 grep $(printf "\360") actual &&
 		 grep $(printf "\377") actual)
@@ -513,10 +540,41 @@ test_expect_success 'tree_tag'        '
 '
 
 # NEEDSWORK: not just check return status, but validate the output
+# Note that these tests DO NOTHING other than print a warning that
+# they are omitting the one tag we asked them to export (because the
+# tags resolve to a tree).  They exist just to make sure we do not
+# abort but instead just warn.
 test_expect_success 'tree_tag-obj'    'git fast-export tree_tag-obj'
 test_expect_success 'tag-obj_tag'     'git fast-export tag-obj_tag'
 test_expect_success 'tag-obj_tag-obj' 'git fast-export tag-obj_tag-obj'
 
+test_expect_success 'handling tags of blobs' '
+	git tag -a -m "Tag of a blob" blobtag $(git rev-parse master:file) &&
+	git fast-export blobtag >actual &&
+	cat >expect <<-EOF &&
+	blob
+	mark :1
+	data 9
+	die Luft
+
+	tag blobtag
+	from :1
+	tagger $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+	data 14
+	Tag of a blob
+
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'handling nested tags' '
+	git tag -a -m "This is a nested tag" nested muss &&
+	git fast-export --mark-tags nested >output &&
+	grep "^from $ZERO_OID$" output &&
+	grep "^tag nested$" output >tag_lines &&
+	test_line_count = 2 tag_lines
+'
+
 test_expect_success 'directory becomes symlink'        '
 	git init dirtosymlink &&
 	git init result &&
@@ -542,9 +600,10 @@ test_expect_success 'directory becomes symlink'        '
 
 test_expect_success 'fast-export quotes pathnames' '
 	git init crazy-paths &&
+	test_config -C crazy-paths core.protectNTFS false &&
 	(cd crazy-paths &&
 	 blob=$(echo foo | git hash-object -w --stdin) &&
-	 git update-index --add \
+	 git -c core.protectNTFS=false update-index --add \
 		--cacheinfo 100644 $blob "$(printf "path with\\nnewline")" \
 		--cacheinfo 100644 $blob "path with \"quote\"" \
 		--cacheinfo 100644 $blob "path with \\backslash" \
@@ -567,17 +626,15 @@ test_expect_success 'fast-export quotes pathnames' '
 '
 
 test_expect_success 'test bidirectionality' '
-	>marks-cur &&
-	>marks-new &&
 	git init marks-test &&
-	git fast-export --export-marks=marks-cur --import-marks=marks-cur --branches | \
-	git --git-dir=marks-test/.git fast-import --export-marks=marks-new --import-marks=marks-new &&
+	git fast-export --export-marks=marks-cur --import-marks-if-exists=marks-cur --branches | \
+	git --git-dir=marks-test/.git fast-import --export-marks=marks-new --import-marks-if-exists=marks-new &&
 	(cd marks-test &&
 	git reset --hard &&
 	echo Wohlauf > file &&
 	git commit -a -m "back in time") &&
-	git --git-dir=marks-test/.git fast-export --export-marks=marks-new --import-marks=marks-new --branches | \
-	git fast-import --export-marks=marks-cur --import-marks=marks-cur
+	git --git-dir=marks-test/.git fast-export --export-marks=marks-new --import-marks-if-exists=marks-new --branches | \
+	git fast-import --export-marks=marks-cur --import-marks-if-exists=marks-cur
 '
 
 cat > expected << EOF
@@ -637,7 +694,7 @@ test_expect_success 'delete ref because entire history excluded' '
 	git fast-export to-delete ^to-delete >actual &&
 	cat >expected <<-EOF &&
 	reset refs/heads/to-delete
-	from 0000000000000000000000000000000000000000
+	from $ZERO_OID
 
 	EOF
 	test_cmp expected actual
@@ -647,7 +704,7 @@ test_expect_success 'delete refspec' '
 	git fast-export --refspec :refs/heads/to-delete >actual &&
 	cat >expected <<-EOF &&
 	reset refs/heads/to-delete
-	from 0000000000000000000000000000000000000000
+	from $ZERO_OID
 
 	EOF
 	test_cmp expected actual
diff --git a/third_party/git/t/t9351-fast-export-anonymize.sh b/third_party/git/t/t9351-fast-export-anonymize.sh
index 897dc50907..5ac2c3b5ee 100755
--- a/third_party/git/t/t9351-fast-export-anonymize.sh
+++ b/third_party/git/t/t9351-fast-export-anonymize.sh
@@ -6,15 +6,24 @@ test_description='basic tests for fast-export --anonymize'
 test_expect_success 'setup simple repo' '
 	test_commit base &&
 	test_commit foo &&
+	test_commit retain-me &&
 	git checkout -b other HEAD^ &&
 	mkdir subdir &&
 	test_commit subdir/bar &&
 	test_commit subdir/xyzzy &&
+	fake_commit=$(echo $ZERO_OID | sed s/0/a/) &&
+	git update-index --add --cacheinfo 160000,$fake_commit,link1 &&
+	git update-index --add --cacheinfo 160000,$fake_commit,link2 &&
+	git commit -m "add gitlink" &&
 	git tag -m "annotated tag" mytag
 '
 
 test_expect_success 'export anonymized stream' '
-	git fast-export --anonymize --all >stream
+	git fast-export --anonymize --all \
+		--anonymize-map=retain-me \
+		--anonymize-map=xyzzy:custom-name \
+		--anonymize-map=other \
+		>stream
 '
 
 # this also covers commit messages
@@ -26,12 +35,23 @@ test_expect_success 'stream omits path names' '
 	! grep xyzzy stream
 '
 
-test_expect_success 'stream allows master as refname' '
-	grep master stream
+test_expect_success 'stream contains user-specified names' '
+	grep retain-me stream &&
+	grep custom-name stream
+'
+
+test_expect_success 'stream omits gitlink oids' '
+	# avoid relying on the whole oid to remain hash-agnostic; this is
+	# plenty to be unique within our test case
+	! grep a000000000000000000 stream
+'
+
+test_expect_success 'stream retains other as refname' '
+	grep other stream
 '
 
 test_expect_success 'stream omits other refnames' '
-	! grep other stream &&
+	! grep master stream &&
 	! grep mytag stream
 '
 
@@ -57,7 +77,8 @@ test_expect_success 'import stream to new repository' '
 test_expect_success 'result has two branches' '
 	git for-each-ref --format="%(refname)" refs/heads >branches &&
 	test_line_count = 2 branches &&
-	other_branch=$(grep -v refs/heads/master branches)
+	other_branch=refs/heads/other &&
+	main_branch=$(grep -v $other_branch branches)
 '
 
 test_expect_success 'repo has original shape and timestamps' '
@@ -65,34 +86,35 @@ test_expect_success 'repo has original shape and timestamps' '
 		git log --format="%m %ct" --left-right --boundary "$@"
 	} &&
 	(cd .. && shape master...other) >expect &&
-	shape master...$other_branch >actual &&
+	shape $main_branch...$other_branch >actual &&
 	test_cmp expect actual
 '
 
 test_expect_success 'root tree has original shape' '
 	# the output entries are not necessarily in the same
-	# order, but we know at least that we will have one tree
-	# and one blob, so just check the sorted order
-	cat >expect <<-\EOF &&
-	blob
-	tree
-	EOF
+	# order, but we should at least have the same set of
+	# object types.
+	git -C .. ls-tree HEAD >orig-root &&
+	cut -d" " -f2 <orig-root | sort >expect &&
 	git ls-tree $other_branch >root &&
 	cut -d" " -f2 <root | sort >actual &&
 	test_cmp expect actual
 '
 
 test_expect_success 'paths in subdir ended up in one tree' '
-	cat >expect <<-\EOF &&
-	blob
-	blob
-	EOF
+	git -C .. ls-tree other:subdir >orig-subdir &&
+	cut -d" " -f2 <orig-subdir | sort >expect &&
 	tree=$(grep tree root | cut -f2) &&
 	git ls-tree $other_branch:$tree >tree &&
 	cut -d" " -f2 <tree >actual &&
 	test_cmp expect actual
 '
 
+test_expect_success 'identical gitlinks got identical oid' '
+	awk "/commit/ { print \$3 }" <root | sort -u >commits &&
+	test_line_count = 1 commits
+'
+
 test_expect_success 'tag points to branch tip' '
 	git rev-parse $other_branch >expect &&
 	git for-each-ref --format="%(*objectname)" | grep . >actual &&
diff --git a/third_party/git/t/t9400-git-cvsserver-server.sh b/third_party/git/t/t9400-git-cvsserver-server.sh
index a5e5dca753..4a46f31c41 100755
--- a/third_party/git/t/t9400-git-cvsserver-server.sh
+++ b/third_party/git/t/t9400-git-cvsserver-server.sh
@@ -603,7 +603,7 @@ test_expect_success 'cvs server does not run with vanilla git-shell' '
 		cd cvswork &&
 		CVS_SERVER=$WORKDIR/remote-cvs &&
 		export CVS_SERVER &&
-		test_must_fail cvs log merge
+		! cvs log merge
 	)
 '
 
diff --git a/third_party/git/t/t9401-git-cvsserver-crlf.sh b/third_party/git/t/t9401-git-cvsserver-crlf.sh
index 84787eee9a..c7a0dd84a4 100755
--- a/third_party/git/t/t9401-git-cvsserver-crlf.sh
+++ b/third_party/git/t/t9401-git-cvsserver-crlf.sh
@@ -167,10 +167,10 @@ test_expect_success 'adding files' '
 
 test_expect_success 'updating' '
     git pull gitcvs.git &&
-    echo 'hi' > subdir/newfile.bin &&
-    echo 'junk' > subdir/file.h &&
-    echo 'hi' > subdir/newfile.c &&
-    echo 'hello' >> binfile.bin &&
+    echo "hi" >subdir/newfile.bin &&
+    echo "junk" >subdir/file.h &&
+    echo "hi" >subdir/newfile.c &&
+    echo "hello" >>binfile.bin &&
     git add subdir/newfile.bin subdir/file.h subdir/newfile.c binfile.bin &&
     git commit -q -m "Add and change some files" &&
     git push gitcvs.git >/dev/null &&
diff --git a/third_party/git/t/t9402-git-cvsserver-refs.sh b/third_party/git/t/t9402-git-cvsserver-refs.sh
index cf31ace667..6436c91a3c 100755
--- a/third_party/git/t/t9402-git-cvsserver-refs.sh
+++ b/third_party/git/t/t9402-git-cvsserver-refs.sh
@@ -178,7 +178,7 @@ test_expect_success 'setup v1.2 on b1' '
 	mkdir cdir &&
 	echo "cdir/cfile" >cdir/cfile &&
 	git add -A cdir adir t3 t2 &&
-	git commit -q -m 'v1.2' &&
+	git commit -q -m "v1.2" &&
 	git tag v1.2 &&
 	git push --tags gitcvs.git b1:b1
 '
diff --git a/third_party/git/t/t9500-gitweb-standalone-no-errors.sh b/third_party/git/t/t9500-gitweb-standalone-no-errors.sh
index cc8d463e01..b484e3e250 100755
--- a/third_party/git/t/t9500-gitweb-standalone-no-errors.sh
+++ b/third_party/git/t/t9500-gitweb-standalone-no-errors.sh
@@ -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 &&
@@ -621,12 +621,22 @@ test_expect_success \
 	 git config gitweb.snapshot "zip,tgz, tbz2" &&
 	 gitweb_run "p=.git;a=tree"'
 
-cat >.git/config <<\EOF
-# testing noval and alternate separator
-[gitweb]
-	blame
-	snapshot = zip tgz
-EOF
+test_expect_success 'setup' '
+	version=$(git config core.repositoryformatversion) &&
+	algo=$(test_might_fail git config extensions.objectformat) &&
+	cat >.git/config <<-\EOF &&
+	# testing noval and alternate separator
+	[gitweb]
+		blame
+		snapshot = zip tgz
+	EOF
+	git config core.repositoryformatversion "$version" &&
+	if test -n "$algo"
+	then
+		git config extensions.objectformat "$algo"
+	fi
+'
+
 test_expect_success \
 	'config override: tree view, features enabled in repo config (2)' \
 	'gitweb_run "p=.git;a=tree"'
@@ -675,8 +685,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 +714,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 +722,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 +739,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 +764,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 +779,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'
 
diff --git a/third_party/git/t/t9502-gitweb-standalone-parse-output.sh b/third_party/git/t/t9502-gitweb-standalone-parse-output.sh
index 0796a438bc..e38cbc97d3 100755
--- a/third_party/git/t/t9502-gitweb-standalone-parse-output.sh
+++ b/third_party/git/t/t9502-gitweb-standalone-parse-output.sh
@@ -188,8 +188,8 @@ test_expect_success 'forks: project_index lists all projects (incl. forks)' '
 '
 
 xss() {
-	echo >&2 "Checking $1..." &&
-	gitweb_run "$1" &&
+	echo >&2 "Checking $*..." &&
+	gitweb_run "$@" &&
 	if grep "$TAG" gitweb.body; then
 		echo >&2 "xss: $TAG should have been quoted in output"
 		return 1
@@ -200,7 +200,8 @@ xss() {
 test_expect_success 'xss checks' '
 	TAG="<magic-xss-tag>" &&
 	xss "a=rss&p=$TAG" &&
-	xss "a=rss&p=foo.git&f=$TAG"
+	xss "a=rss&p=foo.git&f=$TAG" &&
+	xss "" "$TAG+"
 '
 
 test_done
diff --git a/third_party/git/t/t9700/test.pl b/third_party/git/t/t9700/test.pl
index 34cd01366f..e046f7db76 100755
--- a/third_party/git/t/t9700/test.pl
+++ b/third_party/git/t/t9700/test.pl
@@ -23,6 +23,8 @@ sub adjust_dirsep {
 	return $path;
 }
 
+my $oid_re = qr/^[0-9a-fA-F]{40}(?:[0-9a-fA-F]{24})?$/;
+
 BEGIN { use_ok('Git') }
 
 # set up
@@ -59,15 +61,15 @@ ok($@, "config_bool: non-boolean values fail");
 open STDERR, ">&", $tmpstderr or die "cannot restore STDERR";
 
 # ident
-like($r->ident("aUthor"), qr/^A U Thor <author\@example.com> [0-9]+ \+0000$/,
+like($r->ident("aUthor"), qr/^A U Thor <author\@example.com> [0-9]+ [+-]\d{4}$/,
      "ident scalar: author (type)");
-like($r->ident("cOmmitter"), qr/^C O Mitter <committer\@example.com> [0-9]+ \+0000$/,
+like($r->ident("cOmmitter"), qr/^C O Mitter <committer\@example.com> [0-9]+ [+-]\d{4}$/,
      "ident scalar: committer (type)");
 is($r->ident("invalid"), "invalid", "ident scalar: invalid ident string (no parsing)");
 my ($name, $email, $time_tz) = $r->ident('author');
 is_deeply([$name, $email], ["A U Thor", "author\@example.com"],
 	 "ident array: author");
-like($time_tz, qr/[0-9]+ \+0000/, "ident array: author");
+like($time_tz, qr/[0-9]+ [+-]\d{4}/, "ident array: author");
 is_deeply([$r->ident("Name <email> 123 +0000")], ["Name", "email", "123 +0000"],
 	  "ident array: ident string");
 is_deeply([$r->ident("invalid")], [], "ident array: invalid ident string");
@@ -93,7 +95,7 @@ is(Git::hash_object("blob", $tmpfile), $file1hash, "hash_object: roundtrip");
 open TEMPFILE, ">$tmpfile" or die "Can't open $tmpfile: $!";
 print TEMPFILE my $test_text = "test blob, to be inserted\n";
 close TEMPFILE or die "Failed writing to $tmpfile: $!";
-like(our $newhash = $r->hash_and_insert_object($tmpfile), qr/[0-9a-fA-F]{40}/,
+like(our $newhash = $r->hash_and_insert_object($tmpfile), $oid_re,
      "hash_and_insert_object: returns hash");
 open TEMPFILE, "+>$tmpfile" or die "Can't open $tmpfile: $!";
 is($r->cat_blob($newhash, \*TEMPFILE), length $test_text, "cat_blob: roundtrip size");
@@ -119,7 +121,7 @@ is($r2->wc_subdir, "directory2/", "wc_subdir initial (2)");
 
 # commands in sub directory
 my $last_commit = $r2->command_oneline(qw(rev-parse --verify HEAD));
-like($last_commit, qr/^[0-9a-fA-F]{40}$/, 'rev-parse returned hash');
+like($last_commit, $oid_re, 'rev-parse returned hash');
 my $dir_commit = $r2->command_oneline('log', '-n1', '--pretty=format:%H', '.');
 isnt($last_commit, $dir_commit, 'log . does not show last commit');
 
diff --git a/third_party/git/t/t9800-git-p4-basic.sh b/third_party/git/t/t9800-git-p4-basic.sh
index 5856563068..c98c1dfc23 100755
--- a/third_party/git/t/t9800-git-p4-basic.sh
+++ b/third_party/git/t/t9800-git-p4-basic.sh
@@ -202,7 +202,6 @@ test_expect_success 'exit when p4 fails to produce marshaled output' '
 		export PATH &&
 		test_expect_code 1 git p4 clone --dest="$git" //depot >errs 2>&1
 	) &&
-	cat errs &&
 	test_i18ngrep ! Traceback errs
 '
 
diff --git a/third_party/git/t/t9809-git-p4-client-view.sh b/third_party/git/t/t9809-git-p4-client-view.sh
index 3cff1fce1b..9c9710d8c7 100755
--- a/third_party/git/t/t9809-git-p4-client-view.sh
+++ b/third_party/git/t/t9809-git-p4-client-view.sh
@@ -407,7 +407,7 @@ test_expect_success 'reinit depot' '
 '
 
 #
-# What happens when two files of the same name are overlayed together?
+# What happens when two files of the same name are overlaid together?
 # The last-listed file should take preference.
 #
 # //depot
diff --git a/third_party/git/t/t9810-git-p4-rcs.sh b/third_party/git/t/t9810-git-p4-rcs.sh
index 57b533dc6f..e3836888ec 100755
--- a/third_party/git/t/t9810-git-p4-rcs.sh
+++ b/third_party/git/t/t9810-git-p4-rcs.sh
@@ -294,7 +294,6 @@ test_expect_success 'cope with rcs keyword file deletion' '
 		echo "\$Revision\$" >kwdelfile.c &&
 		p4 add -t ktext kwdelfile.c &&
 		p4 submit -d "Add file to be deleted" &&
-		cat kwdelfile.c &&
 		grep 1 kwdelfile.c
 	) &&
 	git p4 clone --dest="$git" //depot &&
diff --git a/third_party/git/t/t9819-git-p4-case-folding.sh b/third_party/git/t/t9819-git-p4-case-folding.sh
index 600ce1e0b0..b4d93f0c17 100755
--- a/third_party/git/t/t9819-git-p4-case-folding.sh
+++ b/third_party/git/t/t9819-git-p4-case-folding.sh
@@ -30,7 +30,7 @@ test_expect_success 'Check p4 is in case-folding mode' '
 		cd "$cli" &&
 		>lc/FILE.TXT &&
 		p4 add lc/FILE.TXT &&
-		test_must_fail p4 submit -d "Cannot add file differing only in case" lc/FILE.TXT
+		! p4 submit -d "Cannot add file differing only in case" lc/FILE.TXT
 	)
 '
 
diff --git a/third_party/git/t/t9831-git-p4-triggers.sh b/third_party/git/t/t9831-git-p4-triggers.sh
index d743ca33ee..ff6c0352e6 100755
--- a/third_party/git/t/t9831-git-p4-triggers.sh
+++ b/third_party/git/t/t9831-git-p4-triggers.sh
@@ -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:
diff --git a/third_party/git/t/t9832-unshelve.sh b/third_party/git/t/t9832-unshelve.sh
index e9276c48f4..7194fb2855 100755
--- a/third_party/git/t/t9832-unshelve.sh
+++ b/third_party/git/t/t9832-unshelve.sh
@@ -29,8 +29,11 @@ test_expect_success 'init depot' '
 	)
 '
 
+# Create an initial clone, with a commit unrelated to the P4 change
+# on HEAD
 test_expect_success 'initial clone' '
-	git p4 clone --dest="$git" //depot/@all
+	git p4 clone --dest="$git" //depot/@all &&
+    test_commit -C "$git" "unrelated"
 '
 
 test_expect_success 'create shelved changelist' '
diff --git a/third_party/git/t/t9834-git-p4-file-dir-bug.sh b/third_party/git/t/t9834-git-p4-file-dir-bug.sh
new file mode 100755
index 0000000000..dac67e89d7
--- /dev/null
+++ b/third_party/git/t/t9834-git-p4-file-dir-bug.sh
@@ -0,0 +1,70 @@
+#!/bin/sh
+
+test_description='git p4 directory/file bug handling
+
+This test creates files and directories with the same name in perforce and
+checks that git-p4 recovers from the error at the same time as the perforce
+repository.'
+
+. ./lib-git-p4.sh
+
+test_expect_success 'start p4d' '
+	start_p4d &&
+	{ p4 configure set submit.collision.check=0 || :; }
+'
+
+test_expect_success 'init depot' '
+	(
+		cd "$cli" &&
+
+		touch add_file_add_dir_del_file add_file_add_dir_del_dir &&
+		p4 add add_file_add_dir_del_file add_file_add_dir_del_dir &&
+		mkdir add_dir_add_file_del_file add_dir_add_file_del_dir &&
+		touch add_dir_add_file_del_file/file add_dir_add_file_del_dir/file &&
+		p4 add add_dir_add_file_del_file/file add_dir_add_file_del_dir/file &&
+		p4 submit -d "add initial" &&
+
+		rm -f add_file_add_dir_del_file add_file_add_dir_del_dir &&
+		mkdir add_file_add_dir_del_file add_file_add_dir_del_dir &&
+		touch add_file_add_dir_del_file/file add_file_add_dir_del_dir/file &&
+		p4 add add_file_add_dir_del_file/file add_file_add_dir_del_dir/file &&
+		rm -rf add_dir_add_file_del_file add_dir_add_file_del_dir &&
+		touch add_dir_add_file_del_file add_dir_add_file_del_dir &&
+		p4 add add_dir_add_file_del_file add_dir_add_file_del_dir &&
+		p4 submit -d "add conflicting" &&
+
+		p4 delete -k add_file_add_dir_del_file &&
+		p4 delete -k add_file_add_dir_del_dir/file &&
+		p4 delete -k add_dir_add_file_del_file &&
+		p4 delete -k add_dir_add_file_del_dir/file &&
+		p4 submit -d "delete conflicting" &&
+
+		p4 delete -k "add_file_add_dir_del_file/file" &&
+		p4 delete -k "add_file_add_dir_del_dir" &&
+		p4 delete -k "add_dir_add_file_del_file/file" &&
+		p4 delete -k "add_dir_add_file_del_dir" &&
+		p4 submit -d "delete remaining"
+	)
+'
+
+test_expect_success 'clone with git-p4' '
+	git p4 clone --dest="$git" //depot/@1,3
+'
+
+test_expect_success 'check contents' '
+	test_path_is_dir "$git/add_file_add_dir_del_file" &&
+	test_path_is_file "$git/add_file_add_dir_del_dir" &&
+	test_path_is_dir "$git/add_dir_add_file_del_file" &&
+	test_path_is_file "$git/add_dir_add_file_del_dir"
+'
+
+test_expect_success 'rebase and check empty' '
+	git -C "$git" p4 rebase &&
+
+	test_path_is_missing "$git/add_file_add_dir_del_file" &&
+	test_path_is_missing "$git/add_file_add_dir_del_dir" &&
+	test_path_is_missing "$git/add_dir_add_file_del_file" &&
+	test_path_is_missing "$git/add_dir_add_file_del_dir"
+'
+
+test_done
diff --git a/third_party/git/t/t9902-completion.sh b/third_party/git/t/t9902-completion.sh
index 75512c3403..7b7bc6e4bd 100755
--- a/third_party/git/t/t9902-completion.sh
+++ b/third_party/git/t/t9902-completion.sh
@@ -28,10 +28,10 @@ complete ()
 #
 # (2) A test makes sure that common subcommands are included in the
 #     completion for "git <TAB>", and a plumbing is excluded.  "add",
-#     "filter-branch" and "ls-files" are listed for this.
+#     "rebase" and "ls-files" are listed for this.
 
-GIT_TESTING_ALL_COMMAND_LIST='add checkout check-attr filter-branch ls-files'
-GIT_TESTING_PORCELAIN_COMMAND_LIST='add checkout filter-branch'
+GIT_TESTING_ALL_COMMAND_LIST='add checkout check-attr rebase ls-files'
+GIT_TESTING_PORCELAIN_COMMAND_LIST='add checkout rebase'
 
 . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash"
 
@@ -378,7 +378,7 @@ test_expect_success '__gitdir - finds repo' '
 '
 
 
-test_expect_success '__gitdir - returns error when cant find repo' '
+test_expect_success '__gitdir - returns error when cannot find repo' '
 	(
 		__git_dir="non-existing" &&
 		test_must_fail __gitdir >"$actual"
@@ -494,7 +494,7 @@ test_expect_success '__gitcomp - prefix' '
 '
 
 test_expect_success '__gitcomp - suffix' '
-	test_gitcomp "branch.me" "master maint next pu" "branch." \
+	test_gitcomp "branch.me" "master maint next seen" "branch." \
 		"ma" "." <<-\EOF
 	branch.master.Z
 	branch.maint.Z
@@ -542,37 +542,37 @@ test_expect_success '__gitcomp - doesnt fail because of invalid variable name' '
 '
 
 read -r -d "" refs <<-\EOF
+main
 maint
-master
 next
-pu
+seen
 EOF
 
 test_expect_success '__gitcomp_nl - trailing space' '
 	test_gitcomp_nl "m" "$refs" <<-EOF
+	main Z
 	maint Z
-	master Z
 	EOF
 '
 
 test_expect_success '__gitcomp_nl - prefix' '
 	test_gitcomp_nl "--fixup=m" "$refs" "--fixup=" "m" <<-EOF
+	--fixup=main Z
 	--fixup=maint Z
-	--fixup=master Z
 	EOF
 '
 
 test_expect_success '__gitcomp_nl - suffix' '
 	test_gitcomp_nl "branch.ma" "$refs" "branch." "ma" "." <<-\EOF
+	branch.main.Z
 	branch.maint.Z
-	branch.master.Z
 	EOF
 '
 
 test_expect_success '__gitcomp_nl - no suffix' '
 	test_gitcomp_nl "ma" "$refs" "" "ma" "" <<-\EOF
+	mainZ
 	maintZ
-	masterZ
 	EOF
 '
 
@@ -945,7 +945,7 @@ test_expect_success 'setup for filtering matching refs' '
 	rm -f .git/FETCH_HEAD
 '
 
-test_expect_success '__git_refs - dont filter refs unless told so' '
+test_expect_success '__git_refs - do not filter refs unless told so' '
 	cat >expected <<-EOF &&
 	HEAD
 	master
@@ -1240,6 +1240,461 @@ test_expect_success '__git_complete_fetch_refspecs - fully qualified & prefix' '
 	test_cmp expected out
 '
 
+test_expect_success 'git switch - with no options, complete local branches and unique remote branch names for DWIM logic' '
+	test_completion "git switch " <<-\EOF
+	branch-in-other Z
+	master Z
+	master-in-other Z
+	matching-branch Z
+	EOF
+'
+
+test_expect_success 'git checkout - completes refs and unique remote branches for DWIM' '
+	test_completion "git checkout " <<-\EOF
+	HEAD Z
+	branch-in-other Z
+	master Z
+	master-in-other Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
+test_expect_success 'git switch - with --no-guess, complete only local branches' '
+	test_completion "git switch --no-guess " <<-\EOF
+	master Z
+	matching-branch Z
+	EOF
+'
+
+test_expect_success 'git switch - with GIT_COMPLETION_CHECKOUT_NO_GUESS=1, complete only local branches' '
+	GIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion "git switch " <<-\EOF
+	master Z
+	matching-branch Z
+	EOF
+'
+
+test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_GUESS=1, complete local branches and unique remote names for DWIM logic' '
+	GIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion "git switch --guess " <<-\EOF
+	branch-in-other Z
+	master Z
+	master-in-other Z
+	matching-branch Z
+	EOF
+'
+
+test_expect_success 'git switch - a later --guess overrides previous --no-guess, complete local and remote unique branches for DWIM' '
+	test_completion "git switch --no-guess --guess " <<-\EOF
+	branch-in-other Z
+	master Z
+	master-in-other Z
+	matching-branch Z
+	EOF
+'
+
+test_expect_success 'git switch - a later --no-guess overrides previous --guess, complete only local branches' '
+	test_completion "git switch --guess --no-guess " <<-\EOF
+	master Z
+	matching-branch Z
+	EOF
+'
+
+test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only completes refs' '
+	GIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion "git checkout " <<-\EOF
+	HEAD Z
+	master Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
+test_expect_success 'git checkout - --guess overrides GIT_COMPLETION_NO_GUESS=1, complete refs and unique remote branches for DWIM' '
+	GIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion "git checkout --guess " <<-\EOF
+	HEAD Z
+	branch-in-other Z
+	master Z
+	master-in-other Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
+test_expect_success 'git checkout - with --no-guess, only completes refs' '
+	test_completion "git checkout --no-guess " <<-\EOF
+	HEAD Z
+	master Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
+test_expect_success 'git checkout - a later --guess overrides previous --no-guess, complete refs and unique remote branches for DWIM' '
+	test_completion "git checkout --no-guess --guess " <<-\EOF
+	HEAD Z
+	branch-in-other Z
+	master Z
+	master-in-other Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
+test_expect_success 'git checkout - a later --no-guess overrides previous --guess, complete only refs' '
+	test_completion "git checkout --guess --no-guess " <<-\EOF
+	HEAD Z
+	master Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
+test_expect_success 'git switch - with --detach, complete all references' '
+	test_completion "git switch --detach " <<-\EOF
+	HEAD Z
+	master Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
+test_expect_success 'git checkout - with --detach, complete only references' '
+	test_completion "git checkout --detach " <<-\EOF
+	HEAD Z
+	master Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
+test_expect_success 'git switch - with -d, complete all references' '
+	test_completion "git switch -d " <<-\EOF
+	HEAD Z
+	master Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
+test_expect_success 'git checkout - with -d, complete only references' '
+	test_completion "git checkout -d " <<-\EOF
+	HEAD Z
+	master Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
+test_expect_success 'git switch - with --track, complete only remote branches' '
+	test_completion "git switch --track " <<-\EOF
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
+test_expect_success 'git checkout - with --track, complete only remote branches' '
+	test_completion "git checkout --track " <<-\EOF
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
+test_expect_success 'git switch - with --no-track, complete only local branch names' '
+	test_completion "git switch --no-track " <<-\EOF
+	master Z
+	matching-branch Z
+	EOF
+'
+
+test_expect_success 'git checkout - with --no-track, complete only local references' '
+	test_completion "git checkout --no-track " <<-\EOF
+	HEAD Z
+	master Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
+test_expect_success 'git switch - with -c, complete all references' '
+	test_completion "git switch -c new-branch " <<-\EOF
+	HEAD Z
+	master Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
+test_expect_success 'git switch - with -C, complete all references' '
+	test_completion "git switch -C new-branch " <<-\EOF
+	HEAD Z
+	master Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
+test_expect_success 'git switch - with -c and --track, complete all references' '
+	test_completion "git switch -c new-branch --track " <<-EOF
+	HEAD Z
+	master Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
+test_expect_success 'git switch - with -C and --track, complete all references' '
+	test_completion "git switch -C new-branch --track " <<-EOF
+	HEAD Z
+	master Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
+test_expect_success 'git switch - with -c and --no-track, complete all references' '
+	test_completion "git switch -c new-branch --no-track " <<-\EOF
+	HEAD Z
+	master Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
+test_expect_success 'git switch - with -C and --no-track, complete all references' '
+	test_completion "git switch -C new-branch --no-track " <<-\EOF
+	HEAD Z
+	master Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
+test_expect_success 'git checkout - with -b, complete all references' '
+	test_completion "git checkout -b new-branch " <<-\EOF
+	HEAD Z
+	master Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
+test_expect_success 'git checkout - with -B, complete all references' '
+	test_completion "git checkout -B new-branch " <<-\EOF
+	HEAD Z
+	master Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
+test_expect_success 'git checkout - with -b and --track, complete all references' '
+	test_completion "git checkout -b new-branch --track " <<-EOF
+	HEAD Z
+	master Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
+test_expect_success 'git checkout - with -B and --track, complete all references' '
+	test_completion "git checkout -B new-branch --track " <<-EOF
+	HEAD Z
+	master Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
+test_expect_success 'git checkout - with -b and --no-track, complete all references' '
+	test_completion "git checkout -b new-branch --no-track " <<-\EOF
+	HEAD Z
+	master Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
+test_expect_success 'git checkout - with -B and --no-track, complete all references' '
+	test_completion "git checkout -B new-branch --no-track " <<-\EOF
+	HEAD Z
+	master Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
+test_expect_success 'git switch - for -c, complete local branches and unique remote branches' '
+	test_completion "git switch -c " <<-\EOF
+	branch-in-other Z
+	master Z
+	master-in-other Z
+	matching-branch Z
+	EOF
+'
+
+test_expect_success 'git switch - for -C, complete local branches and unique remote branches' '
+	test_completion "git switch -C " <<-\EOF
+	branch-in-other Z
+	master Z
+	master-in-other Z
+	matching-branch Z
+	EOF
+'
+
+test_expect_success 'git switch - for -c with --no-guess, complete local branches only' '
+	test_completion "git switch --no-guess -c " <<-\EOF
+	master Z
+	matching-branch Z
+	EOF
+'
+
+test_expect_success 'git switch - for -C with --no-guess, complete local branches only' '
+	test_completion "git switch --no-guess -C " <<-\EOF
+	master Z
+	matching-branch Z
+	EOF
+'
+
+test_expect_success 'git switch - for -c with --no-track, complete local branches only' '
+	test_completion "git switch --no-track -c " <<-\EOF
+	master Z
+	matching-branch Z
+	EOF
+'
+
+test_expect_success 'git switch - for -C with --no-track, complete local branches only' '
+	test_completion "git switch --no-track -C " <<-\EOF
+	master Z
+	matching-branch Z
+	EOF
+'
+
+test_expect_success 'git checkout - for -b, complete local branches and unique remote branches' '
+	test_completion "git checkout -b " <<-\EOF
+	branch-in-other Z
+	master Z
+	master-in-other Z
+	matching-branch Z
+	EOF
+'
+
+test_expect_success 'git checkout - for -B, complete local branches and unique remote branches' '
+	test_completion "git checkout -B " <<-\EOF
+	branch-in-other Z
+	master Z
+	master-in-other Z
+	matching-branch Z
+	EOF
+'
+
+test_expect_success 'git checkout - for -b with --no-guess, complete local branches only' '
+	test_completion "git checkout --no-guess -b " <<-\EOF
+	master Z
+	matching-branch Z
+	EOF
+'
+
+test_expect_success 'git checkout - for -B with --no-guess, complete local branches only' '
+	test_completion "git checkout --no-guess -B " <<-\EOF
+	master Z
+	matching-branch Z
+	EOF
+'
+
+test_expect_success 'git checkout - for -b with --no-track, complete local branches only' '
+	test_completion "git checkout --no-track -b " <<-\EOF
+	master Z
+	matching-branch Z
+	EOF
+'
+
+test_expect_success 'git checkout - for -B with --no-track, complete local branches only' '
+	test_completion "git checkout --no-track -B " <<-\EOF
+	master Z
+	matching-branch Z
+	EOF
+'
+
+test_expect_success 'git switch - with --orphan completes local branch names and unique remote branch names' '
+	test_completion "git switch --orphan " <<-\EOF
+	branch-in-other Z
+	master Z
+	master-in-other Z
+	matching-branch Z
+	EOF
+'
+
+test_expect_success 'git switch - --orphan with branch already provided completes nothing else' '
+	test_completion "git switch --orphan master " <<-\EOF
+
+	EOF
+'
+
+test_expect_success 'git checkout - with --orphan completes local branch names and unique remote branch names' '
+	test_completion "git checkout --orphan " <<-\EOF
+	branch-in-other Z
+	master Z
+	master-in-other Z
+	matching-branch Z
+	EOF
+'
+
+test_expect_success 'git checkout - --orphan with branch already provided completes local refs for a start-point' '
+	test_completion "git checkout --orphan master " <<-\EOF
+	HEAD Z
+	master Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
 test_expect_success 'teardown after ref completion' '
 	git branch -d matching-branch &&
 	git tag -d matching-tag &&
@@ -1257,7 +1712,7 @@ test_path_completion ()
 		# In the following tests calling this function we only
 		# care about how __git_complete_index_file() deals with
 		# unusual characters in path names.  By requesting only
-		# untracked files we dont have to bother adding any
+		# untracked files we do not have to bother adding any
 		# paths to the index in those tests.
 		__git_complete_index_file --others &&
 		print_comp
@@ -1363,6 +1818,63 @@ test_expect_success 'teardown after path completion tests' '
 	       BS\\dir '$'separators\034in\035dir''
 '
 
+test_expect_success '__git_find_on_cmdline - single match' '
+	echo list >expect &&
+	(
+		words=(git command --opt list) &&
+		cword=${#words[@]} &&
+		__git_find_on_cmdline "add list remove" >actual
+	) &&
+	test_cmp expect actual
+'
+
+test_expect_success '__git_find_on_cmdline - multiple matches' '
+	echo remove >expect &&
+	(
+		words=(git command -o --opt remove list add) &&
+		cword=${#words[@]} &&
+		__git_find_on_cmdline "add list remove" >actual
+	) &&
+	test_cmp expect actual
+'
+
+test_expect_success '__git_find_on_cmdline - no match' '
+	(
+		words=(git command --opt branch) &&
+		cword=${#words[@]} &&
+		__git_find_on_cmdline "add list remove" >actual
+	) &&
+	test_must_be_empty actual
+'
+
+test_expect_success '__git_find_on_cmdline - single match with index' '
+	echo "3 list" >expect &&
+	(
+		words=(git command --opt list) &&
+		cword=${#words[@]} &&
+		__git_find_on_cmdline --show-idx "add list remove" >actual
+	) &&
+	test_cmp expect actual
+'
+
+test_expect_success '__git_find_on_cmdline - multiple matches with index' '
+	echo "4 remove" >expect &&
+	(
+		words=(git command -o --opt remove list add) &&
+		cword=${#words[@]} &&
+		__git_find_on_cmdline --show-idx "add list remove" >actual
+	) &&
+	test_cmp expect actual
+'
+
+test_expect_success '__git_find_on_cmdline - no match with index' '
+	(
+		words=(git command --opt branch) &&
+		cword=${#words[@]} &&
+		__git_find_on_cmdline --show-idx "add list remove" >actual
+	) &&
+	test_must_be_empty actual
+'
 
 test_expect_success '__git_get_config_variables' '
 	cat >expect <<-EOF &&
@@ -1392,12 +1904,12 @@ test_expect_success 'basic' '
 	# built-in
 	grep -q "^add \$" out &&
 	# script
-	grep -q "^filter-branch \$" out &&
+	grep -q "^rebase \$" out &&
 	# plumbing
 	! grep -q "^ls-files \$" out &&
 
-	run_completion "git f" &&
-	! grep -q -v "^f" out
+	run_completion "git r" &&
+	! grep -q -v "^r" out
 '
 
 test_expect_success 'double dash "git" itself' '
@@ -1438,6 +1950,8 @@ test_expect_success 'double dash "git checkout"' '
 	--no-guess Z
 	--no-... Z
 	--overlay Z
+	--pathspec-file-nul Z
+	--pathspec-from-file=Z
 	EOF
 '
 
@@ -1548,7 +2062,10 @@ test_expect_success 'complete tree filename with metacharacters' '
 '
 
 test_expect_success PERL 'send-email' '
-	test_completion "git send-email --cov" "--cover-letter " &&
+	test_completion "git send-email --cov" <<-\EOF &&
+	--cover-from-description=Z
+	--cover-letter Z
+	EOF
 	test_completion "git send-email ma" "master "
 '
 
@@ -1576,6 +2093,11 @@ test_expect_success 'complete files' '
 	echo modify > modified &&
 	test_completion "git add " "modified" &&
 
+	mkdir -p some/deep &&
+	touch some/deep/path &&
+	test_completion "git add some/" "some/deep" &&
+	git clean -f some &&
+
 	touch untracked &&
 
 	: TODO .gitignore should not be here &&
@@ -1698,6 +2220,69 @@ do
 	'
 done
 
+test_expect_success 'git config - section' '
+	test_completion "git config br" <<-\EOF
+	branch.Z
+	browser.Z
+	EOF
+'
+
+test_expect_success 'git config - variable name' '
+	test_completion "git config log.d" <<-\EOF
+	log.date Z
+	log.decorate Z
+	EOF
+'
+
+test_expect_success 'git config - value' '
+	test_completion "git config color.pager " <<-\EOF
+	false Z
+	true Z
+	EOF
+'
+
+test_expect_success 'git -c - section' '
+	test_completion "git -c br" <<-\EOF
+	branch.Z
+	browser.Z
+	EOF
+'
+
+test_expect_success 'git -c - variable name' '
+	test_completion "git -c log.d" <<-\EOF
+	log.date=Z
+	log.decorate=Z
+	EOF
+'
+
+test_expect_success 'git -c - value' '
+	test_completion "git -c color.pager=" <<-\EOF
+	false Z
+	true Z
+	EOF
+'
+
+test_expect_success 'git clone --config= - section' '
+	test_completion "git clone --config=br" <<-\EOF
+	branch.Z
+	browser.Z
+	EOF
+'
+
+test_expect_success 'git clone --config= - variable name' '
+	test_completion "git clone --config=log.d" <<-\EOF
+	log.date=Z
+	log.decorate=Z
+	EOF
+'
+
+test_expect_success 'git clone --config= - value' '
+	test_completion "git clone --config=color.pager=" <<-\EOF
+	false Z
+	true Z
+	EOF
+'
+
 test_expect_success 'sourcing the completion script clears cached commands' '
 	__git_compute_all_commands &&
 	verbose test -n "$__git_all_commands" &&
diff --git a/third_party/git/t/t9903-bash-prompt.sh b/third_party/git/t/t9903-bash-prompt.sh
index 88bc733ad6..ab5da2cabc 100755
--- a/third_party/git/t/t9903-bash-prompt.sh
+++ b/third_party/git/t/t9903-bash-prompt.sh
@@ -163,7 +163,7 @@ test_expect_success 'prompt - inside bare repository' '
 '
 
 test_expect_success 'prompt - interactive rebase' '
-	printf " (b1|REBASE-i 2/3)" >expected &&
+	printf " (b1|REBASE 2/3)" >expected &&
 	write_script fake_editor.sh <<-\EOF &&
 		echo "exec echo" >"$1"
 		echo "edit $(git log -1 --format="%h")" >>"$1"
@@ -180,7 +180,7 @@ test_expect_success 'prompt - interactive rebase' '
 '
 
 test_expect_success 'prompt - rebase merge' '
-	printf " (b2|REBASE-i 1/3)" >expected &&
+	printf " (b2|REBASE 1/3)" >expected &&
 	git checkout b2 &&
 	test_when_finished "git checkout master" &&
 	test_must_fail git rebase --merge b1 b2 &&
@@ -189,11 +189,11 @@ test_expect_success 'prompt - rebase merge' '
 	test_cmp expected "$actual"
 '
 
-test_expect_success 'prompt - rebase' '
+test_expect_success 'prompt - rebase am' '
 	printf " (b2|REBASE 1/3)" >expected &&
 	git checkout b2 &&
 	test_when_finished "git checkout master" &&
-	test_must_fail git rebase b1 b2 &&
+	test_must_fail git rebase --apply b1 b2 &&
 	test_when_finished "git rebase --abort" &&
 	__git_ps1 >"$actual" &&
 	test_cmp expected "$actual"
diff --git a/third_party/git/t/test-lib-functions.sh b/third_party/git/t/test-lib-functions.sh
index e0b3f28d3a..8d59b90348 100644
--- a/third_party/git/t/test-lib-functions.sh
+++ b/third_party/git/t/test-lib-functions.sh
@@ -228,9 +228,11 @@ test_commit () {
 # can be a tag pointing to the commit-to-merge.
 
 test_merge () {
+	label="$1" &&
+	shift &&
 	test_tick &&
-	git merge -m "$1" "$2" &&
-	git tag "$1"
+	git merge -m "$label" "$@" &&
+	git tag "$label"
 }
 
 # Efficiently create <nr> commits, each with a unique number (from 1 to <nr>
@@ -306,7 +308,7 @@ test_commit_bulk () {
 	total=$1
 
 	add_from=
-	if git -C "$indir" rev-parse --verify "$ref"
+	if git -C "$indir" rev-parse --quiet --verify "$ref"
 	then
 		add_from=t
 	fi
@@ -580,7 +582,7 @@ test_expect_failure () {
 	export test_prereq
 	if ! test_skip "$@"
 	then
-		say >&3 "checking known breakage: $2"
+		say >&3 "checking known breakage of $TEST_NUMBER.$test_count '$1': $2"
 		if test_run_ "$2" expecting_failure
 		then
 			test_known_broken_ok_ "$1"
@@ -600,7 +602,7 @@ test_expect_success () {
 	export test_prereq
 	if ! test_skip "$@"
 	then
-		say >&3 "expecting success: $2"
+		say >&3 "expecting success of $TEST_NUMBER.$test_count '$1': $2"
 		if test_run_ "$2"
 		then
 			test_ok_ "$1"
@@ -796,6 +798,37 @@ list_contains () {
 	return 1
 }
 
+# Returns success if the arguments indicate that a command should be
+# accepted by test_must_fail(). If the command is run with env, the env
+# and its corresponding variable settings will be stripped before we
+# test the command being run.
+test_must_fail_acceptable () {
+	if test "$1" = "env"
+	then
+		shift
+		while test $# -gt 0
+		do
+			case "$1" in
+			*?=*)
+				shift
+				;;
+			*)
+				break
+				;;
+			esac
+		done
+	fi
+
+	case "$1" in
+	git|__git*|test-tool|test_terminal)
+		return 0
+		;;
+	*)
+		return 1
+		;;
+	esac
+}
+
 # This is not among top-level (test_expect_success | test_expect_failure)
 # but is a prefix that can be used in the test script, like:
 #
@@ -815,6 +848,17 @@ list_contains () {
 #     Multiple signals can be specified as a comma separated list.
 #     Currently recognized signal names are: sigpipe, success.
 #     (Don't use 'success', use 'test_might_fail' instead.)
+#
+# Do not use this to run anything but "git" and other specific testable
+# commands (see test_must_fail_acceptable()).  We are not in the
+# business of vetting system supplied commands -- in other words, this
+# is wrong:
+#
+#    test_must_fail grep pattern output
+#
+# Instead use '!':
+#
+#    ! grep pattern output
 
 test_must_fail () {
 	case "$1" in
@@ -826,6 +870,11 @@ test_must_fail () {
 		_test_ok=
 		;;
 	esac
+	if ! test_must_fail_acceptable "$@"
+	then
+		echo >&7 "test_must_fail: only 'git' is allowed: $*"
+		return 1
+	fi
 	"$@" 2>&7
 	exit_code=$?
 	if test $exit_code -eq 0 && ! list_contains "$_test_ok" success
@@ -903,7 +952,13 @@ test_expect_code () {
 # - not all diff versions understand "-u"
 
 test_cmp() {
-	$GIT_TEST_CMP "$@"
+	test $# -eq 2 || BUG "test_cmp requires two arguments"
+	if ! eval "$GIT_TEST_CMP" '"$@"'
+	then
+		test "x$1" = x- || test -e "$1" || BUG "test_cmp '$1' missing"
+		test "x$2" = x- || test -e "$2" || BUG "test_cmp '$2' missing"
+		return 1
+	fi
 }
 
 # Check that the given config key has the expected value.
@@ -932,7 +987,13 @@ test_cmp_config() {
 # test_cmp_bin - helper to compare binary files
 
 test_cmp_bin() {
-	cmp "$@"
+	test $# -eq 2 || BUG "test_cmp_bin requires two arguments"
+	if ! cmp "$@"
+	then
+		test "x$1" = x- || test -e "$1" || BUG "test_cmp_bin '$1' missing"
+		test "x$2" = x- || test -e "$2" || BUG "test_cmp_bin '$2' missing"
+		return 1
+	fi
 }
 
 # Use this instead of test_cmp to compare files that contain expected and
@@ -1010,19 +1071,30 @@ test_must_be_empty () {
 	fi
 }
 
-# Tests that its two parameters refer to the same revision
+# Tests that its two parameters refer to the same revision, or if '!' is
+# provided first, that its other two parameters refer to different
+# revisions.
 test_cmp_rev () {
+	local op='=' wrong_result=different
+
+	if test $# -ge 1 && test "x$1" = 'x!'
+	then
+	    op='!='
+	    wrong_result='the same'
+	    shift
+	fi
 	if test $# != 2
 	then
 		error "bug in the test script: test_cmp_rev requires two revisions, but got $#"
 	else
 		local r1 r2
 		r1=$(git rev-parse --verify "$1") &&
-		r2=$(git rev-parse --verify "$2") &&
-		if test "$r1" != "$r2"
+		r2=$(git rev-parse --verify "$2") || return 1
+
+		if ! test "$r1" "$op" "$r2"
 		then
 			cat >&4 <<-EOF
-			error: two revisions point to different objects:
+			error: two revisions point to $wrong_result objects:
 			  '$1': $r1
 			  '$2': $r2
 			EOF
@@ -1173,6 +1245,34 @@ perl () {
 	command "$PERL_PATH" "$@" 2>&7
 } 7>&2 2>&4
 
+# Given the name of an environment variable with a bool value, normalize
+# its value to a 0 (true) or 1 (false or empty string) return code.
+#
+#   test_bool_env GIT_TEST_HTTPD <default-value>
+#
+# Return with code corresponding to the given default value if the variable
+# is unset.
+# Abort the test script if either the value of the variable or the default
+# are not valid bool values.
+
+test_bool_env () {
+	if test $# != 2
+	then
+		BUG "test_bool_env requires two parameters (variable name and default value)"
+	fi
+
+	git env--helper --type=bool --default="$2" --exit-code "$1"
+	ret=$?
+	case $ret in
+	0|1)	# unset or valid bool value
+		;;
+	*)	# invalid bool value or something unexpected
+		error >&7 "test_bool_env requires bool values both for \$$1 and for the default fallback"
+		;;
+	esac
+	return $ret
+}
+
 # Exit the test suite, either by skipping all remaining tests or by
 # exiting with an error. If our prerequisite variable $1 falls back
 # on a default assume we were opportunistically trying to set up some
@@ -1181,7 +1281,7 @@ perl () {
 # The error/skip message should be given by $2.
 #
 test_skip_or_die () {
-	if ! git env--helper --type=bool --default=false --exit-code $1
+	if ! test_bool_env "$1" false
 	then
 		skip_all=$2
 		test_done
@@ -1321,14 +1421,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.
@@ -1368,9 +1476,7 @@ test_set_hash () {
 
 # Detect the hash algorithm in use.
 test_detect_hash () {
-	# Currently we only support SHA-1, but in the future this function will
-	# actually detect the algorithm in use.
-	test_hash_algo='sha1'
+	test_hash_algo="${GIT_TEST_DEFAULT_HASH:-sha1}"
 }
 
 # Load common hash metadata and common placeholder object IDs for use with
@@ -1419,7 +1525,17 @@ test_oid_cache () {
 # Look up a per-hash value based on a key ($1).  The value must have been loaded
 # by test_oid_init or test_oid_cache.
 test_oid () {
-	local var="test_oid_${test_hash_algo}_$1" &&
+	local algo="${test_hash_algo}" &&
+
+	case "$1" in
+	--hash=*)
+		algo="${1#--hash=}" &&
+		shift;;
+	*)
+		;;
+	esac &&
+
+	local var="test_oid_${algo}_$1" &&
 
 	# If the variable is unset, we must be missing an entry for this
 	# key-hash pair, so exit with an error.
@@ -1475,3 +1591,73 @@ test_set_port () {
 	port=$(($port + ${GIT_TEST_STRESS_JOB_NR:-0}))
 	eval $var=$port
 }
+
+# Compare a file containing rev-list bitmap traversal output to its non-bitmap
+# counterpart. You can't just use test_cmp for this, because the two produce
+# subtly different output:
+#
+#   - regular output is in traversal order, whereas bitmap is split by type,
+#     with non-packed objects at the end
+#
+#   - regular output has a space and the pathname appended to non-commit
+#     objects; bitmap output omits this
+#
+# This function normalizes and compares the two. The second file should
+# always be the bitmap output.
+test_bitmap_traversal () {
+	if test "$1" = "--no-confirm-bitmaps"
+	then
+		shift
+	elif cmp "$1" "$2"
+	then
+		echo >&2 "identical raw outputs; are you sure bitmaps were used?"
+		return 1
+	fi &&
+	cut -d' ' -f1 "$1" | sort >"$1.normalized" &&
+	sort "$2" >"$2.normalized" &&
+	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
+}
+
+# Check that the given command was invoked as part of the
+# trace2-format trace on stdin.
+#
+#	test_subcommand [!] <command> <args>... < <trace>
+#
+# For example, to look for an invocation of "git upload-pack
+# /path/to/repo"
+#
+#	GIT_TRACE2_EVENT=event.log git fetch ... &&
+#	test_subcommand git upload-pack "$PATH" <event.log
+#
+# If the first parameter passed is !, this instead checks that
+# the given command was not called.
+#
+test_subcommand () {
+	local negate=
+	if test "$1" = "!"
+	then
+		negate=t
+		shift
+	fi
+
+	local expr=$(printf '"%s",' "$@")
+	expr="${expr%,}"
+
+	if test -n "$negate"
+	then
+		! grep "\[$expr\]"
+	else
+		grep "\[$expr\]"
+	fi
+}
diff --git a/third_party/git/t/test-lib.sh b/third_party/git/t/test-lib.sh
index 30b07e310f..ef31f40037 100644
--- a/third_party/git/t/test-lib.sh
+++ b/third_party/git/t/test-lib.sh
@@ -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
 
@@ -212,6 +245,8 @@ fi
 
 TEST_STRESS_JOB_SFX="${GIT_TEST_STRESS_JOB_NR:+.stress-$GIT_TEST_STRESS_JOB_NR}"
 TEST_NAME="$(basename "$0" .sh)"
+TEST_NUMBER="${TEST_NAME%%-*}"
+TEST_NUMBER="${TEST_NUMBER#t}"
 TEST_RESULTS_DIR="$TEST_OUTPUT_DIRECTORY/test-results"
 TEST_RESULTS_BASE="$TEST_RESULTS_DIR/$TEST_NAME$TEST_STRESS_JOB_SFX"
 TRASH_DIRECTORY="trash directory.$TEST_NAME$TEST_STRESS_JOB_SFX"
@@ -402,17 +437,27 @@ unset VISUAL EMAIL LANGUAGE COLUMNS $("$PERL_PATH" -e '
 unset XDG_CACHE_HOME
 unset XDG_CONFIG_HOME
 unset GITPERLLIB
-GIT_AUTHOR_EMAIL=author@example.com
+TEST_AUTHOR_LOCALNAME=author
+TEST_AUTHOR_DOMAIN=example.com
+GIT_AUTHOR_EMAIL=${TEST_AUTHOR_LOCALNAME}@${TEST_AUTHOR_DOMAIN}
 GIT_AUTHOR_NAME='A U Thor'
-GIT_COMMITTER_EMAIL=committer@example.com
+GIT_AUTHOR_DATE='1112354055 +0200'
+TEST_COMMITTER_LOCALNAME=committer
+TEST_COMMITTER_DOMAIN=example.com
+GIT_COMMITTER_EMAIL=${TEST_COMMITTER_LOCALNAME}@${TEST_COMMITTER_DOMAIN}
 GIT_COMMITTER_NAME='C O Mitter'
+GIT_COMMITTER_DATE='1112354055 +0200'
 GIT_MERGE_VERBOSITY=5
 GIT_MERGE_AUTOEDIT=no
 export GIT_MERGE_VERBOSITY GIT_MERGE_AUTOEDIT
 export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME
 export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME
+export GIT_COMMITTER_DATE GIT_AUTHOR_DATE
 export EDITOR
 
+GIT_DEFAULT_HASH="${GIT_TEST_DEFAULT_HASH:-sha1}"
+export GIT_DEFAULT_HASH
+
 # Tests using GIT_TRACE typically don't want <timestamp> <file>:<line> output
 GIT_TRACE_BARE=1
 export GIT_TRACE_BARE
@@ -488,25 +533,13 @@ 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='
 '
 
+# Single quote
+SQ=\'
+
 # UTF-8 ZERO WIDTH NON-JOINER, which HFS+ ignores
 # when case-folding filenames
 u200c=$(printf '\342\200\214')
@@ -567,6 +600,7 @@ export TERM
 
 error () {
 	say_color error "error: $*"
+	finalize_junit_xml
 	GIT_EXIT_OK=t
 	exit 1
 }
@@ -695,7 +729,7 @@ test_failure_ () {
 	say_color error "not ok $test_count - $1"
 	shift
 	printf '%s\n' "$*" | sed -e 's/^/#	/'
-	test "$immediate" = "" || { GIT_EXIT_OK=t; exit 1; }
+	test "$immediate" = "" || { finalize_junit_xml; GIT_EXIT_OK=t; exit 1; }
 }
 
 test_known_broken_ok_ () {
@@ -872,6 +906,7 @@ maybe_setup_valgrind () {
 	fi
 }
 
+trace_level_=0
 want_trace () {
 	test "$trace" = t && {
 		test "$verbose" = t || test "$verbose_log" = t
@@ -885,7 +920,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
 		$*"
 }
 
@@ -916,7 +951,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
 
@@ -994,6 +1030,12 @@ test_skip () {
 		to_skip=t
 		skipped_reason="GIT_SKIP_TESTS"
 	fi
+	if test -z "$to_skip" && test -n "$run_list" &&
+	   ! match_test_selector_list '--run' $test_count "$run_list"
+	then
+		to_skip=t
+		skipped_reason="--run"
+	fi
 	if test -z "$to_skip" && test -n "$test_prereq" &&
 	   ! test_have_prereq "$test_prereq"
 	then
@@ -1006,12 +1048,6 @@ test_skip () {
 		fi
 		skipped_reason="missing $missing_prereq${of_prereq}"
 	fi
-	if test -z "$to_skip" && test -n "$run_list" &&
-		! match_test_selector_list '--run' $test_count "$run_list"
-	then
-		to_skip=t
-		skipped_reason="--run"
-	fi
 
 	case "$to_skip" in
 	t)
@@ -1063,6 +1099,27 @@ write_junit_xml_testcase () {
 	junit_have_testcase=t
 }
 
+finalize_junit_xml () {
+	if test -n "$write_junit_xml" && test -n "$junit_xml_path"
+	then
+		test -n "$junit_have_testcase" || {
+			junit_start=$(test-tool date getnanos)
+			write_junit_xml_testcase "all tests skipped"
+		}
+
+		# adjust the overall time
+		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"
+
+		write_junit_xml "  </testsuite>" "</testsuites>"
+		write_junit_xml=
+	fi
+}
+
 test_atexit_cleanup=:
 test_atexit_handler () {
 	# In a succeeding test script 'test_atexit_handler' is invoked
@@ -1085,21 +1142,7 @@ test_done () {
 	# removed, so the commands can access pidfiles and socket files.
 	test_atexit_handler
 
-	if test -n "$write_junit_xml" && test -n "$junit_xml_path"
-	then
-		test -n "$junit_have_testcase" || {
-			junit_start=$(test-tool date getnanos)
-			write_junit_xml_testcase "all tests skipped"
-		}
-
-		# adjust the overall time
-		junit_time=$(test-tool date getnanos $junit_suite_start)
-		sed "s/<testsuite [^>]*/& time=\"$junit_time\"/" \
-			<"$junit_xml_path" >"$junit_xml_path.new"
-		mv "$junit_xml_path.new" "$junit_xml_path"
-
-		write_junit_xml "  </testsuite>" "</testsuites>"
-	fi
+	finalize_junit_xml
 
 	if test -z "$HARNESS_ACTIVE"
 	then
@@ -1367,6 +1410,21 @@ 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')
+OIDPATH_REGEX=$(test_oid_to_path $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
@@ -1391,23 +1449,23 @@ yes () {
 # The GIT_TEST_FAIL_PREREQS code hooks into test_set_prereq(), and
 # thus needs to be set up really early, and set an internal variable
 # for convenience so the hot test_set_prereq() codepath doesn't need
-# to call "git env--helper". Only do that work if needed by seeing if
-# GIT_TEST_FAIL_PREREQS is set at all.
+# to call "git env--helper" (via test_bool_env). Only do that work
+# if needed by seeing if GIT_TEST_FAIL_PREREQS is set at all.
 GIT_TEST_FAIL_PREREQS_INTERNAL=
 if test -n "$GIT_TEST_FAIL_PREREQS"
 then
-	if git env--helper --type=bool --default=0 --exit-code GIT_TEST_FAIL_PREREQS
+	if test_bool_env GIT_TEST_FAIL_PREREQS false
 	then
 		GIT_TEST_FAIL_PREREQS_INTERNAL=true
 		test_set_prereq FAIL_PREREQS
 	fi
 else
 	test_lazy_prereq FAIL_PREREQS '
-		git env--helper --type=bool --default=0 --exit-code GIT_TEST_FAIL_PREREQS
+		test_bool_env GIT_TEST_FAIL_PREREQS false
 	'
 fi
 
-# Fix some commands on Windows
+# Fix some commands on Windows, and other OS-specific things
 uname_s=$(uname -s)
 case $uname_s in
 *MINGW*)
@@ -1445,6 +1503,14 @@ case $uname_s in
 	;;
 esac
 
+# Detect arches where a few things don't work
+uname_m=$(uname -m)
+case $uname_m in
+parisc* | hppa*)
+	test_set_prereq HPPA
+	;;
+esac
+
 ( COLUMNS=1 && test $COLUMNS = 1 ) && test_set_prereq COLUMNS_CAN_BE_1
 test -z "$NO_PERL" && test_set_prereq PERL
 test -z "$NO_PTHREADS" && test_set_prereq PTHREADS
@@ -1462,7 +1528,7 @@ then
 fi
 
 test_lazy_prereq C_LOCALE_OUTPUT '
-	! git env--helper --type=bool --default=0 --exit-code GIT_TEST_GETTEXT_POISON
+	! test_bool_env GIT_TEST_GETTEXT_POISON false
 '
 
 if test -z "$GIT_TEST_CHECK_CACHE_TREE"
@@ -1584,7 +1650,7 @@ run_with_limited_cmdline () {
 }
 
 test_lazy_prereq CMDLINE_LIMIT '
-	test_have_prereq !MINGW,!CYGWIN &&
+	test_have_prereq !HPPA,!MINGW,!CYGWIN &&
 	run_with_limited_cmdline true
 '
 
@@ -1593,10 +1659,19 @@ run_with_limited_stack () {
 }
 
 test_lazy_prereq ULIMIT_STACK_SIZE '
-	test_have_prereq !MINGW,!CYGWIN &&
+	test_have_prereq !HPPA,!MINGW,!CYGWIN &&
 	run_with_limited_stack true
 '
 
+run_with_limited_open_files () {
+	(ulimit -n 32 && "$@")
+}
+
+test_lazy_prereq ULIMIT_FILE_DESCRIPTORS '
+	test_have_prereq !MINGW,!CYGWIN &&
+	run_with_limited_open_files true
+'
+
 build_option () {
 	git version --build-options |
 	sed -ne "s/^$1: //p"
@@ -1617,7 +1692,11 @@ test_lazy_prereq CURL '
 # which will not work with other hash algorithms and tests that work but don't
 # test anything meaningful (e.g. special values which cause short collisions).
 test_lazy_prereq SHA1 '
-	test $(git hash-object /dev/null) = e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
+	case "$GIT_DEFAULT_HASH" in
+	sha1) true ;;
+	"") test $(git hash-object /dev/null) = e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 ;;
+	*) false ;;
+	esac
 '
 
 test_lazy_prereq REBASE_P '
diff --git a/third_party/git/t/test-terminal.perl b/third_party/git/t/test-terminal.perl
index 46bf618479..1bcf01a9a4 100755
--- a/third_party/git/t/test-terminal.perl
+++ b/third_party/git/t/test-terminal.perl
@@ -81,24 +81,24 @@ if ($#ARGV < 1) {
 	die "usage: test-terminal program args";
 }
 $ENV{TERM} = 'vt100';
-my $master_in = new IO::Pty;
-my $master_out = new IO::Pty;
-my $master_err = new IO::Pty;
-$master_in->set_raw();
-$master_out->set_raw();
-$master_err->set_raw();
-$master_in->slave->set_raw();
-$master_out->slave->set_raw();
-$master_err->slave->set_raw();
-my $pid = start_child(\@ARGV, $master_in->slave, $master_out->slave, $master_err->slave);
-close $master_in->slave;
-close $master_out->slave;
-close $master_err->slave;
-my $in_pid = copy_stdin($master_in);
-copy_stdio($master_out, $master_err);
+my $parent_in = new IO::Pty;
+my $parent_out = new IO::Pty;
+my $parent_err = new IO::Pty;
+$parent_in->set_raw();
+$parent_out->set_raw();
+$parent_err->set_raw();
+$parent_in->slave->set_raw();
+$parent_out->slave->set_raw();
+$parent_err->slave->set_raw();
+my $pid = start_child(\@ARGV, $parent_in->slave, $parent_out->slave, $parent_err->slave);
+close $parent_in->slave;
+close $parent_out->slave;
+close $parent_err->slave;
+my $in_pid = copy_stdin($parent_in);
+copy_stdio($parent_out, $parent_err);
 my $ret = finish_child($pid);
 # If the child process terminates before our copy_stdin() process is able to
-# write all of its data to $master_in, the copy_stdin() process could stall.
+# write all of its data to $parent_in, the copy_stdin() process could stall.
 # Send SIGTERM to it to ensure it terminates.
 kill 'TERM', $in_pid;
 finish_child($in_pid);